feat: 쿠폰등록
This commit is contained in:
855
layers/templates/FxCoupon01/index.vue
Normal file
855
layers/templates/FxCoupon01/index.vue
Normal file
@@ -0,0 +1,855 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
getTime,
|
||||
fromUnixTime,
|
||||
addMonths,
|
||||
differenceInDays,
|
||||
} from '@seed-next/date'
|
||||
import { getComponentGroup } from '#layers/utils/dataUtil'
|
||||
import {
|
||||
COUPON_NO_LENGTH_LIMIT,
|
||||
COUPON_RESULT,
|
||||
} from '#layers/types/api/couponData'
|
||||
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
import type { ReqCouponList } from '#layers/types/api/couponData'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
// Configuration
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const runType = runtimeConfig.public.runType as string
|
||||
const staticUrl = runtimeConfig.public.staticUrl as string
|
||||
const stoveApiUrl = runtimeConfig.public.stoveApiUrl as string
|
||||
const stoveMaintenanceApiUrl = runtimeConfig.public
|
||||
.stoveMaintenanceApiUrl as string
|
||||
const multilingualBaseApiUrl = `${staticUrl}/${runType}/test`
|
||||
const multilingualFileName = 'test_homepage_brand_coupon.json'
|
||||
|
||||
// Multilingual
|
||||
const resultGetMultilingual = await useGetMultilingual({
|
||||
baseApiUrl: multilingualBaseApiUrl,
|
||||
fileName: multilingualFileName,
|
||||
})
|
||||
const { t, tm, locale }: any = useI18n({
|
||||
useScope: 'local',
|
||||
messages: Object(resultGetMultilingual?.value?.multilingual),
|
||||
})
|
||||
|
||||
// Composables
|
||||
const { isGameMaintenance, checkGameMaintenance } = useGetGameMaintenance()
|
||||
const { isWebInspection, getInspectionDataExternal } =
|
||||
useGetInspectionDataExternal()
|
||||
const breakpoints = useResponsiveBreakpoints()
|
||||
const { handleTokenValidation } = useTokenValidation()
|
||||
const { hasGuid, characterList, getGuid, getCharacterList } =
|
||||
useGameLinkedData()
|
||||
const {
|
||||
couponList,
|
||||
hasNoCouponList,
|
||||
totalCount,
|
||||
postCouponUse,
|
||||
getCouponList,
|
||||
} = useCoupon()
|
||||
const {
|
||||
startDate,
|
||||
endDate,
|
||||
currentSearchPeriod,
|
||||
searchStatus,
|
||||
setCouponDate,
|
||||
toUnixTimestamp,
|
||||
} = useCouponDate()
|
||||
const { pageNo, pageSize, pageBlock, updatePagination, getPageBlock } =
|
||||
useCouponPaging()
|
||||
|
||||
// Store
|
||||
const gameDataStore = useGameDataStore()
|
||||
const modalStore = useModalStore()
|
||||
const couponStore = useCouponStore()
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
const { handleOpenAlert, handleOpenConfirm } = modalStore
|
||||
const { couponNo, isSelectCharacter, selectCharacter } =
|
||||
storeToRefs(couponStore)
|
||||
const {
|
||||
updateMemberNo,
|
||||
updateCouponNo,
|
||||
updateSelectCharacter,
|
||||
isEmptyCouponNo,
|
||||
} = couponStore
|
||||
|
||||
// Data
|
||||
const backgroundData = computed(() =>
|
||||
getComponentGroup(props.components, 'background')
|
||||
)
|
||||
|
||||
// Refs
|
||||
const clientIp = ref('')
|
||||
const monthSelectList = ref<Array<number>>([1, 3, 6, 12])
|
||||
const isSelectCharacterModalOpen = ref(false)
|
||||
|
||||
// Computed
|
||||
const minDate = computed(() => {
|
||||
const date = new Date()
|
||||
date.setHours(0, 0, 0, 0)
|
||||
date.setFullYear(date.getFullYear() - 1)
|
||||
return date
|
||||
})
|
||||
const maxDate = computed(() => {
|
||||
const date = new Date()
|
||||
date.setHours(23, 59, 59, 999)
|
||||
return date
|
||||
})
|
||||
const sortedCharacterList = computed(() => {
|
||||
return characterList.value
|
||||
.map(characterInfo => {
|
||||
const worldId =
|
||||
characterInfo.world_id.split('_')[
|
||||
characterInfo.world_id?.split('_').length - 1
|
||||
]
|
||||
const arrWorldIdPriority = ['global', 'asia']
|
||||
const sortedIndex = arrWorldIdPriority.indexOf(worldId)
|
||||
return { ...characterInfo, sortedIndex }
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return a.sortedIndex !== b.sortedIndex ? a.sortedIndex - b.sortedIndex : 0
|
||||
})
|
||||
})
|
||||
|
||||
// Functions
|
||||
/**
|
||||
* @description 기본 Alert 모달 팝업 함수입니다.
|
||||
* @param text - 모달 내용
|
||||
*/
|
||||
const openAlert = (text: string) => {
|
||||
handleOpenAlert({
|
||||
isShowDimmed: true,
|
||||
contentText: text,
|
||||
isOutsideClose: true,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 유저 IP 조회 함수입니다.
|
||||
*/
|
||||
const getClientIp = async () => {
|
||||
if (!clientIp.value) {
|
||||
try {
|
||||
const ipData = await $fetch('/api/clientIp')
|
||||
clientIp.value = ipData || ''
|
||||
} catch (e) {
|
||||
console.error('[Exception] FxCoupon01.getClientIp:', e)
|
||||
clientIp.value = ''
|
||||
}
|
||||
} // 이미 조회했으면 재사용
|
||||
return clientIp.value
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 쿠폰 사용 전 유효성 체크 함수입니다.
|
||||
* @returns {number} 쿠폰 사용 전 유효성 체크 결과
|
||||
*/
|
||||
const validationCheckBefore = async () => {
|
||||
// FIXME: 다른 체크 로직 추가 시 주석 해제 필요 ---------------------------------------------------
|
||||
|
||||
/**
|
||||
* @description 게임 점검 체크하는 로직입니다.
|
||||
* await checkGameMaintenance({
|
||||
* baseApiUrl: stoveMaintenanceApiUrl,
|
||||
* category: 'GAME',
|
||||
* service_id1: gameData.value.game_id,
|
||||
* gameId: gameData.value.game_id,
|
||||
* lang: locale.value,
|
||||
* })
|
||||
* if (isGameMaintenance.value) {
|
||||
* return COUPON_RESULT.GAME_MAINTENANCE
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @description 언어 코드 존재 여부 체크하는 로직입니다.
|
||||
* if (locale.value == null || locale.value === '') {
|
||||
* return COUPON_RESULT.SYSTEM_ERROR
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @description 서버(캐릭터) 선택 여부 체크하는 로직입니다.
|
||||
* if (!isSelectCharacter.value) {
|
||||
* return COUPON_RESULT.SELECT_CHARACTER_REQUIRED
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @description 선택한 캐릭터 정보 유효 여부 (접속 계정과 동일한 캐릭터 여부) 체크하는 로직입니다.
|
||||
* await getCharacterList({
|
||||
* baseApiUrl: stoveApiUrl,
|
||||
* accessToken: csrGetAccessToken(),
|
||||
* game_id: gameData.value.game_id,
|
||||
* gameId: gameData.value.game_id,
|
||||
* })
|
||||
* if (
|
||||
* !characterList.value.some(
|
||||
* character => character.character_id === selectCharacter.value.character_id
|
||||
* )
|
||||
* ) {
|
||||
* return COUPON_RESULT.SELECT_CHARACTER_REQUIRED
|
||||
* }
|
||||
*/
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @description 웹 점검 여부 체크
|
||||
*/
|
||||
await getInspectionDataExternal({
|
||||
baseApiUrl: stoveApiUrl,
|
||||
gameId: gameData.value.game_id,
|
||||
})
|
||||
if (isWebInspection.value) {
|
||||
return COUPON_RESULT.WEB_INSPECTION
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 로그인 여부 체크
|
||||
*/
|
||||
const accessToken = csrGetAccessToken()
|
||||
const validateTokenResult = await handleTokenValidation(accessToken || '')
|
||||
if (validateTokenResult === false) {
|
||||
return COUPON_RESULT.LOGIN_REQUIRED
|
||||
}
|
||||
|
||||
/**
|
||||
* @description GUID(게임 이용 동의) 체크
|
||||
*/
|
||||
await getGuid({
|
||||
baseApiUrl: stoveApiUrl,
|
||||
accessToken: csrGetAccessToken(),
|
||||
game_id: gameData.value.game_id,
|
||||
gameId: gameData.value.game_id,
|
||||
})
|
||||
if (!hasGuid.value) {
|
||||
return COUPON_RESULT.EMPTY_GUID
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 쿠폰번호 입력 여부 체크
|
||||
*/
|
||||
if (isEmptyCouponNo(couponNo.value)) {
|
||||
return COUPON_RESULT.NULL_COUPON_NO
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 쿠폰번호 유효성 체크 (최소 2자, 최대 20자)
|
||||
*/
|
||||
if (
|
||||
!isInRange(
|
||||
couponNo.value.length,
|
||||
COUPON_NO_LENGTH_LIMIT.MIN,
|
||||
COUPON_NO_LENGTH_LIMIT.MAX
|
||||
)
|
||||
) {
|
||||
return COUPON_RESULT.FAIL_COUPON_FORMAT
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 쿠폰 등록 버튼 함수입니다.
|
||||
* @description FE 유효성 체크 후 캐릭터 선택 모달 팝업 노출합니다.
|
||||
*/
|
||||
const handleCouponUse = async () => {
|
||||
const validationCheckBeforeResult = await validationCheckBefore()
|
||||
|
||||
if (validationCheckBeforeResult !== 0) {
|
||||
if (
|
||||
validationCheckBeforeResult === COUPON_RESULT.SELECT_CHARACTER_REQUIRED
|
||||
) {
|
||||
openSelectCharacterModal()
|
||||
return false
|
||||
} else if (validationCheckBeforeResult === COUPON_RESULT.SYSTEM_ERROR) {
|
||||
openAlert(t('Coupon_Error', { code: validationCheckBeforeResult }))
|
||||
return false
|
||||
} else {
|
||||
openAlert(tm(`Coupon_Alert_(${validationCheckBeforeResult})`))
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
openSelectCharacterModal()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 캐릭터 선택 후, 확인 버튼 클릭 시 쿠폰 등록 API 호출 함수입니다.
|
||||
*/
|
||||
const handleCouponRegister = async () => {
|
||||
closeSelectCharacterModal()
|
||||
|
||||
const validationCheckBeforeResult = await validationCheckBefore()
|
||||
if (validationCheckBeforeResult !== 0) {
|
||||
if (
|
||||
validationCheckBeforeResult === COUPON_RESULT.SELECT_CHARACTER_REQUIRED
|
||||
) {
|
||||
openSelectCharacterModal()
|
||||
return false
|
||||
} else if (validationCheckBeforeResult === COUPON_RESULT.SYSTEM_ERROR) {
|
||||
openAlert(t('Coupon_Error', { code: validationCheckBeforeResult }))
|
||||
return false
|
||||
} else {
|
||||
openAlert(tm(`Coupon_Alert_(${validationCheckBeforeResult})`))
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
await getClientIp()
|
||||
const res = await postCouponUse({
|
||||
accessToken: csrGetAccessToken(),
|
||||
user_token_type: 'web',
|
||||
game_code: gameData.value.game_code.toString(),
|
||||
coupon_no: couponNo.value,
|
||||
client_ipaddr: clientIp.value,
|
||||
world_no: selectCharacter.value.world_id,
|
||||
character_no: `${selectCharacter.value.character_id}`,
|
||||
lang_code: locale.value,
|
||||
})
|
||||
if (res.code === 0) {
|
||||
openAlert(tm('Coupon_Alert_Success'))
|
||||
} else {
|
||||
openAlert(tm(`Coupon_Alert_(${res.code})`))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 기간 선택 이벤트 함수입니다.
|
||||
* @param {number} month - 선택한 기간 개월수
|
||||
*/
|
||||
const handlePeriodSelect = async (month: number) => {
|
||||
const today = new Date()
|
||||
const startTimestamp = toUnixTimestamp(today, 'start')
|
||||
const endTimestamp = toUnixTimestamp(today, 'end')
|
||||
|
||||
const newStartDate = addMonths(fromUnixTime(startTimestamp), -month)
|
||||
const newEndDate = fromUnixTime(endTimestamp)
|
||||
|
||||
setCouponDate(newStartDate, 'start')
|
||||
setCouponDate(newEndDate, 'end')
|
||||
|
||||
handlePaging(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 조회하기 이벤트 함수입니다.
|
||||
*/
|
||||
const handlePeriodSearch = async () => {
|
||||
if (!startDate.value || !endDate.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const accessToken = csrGetAccessToken()
|
||||
const validateTokenResult = await handleTokenValidation(accessToken || '')
|
||||
if (validateTokenResult === false) {
|
||||
return
|
||||
}
|
||||
|
||||
const result = differenceInDays(endDate.value, startDate.value)
|
||||
if (result > 365) {
|
||||
openAlert(tm('Coupon_Msg_OverDays'))
|
||||
return
|
||||
}
|
||||
|
||||
const req: ReqCouponList = {
|
||||
accessToken: accessToken,
|
||||
user_token_type: 'web',
|
||||
game_code: gameData.value.game_code.toString(),
|
||||
start_date: getTime(startDate.value),
|
||||
end_date: getTime(endDate.value),
|
||||
use_state_code: searchStatus.value,
|
||||
page_size: pageSize.value,
|
||||
page_no: pageNo.value,
|
||||
lang_code: locale.value,
|
||||
}
|
||||
|
||||
await getCouponList(req)
|
||||
}
|
||||
|
||||
/**
|
||||
* @desciprion 페이지 변경 이벤트 함수입니다.
|
||||
* @param {number} newPageNo
|
||||
*/
|
||||
const handlePaging = async (newPageNo: number) => {
|
||||
updatePagination(newPageNo)
|
||||
await handlePeriodSearch()
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 게임별 스토브 쿠폰함 URL 조회 함수입니다.
|
||||
* @returns {string} 게임별 스토브 쿠폰함 URL
|
||||
*/
|
||||
const getCouponBoxUrl = () => {
|
||||
let url = ''
|
||||
|
||||
if (breakpoints.value.isMobile) {
|
||||
url = getStoveCouponUrl('mobile')
|
||||
} else {
|
||||
url = getStoveCouponUrl('desktop')
|
||||
}
|
||||
|
||||
return `${url}?game_id=${gameData.value.game_id}`
|
||||
}
|
||||
|
||||
/**
|
||||
* @description @coupon{문구} 형식의 문구를 게임별 스토브 쿠폰함 URL의 a태그로 변환합니다.
|
||||
* @param {string} text - 변환할 문구
|
||||
* @returns {string} 변환된 문구 HTML
|
||||
* @description string으로 return하므로 v-dompurify-html 사용 필요
|
||||
*/
|
||||
const getKeyToCouponUrl = (text: string) => {
|
||||
const couponBoxUrl = getCouponBoxUrl()
|
||||
|
||||
let result = ''
|
||||
|
||||
if (text.includes('@coupon')) {
|
||||
result = text.replace(/@coupon\{(.*?)\}/g, (_, key) => {
|
||||
return `<a href="${couponBoxUrl}" rel="noopener noreferrer" target="_blank" class="text-[#3C75FF] underline decoration-solid decoration-auto underline-offset-2 after:content-[''] after:absolute after:top-1/2 after:left-0 after:translate-y-[-50%] after:w-full after:h-fuill after:bg-white after:opacity-0 transition-opacity duration-300 ease-in-out hover:after:opacity-20">${key}</a>`
|
||||
})
|
||||
|
||||
return result
|
||||
} else {
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description STOVE 쿠폰함으로 이동합니다.
|
||||
*/
|
||||
const goToCouponBox = () => {
|
||||
let url = ''
|
||||
|
||||
if (breakpoints.value.isMobile) {
|
||||
url = getStoveCouponUrl('mobile')
|
||||
} else {
|
||||
url = getStoveCouponUrl('desktop')
|
||||
}
|
||||
|
||||
csrGoExternalLink(`${url}?game_id=${gameData.value.game_id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 쿠폰 등록 내역의 사용하기 버튼의 클릭 이벤트 함수입니다.
|
||||
*/
|
||||
const handleGoToCouponBox = async () => {
|
||||
const accessToken = csrGetAccessToken()
|
||||
const validateTokenResult = await handleTokenValidation(accessToken || '')
|
||||
|
||||
// 미로그인 상태일 경우 Return
|
||||
if (validateTokenResult === false) {
|
||||
return
|
||||
}
|
||||
|
||||
// 로그인 상태일 경우 모달 표시
|
||||
handleOpenConfirm({
|
||||
contentText: tm('Coupon_Alert_StoveCouponBox'),
|
||||
confirmButtonText: tm('Coupon_StoveCouponBox'),
|
||||
modalName: 'modal-coupon-use',
|
||||
confirmButtonEvent: () => {
|
||||
goToCouponBox()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 쿠폰 등록 관련 데이터를 초기화합니다.
|
||||
*/
|
||||
const initCouponData = () => {
|
||||
updateSelectCharacter(null)
|
||||
updateCouponNo('')
|
||||
updatePagination(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 캐릭터 선택 모달 팝업 열기
|
||||
*/
|
||||
const openSelectCharacterModal = () => {
|
||||
isSelectCharacterModalOpen.value = true
|
||||
}
|
||||
/**
|
||||
* @description 캐릭터 선택 모달 팝업 닫기
|
||||
*/
|
||||
const closeSelectCharacterModal = () => {
|
||||
isSelectCharacterModalOpen.value = false
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
initCouponData()
|
||||
|
||||
await handlePeriodSelect(1)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WidgetsFixMainTitle
|
||||
:title="tm('Coupon_Page_Title')"
|
||||
:resourcesData="backgroundData"
|
||||
/>
|
||||
|
||||
<div class="section-container static">
|
||||
<section class="section-static">
|
||||
<WidgetsFixSubTitle :title="tm('Coupon_Section_Registration_Title')" />
|
||||
|
||||
<div
|
||||
class="relative flex flex-col items-center jutify-start w-full bg-white p-[20px] mt-[16px] rounded-[12px] sm:rounded-[16px] md:p-[32px] md:mt-[24px]"
|
||||
>
|
||||
<div
|
||||
class="relative flex flex-col items-center justify-start gap-[16px] w-full max-w-[880px] md:gap-[20px]"
|
||||
>
|
||||
<div
|
||||
novalidate
|
||||
class="relative flex flex-col items-center justify-start gap-[12px] w-full sm:flex-row"
|
||||
>
|
||||
<AtomsInput
|
||||
:model-value="couponNo"
|
||||
:name="tm('Coupon_Enter_Number')"
|
||||
:placeholder="tm('Coupon_Enter_Number_Please')"
|
||||
maxlength="20"
|
||||
@update:model-value="updateCouponNo"
|
||||
/>
|
||||
<AtomsButton
|
||||
type="action"
|
||||
button-size="size-small"
|
||||
background-color="#383838"
|
||||
text-color="#FFFFFF"
|
||||
class="relative flex items-center justify-center w-full gap-[4px] px-0 sm:w-[143px] sm:shrink-0 md:h-[56px] md:text-[16px] md:leading-[24px] md:tracking-[-0.48px]"
|
||||
@click="handleCouponUse"
|
||||
>
|
||||
<span>{{ tm('Coupon_Registration') }}</span>
|
||||
<AtomsIconsCouponFill
|
||||
:size="breakpoints.isMobile ? 16 : 20"
|
||||
color="#FFFFFF"
|
||||
/>
|
||||
</AtomsButton>
|
||||
</div>
|
||||
|
||||
<ul class="relative flex flex-col items-start justify-start w-full">
|
||||
<template v-for="notice in tm('Coupon_Notice_List')" :key="notice">
|
||||
<li
|
||||
class="relative flex items-start justify-start w-full pl-[20px] before:content-[''] before:absolute before:top-[10px] before:left-[9px] before:w-[3px] before:h-[3px] before:rounded-full before:bg-[#666666] text-left text-[#666666] text-[13px] font-[400] leading-[22px] tracking-[-0.325px] md:text-[14px] md:leading-[24px] md:tracking-[-0.42px] md:before:left-[8px] md:before:w-[4px] md:before:h-[4px]"
|
||||
>
|
||||
<span
|
||||
v-dompurify-html="
|
||||
getKeyToCouponUrl(tm(notice as string) as string)
|
||||
"
|
||||
></span>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section-static">
|
||||
<WidgetsFixSubTitle :title="tm('Coupon_Section_History_Title')">
|
||||
<div
|
||||
class="relative flex flex-col items-start justify-start gap-[12px] w-full mt-[16px] md:gap-[16px] md:mt-[24px] lg:flex-row lg:items-end lg:justify-between lg:gap-[0]"
|
||||
>
|
||||
<div
|
||||
class="relative flex items-center justify-start gap-[20px] w-full lg:w-auto lg:mr-auto"
|
||||
>
|
||||
<div
|
||||
v-if="breakpoints.isMd || breakpoints.isDesktop"
|
||||
class="relative flex items-center justify-start"
|
||||
>
|
||||
<template v-for="month in monthSelectList" :key="month">
|
||||
<AtomsButton
|
||||
type="action"
|
||||
button-size="size-small"
|
||||
background-color="#FAFAFA"
|
||||
text-color="#CCCCCC"
|
||||
:class="[
|
||||
'btn-period',
|
||||
{ 'btn-period-active': currentSearchPeriod === month },
|
||||
]"
|
||||
@click="handlePeriodSelect(month)"
|
||||
>
|
||||
<span>{{ tm(`Coupon_Month${month}`) }}</span>
|
||||
</AtomsButton>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="relative flex items-center justify-center gap-[8px] w-full md:w-auto"
|
||||
>
|
||||
<div
|
||||
class="relative flex items-center justify-center gap-[4px] md:gap-[8px] w-full md:w-auto"
|
||||
>
|
||||
<BlocksDatePicker
|
||||
:key="getTime(startDate)"
|
||||
:date="startDate"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
@update:date="
|
||||
(date: string) => setCouponDate(new Date(date), 'start')
|
||||
"
|
||||
/>
|
||||
<span
|
||||
class="relative inline-flex items-center justify-center shrink-0 text-[#868078] text-[16px] font-[500] leading-[24px] tracking-[-0.48px] md:text-[18px] md:leading-[26px] md:tracking-[-0.54px]"
|
||||
>
|
||||
~
|
||||
</span>
|
||||
<BlocksDatePicker
|
||||
:key="getTime(endDate)"
|
||||
:date="endDate"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
@update:date="
|
||||
(date: string) => setCouponDate(new Date(date), 'end')
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<AtomsButton
|
||||
type="action"
|
||||
button-size="size-small"
|
||||
background-color="#383838"
|
||||
text-color="#FFFFFF"
|
||||
class="shrink-0 w-[40px] h-[40px] p-0 md:w-auto md:px-[22px]"
|
||||
@click="handlePeriodSearch"
|
||||
>
|
||||
<span
|
||||
class="relative flex items-center justify-center gap-[2px]"
|
||||
>
|
||||
<span v-if="breakpoints.isMd || breakpoints.isDesktop">
|
||||
{{ tm('Coupon_Search') }}
|
||||
</span>
|
||||
<AtomsIconsSearchLine :size="16" color="#FFFFFF" />
|
||||
</span>
|
||||
</AtomsButton>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
class="text-[#666666] text-[13px] font-[400] leading-[22px] tracking-[-0.325px] md:text-[14px] md:leading-[24px] md:tracking-[-0.421px]"
|
||||
>
|
||||
{{ tm('Coupon_Section_History_Description') }}
|
||||
</p>
|
||||
</div>
|
||||
</WidgetsFixSubTitle>
|
||||
|
||||
<div
|
||||
class="relative w-full border border-solid border-[#D9D9D9] rounded-[12px] overflow-hidden sm:rounded-[16px]"
|
||||
>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="!border-l-[0]">
|
||||
{{ tm('Coupon_Item_Name') }}
|
||||
</th>
|
||||
<th
|
||||
v-if="breakpoints.isMd || breakpoints.isDesktop"
|
||||
class="w-[260px]"
|
||||
>
|
||||
{{ tm('Coupon_Item_RegistDate') }}
|
||||
</th>
|
||||
<th class="w-[90px] md:w-[180px]">
|
||||
{{ tm('Coupon_Item_Status') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-if="hasNoCouponList">
|
||||
<tr>
|
||||
<td
|
||||
:colspan="
|
||||
breakpoints.isMd || breakpoints.isDesktop ? '3' : '2'
|
||||
"
|
||||
class="!border-l-[0] !border-b-[0]"
|
||||
>
|
||||
<p>{{ tm('Coupon_Has_Not_Item') }}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template
|
||||
v-for="(coupon, couponIdx) in couponList"
|
||||
:key="coupon.coupon_box_id"
|
||||
>
|
||||
<tr>
|
||||
<td
|
||||
:class="
|
||||
couponIdx === couponList.length - 1
|
||||
? '!border-l-[0] !border-b-[0]'
|
||||
: '!border-l-[0]'
|
||||
"
|
||||
>
|
||||
<p>{{ coupon.coupon_name }}</p>
|
||||
</td>
|
||||
<td
|
||||
v-if="breakpoints.isMd || breakpoints.isDesktop"
|
||||
:class="
|
||||
couponIdx === couponList.length - 1 ? '!border-b-[0]' : ''
|
||||
"
|
||||
>
|
||||
<p>{{ coupon.register_date }}</p>
|
||||
</td>
|
||||
<td
|
||||
:class="
|
||||
couponIdx === couponList.length - 1 ? '!border-b-[0]' : ''
|
||||
"
|
||||
>
|
||||
<template
|
||||
v-if="
|
||||
coupon.use_state === 1 &&
|
||||
coupon.reward_type_code.includes(2)
|
||||
"
|
||||
>
|
||||
<AtomsButton
|
||||
type="action"
|
||||
button-size="size-small"
|
||||
background-color="transparent"
|
||||
text-color="transparent"
|
||||
class="coupon-item coupon-item-use"
|
||||
@click="handleGoToCouponBox"
|
||||
>
|
||||
<span>{{ tm('Coupon_Item_Use') }}</span>
|
||||
</AtomsButton>
|
||||
</template>
|
||||
<template v-else-if="coupon.use_state === 1">
|
||||
<span class="coupon-item coupon-item-not-used">
|
||||
{{ tm('Coupon_Item_Not_Used') }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="coupon.use_state === 2">
|
||||
<span class="coupon-item coupon-item-used">
|
||||
{{ tm('Coupon_Item_Used') }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="coupon.use_state === 4">
|
||||
<span class="coupon-item coupon-item-expires">
|
||||
{{ tm('Coupon_Item_Expires') }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="coupon.use_state === 6">
|
||||
<span class="coupon-item coupon-item-deadline">
|
||||
{{ tm('Coupon_Item_Deadline') }}
|
||||
</span>
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<BlocksPagination
|
||||
:key="pageNo"
|
||||
:current-page="pageNo"
|
||||
:page-size="pageSize"
|
||||
:page-block="getPageBlock(breakpoints.isMobile ? 'mobile' : 'desktop')"
|
||||
:total-count="totalCount"
|
||||
:use-min-max="true"
|
||||
@update:page="handlePaging"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<ClientOnly>
|
||||
<Teleport to="body">
|
||||
<BlocksModalLayer
|
||||
:is-show-dimmed="true"
|
||||
:is-outside-close="true"
|
||||
modal-name="modal-coupon-character-select"
|
||||
:is-open="isSelectCharacterModalOpen"
|
||||
area-class="max-w-[480px] p-6 bg-white rounded-[20px]"
|
||||
close-class="hidden"
|
||||
>
|
||||
<div
|
||||
class="relative flex flex-col items-center justify-center gap-[24px] w-full"
|
||||
>
|
||||
<p
|
||||
class="relative flex items-start justify-center w-full text-center text-[#333333] text-[15px] font-[400] leading-[24px] tracking-[-0.45px]"
|
||||
v-dompurify-html="tm('Coupon_Alert_SelectCharacter')"
|
||||
></p>
|
||||
|
||||
<AtomsSelect
|
||||
:options="sortedCharacterList"
|
||||
label-name="formatted_nm"
|
||||
:placeholder="tm('Coupon_Alert_EnterCharacter')"
|
||||
@update:model-value="updateSelectCharacter"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="relative flex items-center justify-center gap-[8px] w-full"
|
||||
>
|
||||
<AtomsButtonVariant
|
||||
variant="outlined"
|
||||
class="max-w-[128px]"
|
||||
@click="closeSelectCharacterModal"
|
||||
>
|
||||
{{ tm('Text_Cancel') }}
|
||||
</AtomsButtonVariant>
|
||||
<AtomsButtonVariant
|
||||
:disabled="isSelectCharacter ? false : true"
|
||||
class="max-w-[128px]"
|
||||
@click="handleCouponRegister"
|
||||
>
|
||||
{{ tm('Text_Confirm') }}
|
||||
</AtomsButtonVariant>
|
||||
</div>
|
||||
</div>
|
||||
</BlocksModalLayer>
|
||||
</Teleport>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Button Style */
|
||||
.btn-period {
|
||||
@apply border border-solid border-[#D9D9D9] rounded-[0] w-auto h-[40px] px-[16px] before:content-none after:content-none
|
||||
transition-all duration-300 ease-in-out
|
||||
hover:z-[4] hover:!bg-[#FFFFFF] hover:border-[#404040] hover:!text-[#1F1F1F];
|
||||
}
|
||||
.btn-period + .btn-period {
|
||||
@apply ml-[-1px];
|
||||
}
|
||||
.btn-period:first-child {
|
||||
@apply rounded-l-[8px];
|
||||
}
|
||||
.btn-period:last-child {
|
||||
@apply rounded-r-[8px];
|
||||
}
|
||||
.btn-period.btn-period-active {
|
||||
@apply z-[4] !bg-[#FFFFFF] !border-[#404040] !text-[#1F1F1F];
|
||||
}
|
||||
|
||||
/* Table Style */
|
||||
table {
|
||||
@apply w-full h-auto border-collapse border-spacing-0 table-fixed;
|
||||
}
|
||||
table th {
|
||||
@apply py-[8px] px-[12px] border border-[#D9D9D9] border-t-[0] border-r-[0] bg-[#FAFAFA] text-[#1F1F1F] text-[14px] font-bold leading-[24px] tracking-[-0.42px]
|
||||
md:py-[11px] md:px-[20px] md:text-[16px] md:leading-[26px] md:tracking-[-0.48px];
|
||||
}
|
||||
table td {
|
||||
@apply h-[auto] p-[12px] border border-[#D9D9D9] border-t-[0] border-r-[0] bg-[#FFFFFF] text-center text-[#666666] text-[14px] font-[400] leading-[24px] tracking-[-0.42px]
|
||||
md:px-[20px] md:text-[16px] md:leading-[26px] md:tracking-[-0.48px];
|
||||
}
|
||||
table td p {
|
||||
@apply line-clamp-2 text-center;
|
||||
}
|
||||
/* Coupon Item Style */
|
||||
.coupon-item {
|
||||
@apply relative inline-flex items-center justify-center !w-auto !h-auto py-[2px] px-[6px] border-none rounded-full !bg-[#EBEBEB] text-center !text-[#999999] text-[12px] font-[500] leading-[18px] tracking-[-0.24px]
|
||||
md:py-[4px] md:px-[8px] md:text-[14px] md:leading-[20px] md:tracking-[-0.42px];
|
||||
}
|
||||
.coupon-item.coupon-item-use {
|
||||
@apply !bg-[#383838] !text-white;
|
||||
}
|
||||
.coupon-item.coupon-item-used {
|
||||
@apply !bg-[#E2EAFF] !text-[#3C75FF];
|
||||
}
|
||||
.coupon-item.coupon-item-not-used {
|
||||
@apply !bg-[#EEF6F1] !text-[#2B9450];
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user