마이페이지 내 등급별 프로그레스 바가 올라가는 코드
<!--@css(/layout/membership.css)-->
<div class="member_head section" module="myshop_asyncbankbook" style="border:0;">
<div class="left" module="myshop_asyncbenefit">
<div style="display:none;">
<div class="next_grade">{$sNextGrade}</div>
<div class="next_grade_price">{$sGradeIncreasePrice}</div>
</div>
<img src="https://seoyoonwells.cafe24.com/web/upload/od_img/family.png" class="grade_img">
<div>
<h2>안녕하세요 {$member_name} 님!<br>
{$member_name}님의 회원 등급은 <span class="grade">{$group_name}</span></h2>
<p class="total_buy_price">구매 누적금액: {$sPeriodOrderPrice}</p>
<p class="total_buy_period">등급은 최근 12개월간 구매금액 기준입니다.</p>
</div>
<a href="/article/공지사항/1/125/">등급별 혜택 보기</a>
</div>
<div class="right">
<div class="cou">
<div><h2>쿠폰</h2><a href="/myshop/coupon/coupon.html">자세히보기</a></div>
<div>{$coupon_cnt}개</div>
</div>
<div class="cou">
<div><h2>적립금</h2><a href="/myshop/mileage/historyList.html">자세히보기</a></div>
<div>{$total_mileage}</div>
</div>
</div>
</div>
<style>
.progress-wrapper {
position: absolute;
opacity:0.5;
top: 145px;
left: 0;
transition: all 0.3s ease;
width: 100%;
height: 10px;
display: flex;
z-index: 1;
}
.progress-segment {
width: 12.5%;
height: 100%;
background: #d9d9d9;
position: relative;
}
.progress-segment.active {
background: #f5a200;
}
.progress-segment.active::after {
content: '';
position: absolute;
top: 50%;
z-index:100;
right: -8px;
transform: translateY(-50%);
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-left: 10px solid #f5a200;
}
@media screen and (max-width: 1024px) {
.progress-wrapper{ top: calc(60px + 22%);}
}
</style>
<div class="grade-track">
<div class="progress-wrapper">
<div class="progress-segment" data-step="0"></div>
<div class="progress-segment" data-step="1"></div>
<div class="progress-segment" data-step="2"></div>
<div class="progress-segment" data-step="3"></div>
<div class="progress-segment" data-step="4"></div>
<div class="progress-segment" data-step="5"></div>
<div class="progress-segment" data-step="6"></div>
<div class="progress-segment" data-step="7"></div>
</div>
<ul class="grade-steps">
<li class="step" data-grade="FRIEND">
<div class="grade-icon">
<img src="https://seoyoonwells.cafe24.com/web/upload/od_img/friend.png" alt="FRIEND">
</div>
<p class="title">FRIEND</p>
<p class="desc">회원가입 즉시</p>
</li>
<li class="step" data-grade="FAMILY">
<div class="grade-icon">
<img src="https://seoyoonwells.cafe24.com/web/upload/od_img/family.png" alt="FAMILY">
</div>
<p class="title">FAMILY</p>
<p class="desc"><span class="mo_block">구매금액</span> <span class="mo_block">10만원 이상</span> 50만원 미만</p>
</li>
<li class="step" data-grade="VIP">
<div class="grade-icon">
<img src="https://seoyoonwells.cafe24.com/web/upload/od_img/vip.png" alt="VIP">
</div>
<p class="title">VIP</p>
<p class="desc"><span class="mo_block">구매금액</span> <span class="mo_block">50만원 이상</span> 150만원 미만</p>
</li>
<li class="step" data-grade="RENOPTIETER">
<div class="grade-icon crown">
<img src="https://seoyoonwells.cafe24.com/web/upload/od_img/renopti.png" class="renopti_img" alt="RENOPTIETER">
</div>
<p class="title">RENOPTIETER</p>
<p class="desc"><span class="mo_block">구매금액</span>150만원 이상</p>
</li>
</ul>
<div class="remain-box"></div>
</div>
<script> //   없애기
var options = document.getElementsByClassName('grade-steps')
for (index = 0; index < options.length; ++index) {
options[index].innerHTML = options[index].innerHTML.replace(/ /g, ' ');
}
</script>
<script>
setTimeout(function() {
var gradeText = $('.grade').text().trim().toUpperCase();
console.log('[DEBUG] grade 텍스트:', gradeText);
var gradeImgMap = {
'FRIEND': 'https://seoyoonwells.cafe24.com/web/upload/od_img/friend.png',
'FAMILY': 'https://seoyoonwells.cafe24.com/web/upload/od_img/family.png',
'VIP': 'https://seoyoonwells.cafe24.com/web/upload/od_img/vip.png',
'RENOPTIETER': 'https://seoyoonwells.cafe24.com/web/upload/od_img/renopti.png'
};
if (gradeImgMap[gradeText]) {
console.log('[DEBUG] 이미지 src 설정:', gradeImgMap[gradeText]);
$('.grade_img').attr('src', gradeImgMap[gradeText]);
} else {
console.warn('[WARN] 해당하는 등급 이미지가 없습니다:', gradeText);
}
}, 500);
$(function () {
function checkGradeLoaded(callback) {
const gradeText = $('.grade').text().trim();
const nextGradeText = $('.next_grade').text().trim();
if (gradeText && nextGradeText) {
callback();
} else {
setTimeout(() => checkGradeLoaded(callback), 100);
}
}
checkGradeLoaded(function () {
const grade = $('.grade').text().trim().toUpperCase();
const nextGrade = $('.next_grade').text().trim().toUpperCase();
const nextPriceText = $('.next_grade_price').text();
const nextPrice = parseInt(nextPriceText.replace(/[^0-9]/g, ''), 10) || 0;
const isMobile = window.innerWidth <= 1024;
// ✅ 프로그레스 바 처리 (홀수 index만 사용)
$('.progress-segment').removeClass('active');
const stepMap = {
'FRIEND': 1,
'FAMILY': 3,
'VIP': 5
};
const activeStep = stepMap[grade];
if (activeStep !== undefined) {
$('.progress-segment[data-step="' + activeStep + '"]').addClass('active');
}
// ✅ 등급 단계 표시 (data-grade 기준)
$('.grade-steps .step').removeClass('done current next');
let foundCurrent = false;
$('.grade-steps .step').each(function () {
const stepGrade = $(this).data('grade')?.toString().toUpperCase();
if (stepGrade === grade) {
$(this).addClass('current');
foundCurrent = true;
} else if (foundCurrent && stepGrade === nextGrade) {
$(this).addClass('next');
} else if (!foundCurrent) {
$(this).addClass('done');
}
});
// ✅ 말풍선 처리 유지
if (nextGrade && nextPrice > 0 && grade !== 'RENOPTIETER') {
const formattedPrice = nextPrice.toLocaleString();
const message = `${nextGrade} 등급까지 남은 금액: <strong>${formattedPrice}원</strong>`;
$('.remain-box').html(message).show();
$('.remain-box').removeClass('tail-friend tail-family tail-vip tail-renop');
if (isMobile) {
if (nextGrade === 'FAMILY') $('.remain-box').addClass('tail-family');
else if (nextGrade === 'VIP') $('.remain-box').addClass('tail-vip');
else if (nextGrade === 'RENOPTIETER') $('.remain-box').addClass('tail-renop');
else $('.remain-box').addClass('tail-friend');
}
const $currentStep = $('.grade-steps .step.current');
const $nextStep = $('.grade-steps .step.next');
const $track = $('.grade-track');
if ($currentStep.length && $nextStep.length && $track.length) {
const currentOffset = $currentStep.offset();
const nextOffset = $nextStep.offset();
const trackOffset = $track.offset();
const currentWidth = $currentStep.outerWidth();
const nextWidth = $nextStep.outerWidth();
const currentCenter = currentOffset.left - trackOffset.left + currentWidth / 2;
const nextCenter = nextOffset.left - trackOffset.left + nextWidth / 2;
const middleLeft = (currentCenter + nextCenter) / 2;
if (isMobile) {
$('.grade-steps').after($('.remain-box'));
$('.remain-box').css({
left: '50%',
transform: 'translateX(-50%)',
width: 'calc(100% - 40px)',
maxWidth: '500px',
margin: '20px auto 0',
opacity: '1'
});
} else {
$nextStep.append($('.remain-box'));
$('.remain-box').css({
position: 'relative',
left: '50%',
transform: 'translateX(-50%)',
opacity: '1',
marginTop: '25px'
});
}
}
} else {
$('.remain-box').hide();
}
});
});
$(window).on('load resize', function () {
var $img = $('.step.current .grade-icon img');
var $wrapper = $('.progress-wrapper');
var imgHeight = $img.outerHeight();
var wrapperHeight = $wrapper.outerHeight();
if (imgHeight && wrapperHeight) {
var newTop = (imgHeight / 2) - (wrapperHeight / 2);
var fixedOffset = 110; // 추가로 고정값을 더하고 싶으면 여기 수정
// top 계산 적용
$wrapper.css('top', (fixedOffset + newTop) + 'px');
$wrapper.css('opacity', '1');
console.log('[DEBUG] imgHeight:', imgHeight, '| wrapperHeight:', wrapperHeight, '| top:', fixedOffset + newTop + 'px');
} else {
console.log('[DEBUG] 이미지나 래퍼 높이 측정 실패');
}
});
</script>
<style>
.member_head {display:flex; padding:30px 0 60px;}
.grade_img { height: 88px;}
.member_head .left {display:flex; gap:35px;}
.member_head .left h2 {font-size:20px; font-weight:600; line-height:1.58;}
.member_head .left h2 > span.grade {color:#FFA500;}
.member_head .left .total_buy_price {font-size:15px; font-weight:500; margin-top:5px;}
.member_head .left .total_buy_period {font-size:12px; font-weight:500; margin-top:5px; color:#666;}
.member_head .left a {font-sizE:15px; padding:10px 23px; border:1px solid #000; border-radius:30px; text-align:center; height: fit-content; margin-left:50px;}
.member_head .right {background:#F0F0F0; display:flex; padding:20px 27px; gap:40px;}
.member_head .right > div {display:flex; flex-direction: column; gap:40px;}
.member_head .right > div > div:first-child {display:flex; gap:160px; align-items: center; }
.member_head .right > div > div:last-child {font-size:17px; font-weight:500;}
.member_head .right > div h2 {font-size:12px;}
.member_head .right > div a {color:#7B808F; font-size:10px; text-decoration:underline;}
.grade-track {
max-width: 1680px;
position: relative;
width: 92%;
padding:50px 0;
border:1px solid #000;
margin: 0 auto;
text-align: center;
font-family: 'Pretendard', sans-serif;
margin-bottom:50px;
position: relative;
}
.grade-steps {
display: flex;
align-items: flex-start
justify-content: space-between;
padding: 60px 0 40px;
margin: 0;
list-style: none;
position: relative;
z-index: 2;
}
.progress-bar {
position: absolute;
top: 145px;
left: 0;
right: 0;
height: 10px;
background: #d9d9d9;
z-index: 1;
}
.progress-fill {
height: 100%;
width: 0%; /* JS로 동적으로 제어 가능 */
background: #f5a200;
transition: width 0.3s;
position: relative;
}
.progress-fill::after {
opacity:0;
content: "";
position: absolute;
top: 50%;
right: -8px; /* 살짝 오른쪽 바깥쪽으로 */
transform: translateY(-50%);
width: 0;
height: 0;
border-top: 20px solid transparent;
transition: opacity 0.4s ease;
border-bottom: 20px solid transparent;
border-left: 20px solid #f5a200; /* 노란 화살표색 */
}
.progress-fill.show-arrow::after {
opacity: 1;
}
.step {
width: 25%;
position: relative;
z-index: 2;
}
.grade-icon {
width: 74px;
height:74px;
border-radius: 50%;
margin: 0 auto 20px;
display: flex;
background: #fff;
justify-content: center;
align-items: center;
box-sizing: border-box;
}
.grade-icon img {
width: 100%;
opacity:0.3;
height: auto;
}
.step .title {
font-weight: 700;
color: #000;
opacity:0.3;
margin: 0 0 4px;
font-size:15px;
}
.step .desc {
font-size: 14px;
line-height: 1.4;
color: #000;
opacity:0.3;
margin: 0;
}
.step.current .title,
.step.current .desc,
.step.current .grade-icon img{
opacity:1;
}
.step.next .title,
.step.next .desc,
.step.next .grade-icon img{
opacity:1;
}
.step.current .grade-icon img {border: 3px solid #f5a200; border-radius:100%;}
.renopti_img {border:0 !important;}
.step.current .desc,
.step.next .desc {font-weight:700;}
.step.done .title,
.step.done .desc,
.step.dones .grade-icon img
{
opacity:0.3;
}
.step.next .title,
.step.current .title {color:#FFA500;}
.renopti_img{ margin-top: -23px;}
.grade-icon.crown {
position: relative;
}
.grade-icon.crown::before {
content: "";
position: absolute;
top: -30px;
left: 50%;
width: 50px;
height: 30px;
background: url(crown.svg) center/contain no-repeat;
transform: translateX(-50%);
}
.remain-box {
position: absolute;
opacity:0;
z-index: 10;
background: #FFA500;
color: #fff;
font-size: 13px;
margin-top: -20px;
font-weight: 500;
padding: 14px 60px;
border-radius: 32px;
white-space: nowrap;
width: fit-content;
box-sizing: border-box;
}
.remain-box::before {
content: "";
position: absolute;
top: -8px;
left: 50%;
transform: translateX(-50%);
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-bottom: 8px solid #FFA500;
}
/* 말풍선 꼬리 (모바일) */
.remain-box::before {
content: '';
position: absolute;
top: -8px;
width: 0;
height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-bottom: 8px solid #FFA500;
}
/* 꼬리 위치 (등급에 따라 조정) */
.tail-friend::before {
transform: translateX(-50%);
left: 9%;
}
.tail-family::before {
left: 36%;
transform: translateX(-50%);
}
.tail-vip::before {
left: 64%;
transform: translateX(-50%);
}
.tail-renop::before {
left: 92%;
transform: translateX(-50%);
}
.grade_msg {text-align:center; line-height:1.4; margin:30px 0 90px; }
.grade_msg b {font-weight:600; font-size:12px;}
.grade_msg .point {font-size:14px; color:#FFA500; font-weight:900;}
.grade_msg p {font-weight:600; font-size:12px; color:#828282;}
.only_mo {display:none;}
@media screen and (max-width: 1024px) {
.member_head .left h2 > span.grade {font-weight:bold;}
.only_mo {display: block; width: 100%;
position: absolute;
bottom: 10px;
text-align: center;}
.grade-track {padding:50px 0 100px;}
.member_head { flex-direction: column; width:100%; margin:0;}
.member_head .left { flex-direction: column; align-items: center; border:0; gap:20px;}
.member_head .left h2 {text-align:center; font-weight:400;}
.step .title {font-size:13px;}
.total_buy_price,
.total_buy_period{text-align:center;}
.member_head .left a {margin-left:0;}
.member_head .right > div {width:50%;}
.member_head .right > div > div:first-child {gap:0; justify-content: space-between;}
.member_head .right { margin: 40px 4% 0;}
.grade-steps { padding: 60px 0 0px;}
.grade-icon {width:50%; height:auto;}
.desc_period {display:none;}
.renopti_img{ margin-top: -28%;}
.grade-steps{ align-items: flex-start;}
.progress-bar {top:39%;}
.remain-box {padding:10px; font-size:11px; left: 50% !important; margin-top: 20px;}
.grade_msg .point {font-weight:700;}
.grade_msg {line-height:1.8;}
.mo_block {display:block;}
.step .desc {font-size:10px;}
.progress-fill::after {
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-left: 10px solid #f5a200;
}
}
</style>