239 lines
5.5 KiB
TypeScript
239 lines
5.5 KiB
TypeScript
import { defineStore } from 'pinia'
|
|
|
|
export interface PageVariant {
|
|
id: string
|
|
pageId: string
|
|
name: string
|
|
description: string
|
|
trafficWeight: number
|
|
isActive: boolean
|
|
leadCount: number
|
|
updatedAt: string
|
|
}
|
|
|
|
export interface CreateVariantInput {
|
|
pageId: string
|
|
name: string
|
|
description: string
|
|
trafficWeight: number
|
|
}
|
|
|
|
export interface UpdateVariantInput {
|
|
id: string
|
|
pageId: string
|
|
name?: string
|
|
description?: string
|
|
trafficWeight?: number
|
|
isActive?: boolean
|
|
leadCount?: number
|
|
}
|
|
|
|
type VariantMap = Record<string, PageVariant[]>
|
|
|
|
const toVariantIso = (value = 0) => {
|
|
const date = new Date()
|
|
date.setSeconds(date.getSeconds() + value)
|
|
|
|
return date.toISOString()
|
|
}
|
|
|
|
const variantSeed: VariantMap = {
|
|
lp01: [
|
|
{
|
|
id: 'v-lp01-a',
|
|
pageId: 'lp01',
|
|
name: '일반형',
|
|
description: '현재 운영 중인 기본 랜딩 버전',
|
|
trafficWeight: 70,
|
|
isActive: true,
|
|
leadCount: 76,
|
|
updatedAt: toVariantIso(-1200)
|
|
},
|
|
{
|
|
id: 'v-lp01-b',
|
|
pageId: 'lp01',
|
|
name: '강한 메시지형',
|
|
description: 'CTA 문구를 자극적으로 강화한 변형',
|
|
trafficWeight: 30,
|
|
isActive: true,
|
|
leadCount: 48,
|
|
updatedAt: toVariantIso(-900)
|
|
}
|
|
],
|
|
lp02: [
|
|
{
|
|
id: 'v-lp02-a',
|
|
pageId: 'lp02',
|
|
name: '기본 오퍼',
|
|
description: '초안 상태 테스트용 템플릿',
|
|
trafficWeight: 100,
|
|
isActive: true,
|
|
leadCount: 16,
|
|
updatedAt: toVariantIso(-2000)
|
|
}
|
|
],
|
|
lp03: [
|
|
{
|
|
id: 'v-lp03-a',
|
|
pageId: 'lp03',
|
|
name: '구글 버전 A',
|
|
description: '광고 채널별로 분기되는 기본 버전',
|
|
trafficWeight: 50,
|
|
isActive: true,
|
|
leadCount: 58,
|
|
updatedAt: toVariantIso(-1300)
|
|
},
|
|
{
|
|
id: 'v-lp03-b',
|
|
pageId: 'lp03',
|
|
name: '구글 버전 B',
|
|
description: '강조 문구/이미지를 변경한 실험군',
|
|
trafficWeight: 30,
|
|
isActive: true,
|
|
leadCount: 22,
|
|
updatedAt: toVariantIso(-800)
|
|
},
|
|
{
|
|
id: 'v-lp03-c',
|
|
pageId: 'lp03',
|
|
name: '구글 버전 C',
|
|
description: '폼 위치를 상단으로 이동한 변형',
|
|
trafficWeight: 20,
|
|
isActive: true,
|
|
leadCount: 8,
|
|
updatedAt: toVariantIso(-500)
|
|
}
|
|
],
|
|
lp04: [
|
|
{
|
|
id: 'v-lp04-a',
|
|
pageId: 'lp04',
|
|
name: '저녁 타겟형',
|
|
description: '기본 버전',
|
|
trafficWeight: 100,
|
|
isActive: false,
|
|
leadCount: 22,
|
|
updatedAt: toVariantIso(-700)
|
|
}
|
|
],
|
|
lp05: [
|
|
{
|
|
id: 'v-lp05-a',
|
|
pageId: 'lp05',
|
|
name: '리타겟 기본형',
|
|
description: '리마케팅 유입용 기본 템플릿',
|
|
trafficWeight: 100,
|
|
isActive: true,
|
|
leadCount: 0,
|
|
updatedAt: toVariantIso(-1000)
|
|
}
|
|
]
|
|
}
|
|
|
|
const clampTrafficWeight = (value: number) => {
|
|
if (!Number.isFinite(value)) {
|
|
return 1
|
|
}
|
|
|
|
if (value < 1) {
|
|
return 1
|
|
}
|
|
|
|
if (value > 100) {
|
|
return 100
|
|
}
|
|
|
|
return Math.round(value)
|
|
}
|
|
|
|
export const useVariantsStore = defineStore('admin-variants', {
|
|
state: () => ({
|
|
variantsByPage: variantSeed as VariantMap
|
|
}),
|
|
getters: {
|
|
listByPage: (state) => (pageId: string) => {
|
|
const list = state.variantsByPage[pageId] ?? []
|
|
return [...list].sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())
|
|
},
|
|
countByPage: (state) => (pageId: string) => {
|
|
return state.variantsByPage[pageId]?.length ?? 0
|
|
},
|
|
totalLeadsByPage: (state) => (pageId: string) => {
|
|
return (state.variantsByPage[pageId] ?? []).reduce((sum, item) => sum + item.leadCount, 0)
|
|
}
|
|
},
|
|
actions: {
|
|
createVariant(payload: CreateVariantInput) {
|
|
const normalizedName = payload.name.trim()
|
|
const pageId = payload.pageId.trim()
|
|
const description = payload.description.trim()
|
|
|
|
if (pageId.length === 0 || normalizedName.length === 0) {
|
|
return
|
|
}
|
|
|
|
const pageVariants = this.variantsByPage[pageId] ?? []
|
|
const newVariant: PageVariant = {
|
|
id: `v-${Date.now().toString(36)}`,
|
|
pageId,
|
|
name: normalizedName,
|
|
description,
|
|
trafficWeight: clampTrafficWeight(payload.trafficWeight),
|
|
isActive: true,
|
|
leadCount: 0,
|
|
updatedAt: new Date().toISOString()
|
|
}
|
|
|
|
this.variantsByPage[pageId] = [newVariant, ...pageVariants]
|
|
},
|
|
updateVariant(payload: UpdateVariantInput) {
|
|
const pageId = payload.pageId.trim()
|
|
const variants = this.variantsByPage[pageId]
|
|
|
|
if (!variants) {
|
|
return
|
|
}
|
|
|
|
const idx = variants.findIndex((item) => item.id === payload.id)
|
|
if (idx === -1) {
|
|
return
|
|
}
|
|
|
|
const target = variants[idx]
|
|
variants[idx] = {
|
|
...target,
|
|
...payload,
|
|
updatedAt: new Date().toISOString(),
|
|
pageId
|
|
}
|
|
},
|
|
toggleActive(pageId: string, variantId: string) {
|
|
const variants = this.variantsByPage[pageId]
|
|
|
|
if (!variants) {
|
|
return
|
|
}
|
|
|
|
const idx = variants.findIndex((item) => item.id === variantId)
|
|
if (idx === -1) {
|
|
return
|
|
}
|
|
|
|
variants[idx] = {
|
|
...variants[idx],
|
|
isActive: !variants[idx].isActive,
|
|
updatedAt: new Date().toISOString()
|
|
}
|
|
},
|
|
removeVariant(pageId: string, variantId: string) {
|
|
const variants = this.variantsByPage[pageId]
|
|
|
|
if (!variants) {
|
|
return
|
|
}
|
|
|
|
this.variantsByPage[pageId] = variants.filter((variant) => variant.id !== variantId)
|
|
}
|
|
}
|
|
})
|