| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700 |
- <template>
- <div class="roulette--container--wrappers">
- <div class="roulette--wrapper">
- <div class="title">
- <div>미리 포인트로 즐기는 행운의 찬스!</div>
- <div class="main-title"><span>룰렛</span><span>을 돌려라!</span></div>
- </div>
- <div class="roulette-container-wrap">
- <div class="roulette-container">
- <div class="pointer"></div>
- <div ref="wheelRef" class="wheel"></div>
- <div ref="textLayerRef" class="text-layer"></div>
- <button
- id="agreeButton"
- type="button"
- class="center-button"
- @click="activeLayer(1)"
- ></button>
- <!-- <button
- id="spinButton"
- type="button"
- class="center-button"
- @click="trySpinRoulette"
- ></button> -->
- </div>
- </div>
- <div class="bottom-text">100% 당첨! 룰렛 돌리기</div>
- <div class="sub-text">미리톡에서 추후 시 500P 사용하면 보너 2개!</div>
- <div class="buttons">
- <button type="button" class="button secondary">응모권 확인하기</button>
- <button type="button" class="button primary">나의 포인트 전환</button>
- </div>
- <div class="probability-display">
- <div>당첨 확률: <span id="probabilityDisplay">0.2</span>%</div>
- </div>
- </div>
- <!-- 레이어 입력 폼 -->
- <div
- id="randomBoxAuth-layer01"
- class="layer-popup bottom-sheet-wrap event"
- role="dialog"
- aria-hidden="false"
- tabindex="0"
- style="z-index: 101"
- :class="{ show: activeLayer1 }"
- >
- <div class="layer-popup-item evt-reservation jan-roulette-pop">
- <div class="popup-header">
- <strong class="txt-main">룰렛 이벤트 신청</strong>
- </div>
- <div class="popup-body">
- <div class="page-desc">
- <h2>
- 이벤트 신청을 위해서 본인 인증이 필요합니다.<br />본인 인증을 진행해 주세요.
- </h2>
- <!-- <p>이벤트 기간 내 가입한 010 신규가입자도 룰렛 이벤트에 참여 가능합니다.</p>-->
- </div>
- <!-- <div class="box-btn">-->
- <!-- <button class="btns md-ripples ripples-light gtm-tracking" type="button" data-gtm-tracking-category="랜덤박스 팝업_PC" data-gtm-tracking-action="본인인증 시도_PC" data-gtm-tracking-label="인증하기 버튼" id="randomBoxAuthBtn">본인 인증 하기</button>-->
- <!-- <button id="randomBoxChkBtn" class="btns md-ripples ripples-light" type="button" disabled="" style="display: none;">인증 완료</button>-->
- <!-- </div>-->
- <div class="phone-certification mt45">
- <h2>신청자 정보</h2>
- <div class="box-input mt--45">
- <label for="userName" class="input-label">이름</label>
- <div class="input-wrap">
- <input
- id="userName"
- type="text"
- placeholder="이름을 입력해주세요."
- class="input-default is-delete"
- />
- </div>
- </div>
- <div class="box-input mt45">
- <label for="userPhone" class="input-label">휴대폰 번호</label>
- <div class="input-wrap">
- <input
- id="userPhone"
- type="text"
- placeholder="휴대폰 번호를 입력해주세요."
- class="input-default is-delete"
- />
- </div>
- <input id="randomBoxInput3" type="hidden" disabled="" value="" />
- </div>
- <div class="rq-form">
- <div class="agree-wrap">
- <!-- 전체 동의 -->
- <div class="btn-box btn-check btn-text-line">
- <input
- type="checkbox"
- id="agreeAll"
- v-model="agreeAll"
- @change="onAgreeAllChange"
- />
- <label for="agreeAll">
- <span class="ico-check"></span>전체 동의 (필수)
- </label>
- </div>
- <div class="agree-group">
- <div class="btn-box btn-check">
- <input
- type="checkbox"
- class="agreeReq"
- id="randomBoxAgree1"
- v-model="agree1"
- @change="onAgreeChange"
- />
- <label for="randomBoxAgree1">
- <span class="ico-check"></span>이벤트 참여 및 전화 상담을 위한
- 개인정보 수집 및 이용 동의 (필수)
- </label>
- <a @click="activeLayer(2)" class="ico-arrow-right agreeActions"
- >더보기</a
- >
- </div>
- <div class="btn-box btn-check">
- <input
- type="checkbox"
- class="agreeReq"
- id="randomBoxAgree2"
- v-model="agree2"
- @change="onAgreeChange"
- />
- <label for="randomBoxAgree2">
- <span class="ico-check"></span>고객 혜택 정보 및 광고 수신 동의
- (필수)
- </label>
- <a @click="activeLayer(2)" class="ico-arrow-right agreeActions"
- >더보기</a
- >
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="popup-footer">
- <div class="btn-group">
- <button
- class="btns w-sm lightgray md-ripples ripples-dark popup-close"
- type="button"
- >
- 취소
- </button>
- <button
- id="randomBoxAplyBtn"
- class="btns w-sm md-ripples ripples-light gtm-tracking"
- data-gtm-tracking-category="랜덤박스 팝업_PC"
- data-gtm-tracking-action="랜덤박스 열기_PC"
- data-gtm-tracking-label="박스열기 버튼"
- type="button"
- @click="trySpinRoulette(useSeq)"
- >
- 룰렛 돌리기
- </button>
- </div>
- <button
- @click="activeLayer1 = !activeLayer1"
- type="button"
- id="layer-close01"
- class="btn-close-x popup-close"
- >
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="37.657"
- height="37.657"
- viewBox="0 0 37.657 37.657"
- >
- <path
- data-name="선 392"
- transform="translate(2.828 2.828)"
- style="fill: none; stroke: #000; stroke-linecap: round; stroke-width: 4px"
- d="m0 0 32 32"
- />
- <path
- data-name="선 393"
- transform="translate(2.828 2.828)"
- style="fill: none; stroke: #000; stroke-linecap: round; stroke-width: 4px"
- d="M32 0 0 32"
- />
- </svg>
- </button>
- </div>
- </div>
- </div>
- <div
- id="randomBoxAgree-layer02"
- class="layer-popup bottom-sheet-wrap terms"
- role="dialog"
- aria-hidden="false"
- tabindex="0"
- style="z-index: 101"
- :class="{ show: activeLayer2 }"
- >
- <div class="layer-popup-item">
- <div class="popup-header">
- <strong class="txt-main">약관 및 동의 내용 보기</strong>
- </div>
- <div class="popup-body">
- <div class="agree-cont">
- <div class="agree-box">
- <p>
- 귀사가 고객 혜택정보 및 광고 수신동의(필수) 항목에서 수집한 개인정보,
- 고객세분화정보,<br />
- 선호도 및 라이프스타일 정보, 전산조회이력정보 및 상담이력정보, 고객 간
- 관계에 관한 예측 정보 및 이 정보들에 대한 통계·분석데이터를 해지 시까지
- 수집·이용·분석하여 각종 서비스<br />
- ·상품(주)미디어로그가 제공하는 이동통신, 금융서비스, 결합·제휴상품,
- 스토리지 등<br />
- 데이터·콘텐츠서비스, 부가서비스, 전자상거래서비스, 위치정보서비스,
- it솔루션, Smart health서비스, 신규서비스·상품 포함), 제휴사와 결합된
- 서비스 및 제휴사의 서비스에 대하여 홍보, 가입권유, 프로모션, 생활정보,
- 멤버십정보, 이벤트, 해외로밍 안내(공항 또는 항만 위치 시 로밍 이용방법,
- 진행중인 이벤트 등 안내) 및 설문조사 목적으로 수집·이용·활용하는 것,
- 본인에게 혜택정보, 광고정보를 각종 통신방식[전화, SMS, LMS, MMS, WAP
- Push,이메일, 우편, APP안내 및 팝업, APP PUSH]으로 전송하는 것에
- 동의합니다.
- </p>
- </div>
- </div>
- </div>
- <div class="popup-footer">
- <!-- <div class="btn-group">
- <button
- class="btns w-sm lightgray md-ripples ripples-dark popup-close"
- type="button"
- >
- 취소
- </button>
- <button class="btns w-sm md-ripples ripples-light popup-close" type="button">
- 확인
- </button>
- </div> -->
- <button
- @click="activeLayer2 = !activeLayer2"
- class="btn-close-x popup-close"
- id="layer-close02"
- type="button"
- >
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="37.657"
- height="37.657"
- viewBox="0 0 37.657 37.657"
- >
- <path
- data-name="선 392"
- transform="translate(2.828 2.828)"
- style="fill: none; stroke: #000; stroke-linecap: round; stroke-width: 4px"
- d="m0 0 32 32"
- />
- <path
- data-name="선 393"
- transform="translate(2.828 2.828)"
- style="fill: none; stroke: #000; stroke-linecap: round; stroke-width: 4px"
- d="M32 0 0 32"
- />
- </svg>
- </button>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script setup>
- import { ref, onMounted, nextTick } from "vue";
- import { useRoute } from "vue-router";
- definePageMeta({
- layout: "roulette",
- });
- /************************************************************************
- | 전역
- ************************************************************************/
- const pageId = ref("ROULETTE");
- const { $eventBus } = useNuxtApp();
- const winProbability = ref(100.0);
- const savedName = ref("");
- const savedPhone = ref("");
- const isSpinning = ref(false);
- const activeLayer1 = ref(false);
- const activeLayer2 = ref(false);
- const wheelRef = ref(null);
- const textLayerRef = ref(null);
- const agreeAll = ref(false);
- const agree1 = ref(false);
- const agree2 = ref(false);
- const route = useRoute();
- const useSeq = ref(route.query.seq || route.params.seq || ""); // 쿼리 또는 params에서 seq 추출
- const compId = ref(route.query.id || route.params.id || ""); // 쿼리 또는 params에서 id 추출
- /************************************************************************
- | 스토어
- ************************************************************************/
- const useDtStore = useDetailStore();
- /************************************************************************
- | 함수
- ************************************************************************/
- const setWinProbability = (probability) => {
- winProbability.value = Math.max(0, Math.min(100, parseFloat(probability)));
- return winProbability.value;
- };
- const activeLayer = (idx) => {
- if (idx === 1) {
- activeLayer1.value = !activeLayer1.value;
- } else if (idx === 2) {
- activeLayer2.value = !activeLayer2.value;
- }
- };
- const createRoulette = (numSections, itemName) => {
- nextTick(() => {
- const $wheel = wheelRef.value;
- const $textLayer = textLayerRef.value;
- if (!$wheel || !$textLayer) return;
- // 초기화
- $wheel.style.background = "";
- $textLayer.innerHTML = "";
- const items = ["당첨", "꽝", "꽝", "꽝", "꽝", "꽝", "꽝", "꽝"];
- const sectionCount = numSections;
- const sectionAngle = 360 / sectionCount;
- const colors = ["#f5c4c3", "#fdf195", "#F0FFF0"];
- // conic-gradient
- let conicGradient = "conic-gradient(";
- for (let i = 0; i < sectionCount; i++) {
- const startAngle = i * sectionAngle;
- const endAngle = (i + 1) * sectionAngle;
- const color = colors[i % 3];
- conicGradient += `${color} ${startAngle}deg ${endAngle}deg`;
- if (i < sectionCount - 1) conicGradient += ", ";
- }
- conicGradient += ")";
- $wheel.style.background = conicGradient;
- // 룰렛 크기 계산
- const wheelRect = $wheel.getBoundingClientRect();
- const radius = wheelRect.width / 2;
- for (let i = 0; i < sectionCount; i++) {
- const textDiv = document.createElement("div");
- textDiv.className = "section-text";
- textDiv.innerText = items[i % items.length];
- const angle = i * sectionAngle + sectionAngle / 2;
- const distance = radius * 0.75;
- const radians = angle * (Math.PI / 180);
- const centerX = radius;
- const centerY = radius;
- const x = centerX + Math.sin(radians) * distance;
- const y = centerY - Math.cos(radians) * distance;
- textDiv.style.position = "absolute";
- textDiv.style.left = `${x}px`;
- textDiv.style.top = `${y}px`;
- textDiv.style.transform = `rotate(${angle}deg)`;
- $textLayer.appendChild(textDiv);
- }
- // 당첨 섹션 강조
- const winSectionIndex = 4;
- const $winText = $textLayer.children[winSectionIndex];
- if ($winText) {
- $winText.style.color = "#E91E63";
- $winText.style.fontWeight = "bolder";
- }
- });
- };
- const handleWin = (name, phone, EVT_SEQ) => {
- //참여자 정보
- let __req = {
- name: name,
- phone: phone,
- seq: EVT_SEQ,
- ignore_auth: 1,
- };
- useAxios()
- .post("/winner/reg", __req)
- .then((res) => {
- if (res.data.rank > 0) {
- let param = {
- id: pageId,
- title: "시스템 메시지",
- content: `축하합니다.<br/>${res.data.rank}등에 당첨되었습니다!`,
- yes: {
- text: "확인",
- isProc: false,
- },
- no: {
- text: "취소",
- isProc: false,
- },
- reload: true,
- };
- $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
- } else {
- let param = {
- id: pageId,
- title: "시스템 메시지",
- content: `당첨에 실패하였습니다.<br/>다음 기회에 도전해보세요!`,
- yes: {
- text: "확인",
- isProc: false,
- },
- no: {
- text: "취소",
- isProc: false,
- },
- reload: true,
- };
- $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
- }
- })
- .catch((error) => {})
- .finally(() => {});
- };
- const spinRoulette = (name, phone, EVT_SEQ) => {
- if (isSpinning.value) return;
- isSpinning.value = true;
- const spinDuration = 5000;
- const spinRounds = 5; // 룰렛 회전 횟수
- const sectionCount = 8; // 룰렛 섹션 개수
- const sectionAngle = 360 / sectionCount;
- const winSectionIndex = 0; //당첨 섹션 인덱스 : crateRoulette()에서 설정한 인덱스와 동일해야 함
- const winSectionStart = winSectionIndex * sectionAngle; // 당첨 섹션 시작 각도
- const winSectionEnd = (winSectionIndex + 1) * sectionAngle; // 당첨 섹션 끝 각도
- const isWin = Math.random() * 100 < winProbability.value;
- let stopAngle;
- if (isWin) {
- stopAngle = winSectionStart + Math.random() * (winSectionEnd - winSectionStart);
- } else {
- const loseSections = [];
- for (let i = 0; i < sectionCount; i++) {
- if (i !== winSectionIndex) loseSections.push(i);
- }
- const loseIndex = loseSections[Math.floor(Math.random() * loseSections.length)];
- const loseStart = loseIndex * sectionAngle;
- const loseEnd = (loseIndex + 1) * sectionAngle;
- stopAngle = loseStart + Math.random() * (loseEnd - loseStart);
- }
- const rotationAmount = spinRounds * 360 + (360 - stopAngle);
- nextTick(() => {
- const $wheel = wheelRef.value;
- const $textLayer = textLayerRef.value;
- if (!$wheel || !$textLayer) return;
- $wheel.style.transition = "none";
- $wheel.style.transform = "rotate(0deg)";
- $textLayer.style.transition = "none";
- $textLayer.style.transform = "rotate(0deg)";
- void $wheel.offsetWidth;
- $wheel.style.transition = `transform ${spinDuration}ms cubic-bezier(0.2, 0.8, 0.3, 0.9)`;
- $wheel.style.transform = `rotate(${rotationAmount}deg)`;
- $textLayer.style.transition = `transform ${spinDuration}ms cubic-bezier(0.2, 0.8, 0.3, 0.9)`;
- $textLayer.style.transform = `rotate(${rotationAmount}deg)`;
- setTimeout(() => {
- isSpinning.value = false;
- const finalAngle = (360 - (rotationAmount % 360)) % 360;
- const sectionIndex = Math.floor(finalAngle / sectionAngle);
- //console.log(sectionIndex, winSectionIndex);
- if (sectionIndex === winSectionIndex) {
- handleWin(name, phone, EVT_SEQ);
- } else {
- handleWin(name, phone, EVT_SEQ);
- }
- }, spinDuration);
- });
- };
- const trySpinRoulette = async (EVT_SEQ) => {
- const name = document.getElementById("userName").value.trim();
- const phone = document.getElementById("userPhone").value.trim();
- if (!name) {
- let param = {
- id: pageId,
- title: "시스템 메시지",
- content: "이름을 입력하세요.",
- yes: {
- text: "확인",
- isProc: false,
- },
- no: {
- text: "취소",
- isProc: false,
- },
- };
- $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
- document.getElementById("userName").focus();
- return;
- }
- if (!phone) {
- let param = {
- id: pageId,
- title: "시스템 메시지",
- content: "휴대폰 번호를 입력하세요.",
- yes: {
- text: "확인",
- isProc: false,
- },
- no: {
- text: "취소",
- isProc: false,
- },
- };
- $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
- document.getElementById("userPhone").focus();
- return;
- }
- const phoneRegex = /^010-\d{4}-\d{4}$/;
- if (!phoneRegex.test(phone)) {
- let param = {
- id: pageId,
- title: "시스템 메시지",
- content: "휴대폰 번호를 형식에 맞게 입력하세요. 예시: 010-1234-5678",
- yes: {
- text: "확인",
- isProc: false,
- },
- no: {
- text: "취소",
- isProc: false,
- },
- };
- $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
- document.getElementById("userPhone").focus();
- return;
- }
- if (!agreeAll.value) {
- let param = {
- id: pageId,
- title: "시스템 메시지",
- content:
- "이벤트 참여 및 전화 상담을 위한 개인정보 수집 및\n이용 동의 (필수) 에 동의해주세요.",
- yes: {
- text: "확인",
- isProc: false,
- },
- no: {
- text: "취소",
- isProc: false,
- },
- };
- $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
- return;
- }
- savedName.value = name;
- savedPhone.value = phone;
- activeLayer1.value = !activeLayer1.value;
- const isMatched = await matchedUser(savedName.value, savedPhone.value, EVT_SEQ);
- if (!isMatched) {
- spinRoulette(savedName.value, savedPhone.value, EVT_SEQ);
- }
- };
- const rouletteCnt = (EVT_SEQ) => {
- let __req = {
- seq: EVT_SEQ,
- ignore_auth: 1,
- };
- useAxios()
- .post("/winner/itemcount", __req)
- .then((res) => {
- //createRoulette(res.data.count, res.data.items);
- /*
- TODO :
- 룰렛 생성 어떤 형태로 진행할지 기획 조율 필요
- */
- createRoulette(8, res.data.items);
- })
- .catch((error) => {})
- .finally(() => {});
- };
- // 전체 동의 체크 시 하위 동의도 같이 변경
- const onAgreeAllChange = () => {
- agree1.value = agreeAll.value;
- agree2.value = agreeAll.value;
- };
- // 하위 동의 체크 시 전체 동의 상태도 동기화
- const onAgreeChange = () => {
- agreeAll.value = agree1.value && agree2.value;
- };
- const winnerCheck = (EVT_SEQ) => {
- let params = {
- seq: EVT_SEQ,
- ignore_auth: 1,
- };
- useAxios()
- .post("/winner/winnerchk", params)
- .then((res) => {
- //console.log(res.data.status);
- rouletteCnt(EVT_SEQ);
- if (res.data.status == "closed") {
- setWinProbability(0);
- } else {
- setWinProbability(100); // 당첨 확률 몇 퍼센트로 할지 관리자 설정값으로 진행할지 여부 확인 필요
- }
- })
- .catch((error) => {
- //$log.debug("[equipMgmtReg][fnGetTenantList][error]");
- //useErrorHandler().fnSetCommErrorHandle(error, fnGetTenantList);
- })
- .finally(() => {
- //$log.debug("[equipMgmtReg][fnGetTenantList][finished]");
- //objSlt.value.tenantNameList = _cloneDeep(temp);
- });
- };
- const matchedUser = async (name, phone, EVT_SEQ) => {
- let __req = {
- name,
- phone,
- seq: EVT_SEQ,
- ignore_auth: 1,
- };
- try {
- const res = await useAxios().post("/winner/matcheduser", __req);
- if (res.data.result == "matched") {
- let param = {
- id: pageId,
- title: "시스템 메시지",
- content: `이미 참여하신 사용자입니다.<br/>다음 기회에 도전해보세요!`,
- yes: { text: "확인", isProc: false },
- no: { text: "취소", isProc: false },
- reload: true,
- };
- $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
- return true; // 이미 참여자
- } else if (res.data.result == "phonesame") {
- let param = {
- id: pageId,
- title: "시스템 메시지",
- content: `이미 참여한 사용자의 휴대폰 번호와 동일합니다.<br/>다음 기회에 도전해보세요!`,
- yes: { text: "확인", isProc: false },
- no: { text: "취소", isProc: false },
- reload: true,
- };
- $eventBus.emit("OPEN_CONFIRM_POP_UP", param);
- return true; // 이미 참여자
- }
- return false; // 참여자 아님
- } catch (e) {
- return false;
- }
- };
- /************************************************************************
- | 라이프 사이클
- ************************************************************************/
- onMounted(() => {
- // useSeq.value가 비어있지 않을 때만 winnerCheck 호출
- if (!useSeq.value || !compId.value) {
- $eventBus.emit("OPEN_CONFIRM_POP_UP", {
- id: "ROULETTE",
- title: "시스템 메시지",
- content: "이벤트 식별값(id/seq)이 없습니다.",
- yes: { text: "확인", isProc: false },
- no: { text: "취소", isProc: false },
- });
- return;
- }
- winnerCheck(useSeq.value);
- /*
- TODO :
- */
- });
- </script>
|