최초 세팅
This commit is contained in:
1416
src/main/resources/templates/web/service/makeReservation.html
Normal file
1416
src/main/resources/templates/web/service/makeReservation.html
Normal file
File diff suppressed because it is too large
Load Diff
408
src/main/resources/templates/web/service/serviceInfo.html
Normal file
408
src/main/resources/templates/web/service/serviceInfo.html
Normal file
@@ -0,0 +1,408 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{/web/layout/layout}">
|
||||
<th:block layout:fragment="layoutCss">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 전체 기본 스타일 */
|
||||
html, body {
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
color: #1a1a1a;
|
||||
line-height: 1.6;
|
||||
overflow-x: hidden;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 메인 컨테이너 */
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
width: 100%;
|
||||
height: calc(100vh - 300px); /* 헤더/푸터 공간 확보 */
|
||||
min-height: calc(100vh - 300px);
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
/* 상단 헤더 영역 */
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem 2rem;
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 2px 16px rgba(0,0,0,0.06);
|
||||
border-bottom: 3px solid #C60B24;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: clamp(1.5rem, 3vw, 1.875rem); /* 24px ~ 30px */
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin: 0;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
/* 하단 메인 콘텐츠 영역 (사이드바 + 메인) */
|
||||
.content-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* 좌측 카테고리 사이드바 */
|
||||
.sidebar {
|
||||
width: 280px;
|
||||
min-width: 250px;
|
||||
flex-shrink: 0;
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 2px 16px rgba(0,0,0,0.06);
|
||||
height: fit-content;
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 1.25rem 1.5rem;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
font-size: 1.125rem; /* 18px */
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.category-list {
|
||||
list-style: none;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.category-item {
|
||||
margin-bottom: 0.375rem;
|
||||
}
|
||||
|
||||
.category-link {
|
||||
display: block;
|
||||
padding: 0.875rem 1rem;
|
||||
text-decoration: none;
|
||||
color: #6b7280;
|
||||
border-radius: 10px;
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem; /* 15.2px */
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.category-link:hover {
|
||||
background-color: #f9fafb;
|
||||
color: #C60B24;
|
||||
border-color: #f3f4f6;
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.category-link.active {
|
||||
background-color: rgba(198, 11, 36, 0.08);
|
||||
color: #C60B24;
|
||||
border-color: #C60B24;
|
||||
border-left: 4px solid #C60B24;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 메인 콘텐츠 영역 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 스크롤 가능한 서비스 카드 컨테이너 */
|
||||
.services-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-right: 0.5rem;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.services-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
|
||||
gap: 1.25rem;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
.service-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 1.75rem;
|
||||
box-shadow: 0 2px 16px rgba(0,0,0,0.06);
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid #f1f5f9;
|
||||
}
|
||||
|
||||
.service-card:hover {
|
||||
transform: translateY(-6px);
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.12);
|
||||
border-color: #C60B24;
|
||||
}
|
||||
|
||||
.service-title {
|
||||
font-size: 1.375rem; /* 22px */
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 0.5rem;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
.service-description {
|
||||
color: #6b7280;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.5;
|
||||
font-size: 0.9375rem; /* 15px */
|
||||
}
|
||||
|
||||
.service-price {
|
||||
font-size: 1.5rem; /* 24px */
|
||||
font-weight: 700;
|
||||
color: #C60B24;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.cancel-price {
|
||||
font-size: 1.125rem; /* 18px */
|
||||
font-weight: 400;
|
||||
color: #9ca3af;
|
||||
text-align: right;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
/* 로딩 스타일 */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: #6b7280;
|
||||
font-size: 0.9375rem; /* 15px */
|
||||
}
|
||||
|
||||
.error-message {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: #dc2626;
|
||||
font-size: 0.9375rem; /* 15px */
|
||||
}
|
||||
|
||||
/* 스크롤바 커스텀 스타일링 */
|
||||
.services-list::-webkit-scrollbar,
|
||||
.sidebar::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.services-list::-webkit-scrollbar-track,
|
||||
.sidebar::-webkit-scrollbar-track {
|
||||
background: #f8fafc;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.services-list::-webkit-scrollbar-thumb,
|
||||
.sidebar::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.services-list::-webkit-scrollbar-thumb:hover,
|
||||
.sidebar::-webkit-scrollbar-thumb:hover {
|
||||
background: #C60B24;
|
||||
}
|
||||
|
||||
/* 반응형 디자인 */
|
||||
@media (max-width: 1024px) {
|
||||
.container {
|
||||
max-width: 100%;
|
||||
padding: 1rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 240px;
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
.services-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 1.25rem 1.5rem;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: clamp(1.375rem, 2.8vw, 1.75rem); /* 22px ~ 28px */
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
gap: 1rem;
|
||||
height: auto;
|
||||
min-height: calc(100vh - 300px);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
position: static;
|
||||
order: 1;
|
||||
flex-shrink: 0;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.category-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
gap: 0.5rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.category-link {
|
||||
text-align: center;
|
||||
padding: 0.75rem 0.5rem;
|
||||
font-size: 0.875rem; /* 14px */
|
||||
}
|
||||
|
||||
.main-content {
|
||||
order: 2;
|
||||
flex: 1;
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.services-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: clamp(1.25rem, 2.5vw, 1.5rem); /* 20px ~ 24px */
|
||||
}
|
||||
|
||||
.service-title {
|
||||
font-size: 1.25rem; /* 20px */
|
||||
}
|
||||
|
||||
.service-price {
|
||||
font-size: 1.375rem; /* 22px */
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.container {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 0.875rem 1rem;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: clamp(1.125rem, 2.2vw, 1.375rem); /* 18px ~ 22px */
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
font-size: 1rem; /* 16px */
|
||||
padding: 1rem 1.25rem;
|
||||
}
|
||||
|
||||
.service-card {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</th:block>
|
||||
|
||||
<th:block layout:fragment="layout_top_script">
|
||||
<script th:inline="javascript">
|
||||
// 서버에서 전달받은 파라미터들을 전역 변수로 설정
|
||||
window.pageParams = {
|
||||
categoryDivCd: /*[[${param.categoryDivCd}]]*/ '',
|
||||
categoryNo: /*[[${param.categoryNo}]]*/ ''
|
||||
};
|
||||
|
||||
// 빈 값 처리
|
||||
if (!window.pageParams.categoryDivCd || window.pageParams.categoryDivCd === 'null') {
|
||||
window.pageParams.categoryDivCd = 'SERVICE'; // 기본값
|
||||
}
|
||||
if (!window.pageParams.categoryNo || window.pageParams.categoryNo === 'null') {
|
||||
window.pageParams.categoryNo = '';
|
||||
}
|
||||
|
||||
console.log('페이지 파라미터:', window.pageParams);
|
||||
</script>
|
||||
</th:block>
|
||||
|
||||
<th:block layout:fragment="layoutContent">
|
||||
<div class="container">
|
||||
<!-- 상단 헤더 영역 -->
|
||||
<div class="header">
|
||||
<h2 class="page-title">시술안내/가격</h2>
|
||||
</div>
|
||||
|
||||
<!-- 하단 콘텐츠 영역 (사이드바 + 메인) -->
|
||||
<div class="content-wrapper">
|
||||
<!-- 좌측 카테고리 사이드바 -->
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-header">Category</div>
|
||||
<ul class="category-list" id="category-list">
|
||||
<li class="category-item">
|
||||
<div class="loading">카테고리 로딩 중...</div>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
<!-- 메인 콘텐츠 -->
|
||||
<main class="main-content">
|
||||
<!-- 스크롤 가능한 서비스 카드 컨테이너 -->
|
||||
<div class="services-list">
|
||||
<div class="services-grid" id="services-grid">
|
||||
<div class="loading">서비스 정보 로딩 중...</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
<th:block layout:fragment="layoutContentScript">
|
||||
<script src="/js/web/service/serviceInfo.js"></script>
|
||||
</th:block>
|
||||
</html>
|
||||
782
src/main/resources/templates/web/service/webServiceDetail.html
Normal file
782
src/main/resources/templates/web/service/webServiceDetail.html
Normal file
@@ -0,0 +1,782 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{/web/layout/layout}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<th:block layout:fragment="layoutCss">
|
||||
<!-- Choices.js CSS -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/choices.js/10.2.0/choices.min.css" />
|
||||
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
font-family: 'Noto Sans KR', sans-serif;
|
||||
margin: 0;
|
||||
background: #f7f7f9;
|
||||
color: #222;
|
||||
}
|
||||
#service-header {
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #ececec;
|
||||
padding: 14px 0 14px 24px;
|
||||
font-size: 0.98em;
|
||||
color: #888;
|
||||
}
|
||||
.main-wrap {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
border-radius: 18px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.07);
|
||||
margin-top: 32px;
|
||||
padding: 0 0 32px 0;
|
||||
}
|
||||
.top-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 32px;
|
||||
padding: 32px 32px 0 32px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.img-box {
|
||||
border-radius: 18px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
.img-box img {
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 18px;
|
||||
}
|
||||
.info-box {
|
||||
flex: 1 1 300px;
|
||||
min-width: 240px;
|
||||
}
|
||||
.info-title {
|
||||
font-size: 1.7em;
|
||||
font-weight: 700;
|
||||
margin-bottom: 6px;
|
||||
color: #222;
|
||||
}
|
||||
.info-desc {
|
||||
color: #444;
|
||||
font-size: 1.1em;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.info-price {
|
||||
font-size: 1.2em;
|
||||
font-weight: 700;
|
||||
color: #b23c3c;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.select-row {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.select-row label {
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
.total-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 1.1em;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.total-row .total-label {
|
||||
color: #888;
|
||||
}
|
||||
.total-row .total-price {
|
||||
font-weight: bold;
|
||||
color: #b23c3c;
|
||||
}
|
||||
.reserve-btn {
|
||||
width: 100%;
|
||||
padding: 14px 0;
|
||||
background: #b23c3c;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.reserve-btn:disabled {
|
||||
background: #ddd;
|
||||
color: #888;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.desc-section {
|
||||
margin-top: 36px;
|
||||
padding: 0 32px;
|
||||
}
|
||||
.desc-title {
|
||||
font-size: 1.25em;
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
color: #222;
|
||||
}
|
||||
.desc-content {
|
||||
color: #444;
|
||||
font-size: 1.05em;
|
||||
line-height: 1.7;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.hashtag-section {
|
||||
margin-top: 30px;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
.hashtag-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
.hashtag-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
.hashtag {
|
||||
display: inline-block;
|
||||
background-color: #f8f9fa;
|
||||
color: #495057;
|
||||
padding: 8px 15px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
border: 1px solid #dee2e6;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.hashtag:hover {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border-color: #007bff;
|
||||
cursor: pointer;
|
||||
}
|
||||
#thumbnail-bottom-txt{
|
||||
padding: 8px;
|
||||
margin-top: 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* Choices.js 커스터마이징 - 개선된 버전 */
|
||||
.choices {
|
||||
border: 1px solid #ddd !important;
|
||||
border-radius: 6px !important;
|
||||
font-size: 1em !important;
|
||||
background: #fff !important;
|
||||
min-width: 300px !important;
|
||||
}
|
||||
|
||||
.choices__inner {
|
||||
background: #fff !important;
|
||||
padding: 8px 12px !important;
|
||||
min-height: 40px !important;
|
||||
color: #222 !important;
|
||||
min-width: 280px !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* 플레이스홀더 텍스트 잘림 방지 - 핵심 수정 */
|
||||
.choices__placeholder {
|
||||
color: #888 !important;
|
||||
opacity: 1 !important;
|
||||
width: 100% !important;
|
||||
min-width: 200px !important;
|
||||
white-space: nowrap !important;
|
||||
overflow: visible !important;
|
||||
text-overflow: clip !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.choices__input {
|
||||
color: #222 !important;
|
||||
background: transparent !important;
|
||||
min-width: 200px !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.choices__input::placeholder {
|
||||
color: #888 !important;
|
||||
opacity: 1 !important;
|
||||
width: 100% !important;
|
||||
min-width: 200px !important;
|
||||
white-space: nowrap !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* 다중 선택시 입력 필드 너비 확보 */
|
||||
.choices[data-type*="select-multiple"] .choices__input {
|
||||
min-width: 200px !important;
|
||||
width: auto !important;
|
||||
flex: 1 !important;
|
||||
}
|
||||
|
||||
/* 선택된 항목들과 입력 필드 공간 분배 */
|
||||
.choices__list--multiple {
|
||||
/* display: flex !important; */
|
||||
flex-wrap: wrap !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
.choices__list--multiple:empty + .choices__input {
|
||||
min-width: 200px !important;
|
||||
width: 100% !important;
|
||||
flex: 1 !important;
|
||||
}
|
||||
|
||||
/* 선택된 항목의 가격 표시 스타일 - 새로 추가 */
|
||||
.selected-item-content {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
align-items: flex-start !important;
|
||||
flex: 1 !important;
|
||||
}
|
||||
|
||||
.selected-item-name {
|
||||
font-weight: 500 !important;
|
||||
margin-bottom: 2px !important;
|
||||
}
|
||||
|
||||
.selected-item-price {
|
||||
font-size: 0.85em !important;
|
||||
}
|
||||
|
||||
.selected-price {
|
||||
color: #b23c3c !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.selected-price-discount {
|
||||
color: #b23c3c !important;
|
||||
font-weight: bold !important;
|
||||
font-size: 0.9em !important;
|
||||
}
|
||||
|
||||
.selected-price-original {
|
||||
color: #aaa !important;
|
||||
font-size: 0.85em !important;
|
||||
text-decoration: line-through !important;
|
||||
margin-left: 4px !important;
|
||||
}
|
||||
|
||||
/* 선택된 항목들 스타일 수정 */
|
||||
.choices__list--multiple .choices__item {
|
||||
background-color: #f8f9fa !important;
|
||||
border: 1px solid #dee2e6 !important;
|
||||
border-radius: 16px !important;
|
||||
padding: 8px 12px !important;
|
||||
margin: 2px !important;
|
||||
font-size: 0.9em !important;
|
||||
color: #495057 !important;
|
||||
flex-shrink: 0 !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
/* max-width: 300px !important; */
|
||||
}
|
||||
|
||||
/* 빨간색으로 변경 */
|
||||
.choices__button {
|
||||
background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjYjIzYzNjIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==') !important;
|
||||
background-size: 14px 14px !important;
|
||||
background-position: center !important;
|
||||
background-repeat: no-repeat !important;
|
||||
border-left:0 !important;
|
||||
margin:0 !important;
|
||||
padding:0 !important;
|
||||
width:14px !important;
|
||||
}
|
||||
|
||||
/* 드롭다운 컨테이너 */
|
||||
.choices__list--dropdown {
|
||||
background: #fff !important;
|
||||
border: 1px solid #ddd !important;
|
||||
border-radius: 6px !important;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important;
|
||||
}
|
||||
|
||||
/* 드롭다운 옵션들 스타일 */
|
||||
.choices__list--dropdown .choices__item {
|
||||
padding: 8px 12px !important;
|
||||
display: flex !important;
|
||||
justify-content: space-between !important;
|
||||
align-items: center !important;
|
||||
color: #222 !important;
|
||||
}
|
||||
|
||||
.choices__list--dropdown .choices__item--selectable:hover {
|
||||
background-color: #f5f5f5 !important;
|
||||
color: #222 !important;
|
||||
}
|
||||
|
||||
.choices__list--dropdown .choices__item--highlighted {
|
||||
background-color: #b23c3c !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* 포커스 상태 */
|
||||
.choices.is-focused .choices__inner {
|
||||
border-color: #b23c3c !important;
|
||||
}
|
||||
|
||||
/* 가격 표시 스타일 */
|
||||
.procedure-price {
|
||||
color: #b23c3c !important;
|
||||
font-weight: bold !important;
|
||||
font-size: 0.9em !important;
|
||||
}
|
||||
|
||||
.procedure-price-discount {
|
||||
color: #b23c3c !important;
|
||||
font-weight: bold !important;
|
||||
font-size: 0.9em !important;
|
||||
}
|
||||
|
||||
.procedure-price-original {
|
||||
color: #aaa !important;
|
||||
font-size: 0.85em !important;
|
||||
text-decoration: line-through !important;
|
||||
margin-left: 4px !important;
|
||||
}
|
||||
|
||||
/* 드롭다운이 열렸을 때 하이라이트된 항목의 가격 색상 */
|
||||
.choices__list--dropdown .choices__item--highlighted .procedure-price,
|
||||
.choices__list--dropdown .choices__item--highlighted .procedure-price-discount {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.choices__list--dropdown .choices__item--highlighted .procedure-price-original {
|
||||
color: #ddd !important;
|
||||
}
|
||||
|
||||
/* 검색 입력창 스타일 */
|
||||
.choices[data-type*="select-multiple"] .choices__input {
|
||||
background-color: transparent !important;
|
||||
color: #222 !important;
|
||||
}
|
||||
|
||||
/* 비활성화 상태 */
|
||||
.choices.is-disabled .choices__inner {
|
||||
background-color: #f8f9fa !important;
|
||||
color: #6c757d !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
/* 반응형 대응 */
|
||||
@media (max-width: 600px) {
|
||||
.choices {
|
||||
min-width: 250px !important;
|
||||
}
|
||||
|
||||
.choices__inner {
|
||||
min-width: 230px !important;
|
||||
}
|
||||
|
||||
.choices__placeholder,
|
||||
.choices__input,
|
||||
.choices__input::placeholder {
|
||||
min-width: 150px !important;
|
||||
}
|
||||
|
||||
.choices__list--multiple .choices__item {
|
||||
max-width: 250px !important;
|
||||
padding: 6px 10px !important;
|
||||
}
|
||||
|
||||
.selected-item-name {
|
||||
font-size: 0.9em !important;
|
||||
}
|
||||
|
||||
.selected-item-price {
|
||||
font-size: 0.8em !important;
|
||||
}
|
||||
|
||||
.choices__list--multiple .choices__item[data-deletable] .choices__button {
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
font-size: 16px !important;
|
||||
text-indent: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.main-wrap { margin-top: 16px; }
|
||||
.top-section { flex-direction: column; gap: 18px; padding: 20px 10px 0 10px; }
|
||||
.img-box { width: 100%;}
|
||||
.info-box { min-width: unset; }
|
||||
.desc-section { padding: 0 10px; }
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.main-wrap { margin-top: 0; border-radius: 0; box-shadow: none; }
|
||||
.top-section { padding: 12px 2vw 0 2vw; }
|
||||
.desc-section { padding: 0 2vw; }
|
||||
}
|
||||
</style>
|
||||
</th:block>
|
||||
|
||||
<th:block layout:fragment="layout_top_script">
|
||||
<script th:inline="javascript">
|
||||
let category_div_cd = [[${CATEGORY_DIV_CD}]];
|
||||
let category_no = [[${CATEGORY_NO}]];
|
||||
let post_no = [[${POST_NO}]];
|
||||
const CDN_URL = [[${@environment.getProperty('url.cdn')}]];
|
||||
</script>
|
||||
</th:block>
|
||||
|
||||
<th:block layout:fragment="layoutContent">
|
||||
<div id="service-header">
|
||||
시술안내/가격 > <span id="header-category-nm"></span> > <span id="header-title"></span>
|
||||
</div>
|
||||
<div class="main-wrap">
|
||||
<div class="top-section">
|
||||
<div class="img-box">
|
||||
<img id="serviceThumb" th:src="${@environment.getProperty('madeu.logo.size800x450')}" alt="썸네일 이미지">
|
||||
<pre id="thumbnail-bottom-txt"></pre>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<div class="info-title" id="title">메쉬다 D/S</div>
|
||||
<div class="info-desc" id="contents">전신 라인 정리에 특화된 메쉬다 바디주사!</div>
|
||||
|
||||
<div class="hashtag-section">
|
||||
<div class="hashtag-container">
|
||||
<div class="hashtag-list">
|
||||
<span class="hashtag">#메쉬다바디주사</span>
|
||||
<span class="hashtag">#쥬베룩</span>
|
||||
<span class="hashtag">#바디라인정리</span>
|
||||
<span class="hashtag">#콜라겐부스터</span>
|
||||
<span class="hashtag">#전신시술</span>
|
||||
<span class="hashtag">#자연스러운볼륨</span>
|
||||
<span class="hashtag">#빠른회복</span>
|
||||
<span class="hashtag">#합리적가격</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-price"><span id="price">0</span>원 부터</div>
|
||||
|
||||
<div class="select-row">
|
||||
<label for="procedure-select">시술 선택</label>
|
||||
<select id="procedure-select" multiple></select>
|
||||
</div>
|
||||
|
||||
<div class="total-row">
|
||||
<span class="total-label">총 금액</span>
|
||||
<span class="total-price" id="total">0원</span>
|
||||
</div>
|
||||
<button class="reserve-btn" id="reserve-btn" disabled>시술 예약하기</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="desc-section">
|
||||
<div class="desc-content">
|
||||
<img id="contents_path" th:src="${@environment.getProperty('madeu.logo.size800x450')}" alt="컨텐츠 이미지" width="100%" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
<th:block layout:fragment="layoutContentScript">
|
||||
<!-- Choices.js JavaScript -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/choices.js/10.2.0/choices.min.js"></script>
|
||||
|
||||
<script>
|
||||
// 전역 변수
|
||||
let procedureChoices;
|
||||
let priceList = [];
|
||||
const totalEl = document.getElementById('total');
|
||||
const reserveBtn = document.getElementById('reserve-btn');
|
||||
|
||||
// 초기화
|
||||
fn_SelectDetail(category_div_cd, category_no, post_no);
|
||||
|
||||
/****************************************************************************
|
||||
* 카테고리 목록 가져오기
|
||||
****************************************************************************/
|
||||
function fn_SelectDetail(category_div_cd, category_no, post_no) {
|
||||
let formData = new FormData();
|
||||
formData.append('CATEGORY_DIV_CD', category_div_cd);
|
||||
formData.append('CATEGORY_NO', category_no);
|
||||
formData.append('POST_NO', post_no);
|
||||
|
||||
$.ajax({
|
||||
url: encodeURI('/webservice/selectServiceDetail.do'),
|
||||
data: formData,
|
||||
dataType: 'json',
|
||||
processData: false,
|
||||
contentType: false,
|
||||
type: 'POST',
|
||||
async: true,
|
||||
success: function(data) {
|
||||
if(data.msgCode == '0') {
|
||||
// 화면 데이터 변경
|
||||
$('#title').text(data.rows.TITLE);
|
||||
$('#serviceThumb').attr('src', CDN_URL + data.rows.THUMBNAIL_PATH);
|
||||
$('#thumbnail-bottom-txt').text(data.rows.THUMBNAIL_BOTTOM_TXT);
|
||||
$('#contents').text(data.rows.CONTENT);
|
||||
|
||||
if(data.rows.PRICE == null || data.rows.PRICE == undefined) {
|
||||
$('#price').text('0');
|
||||
} else {
|
||||
$('#price').text(data.rows.PRICE.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","));
|
||||
}
|
||||
|
||||
// header 동적 변경
|
||||
$('#header-category-nm').text(data.rows.CATEGORY_NM);
|
||||
$('#header-title').text(data.rows.TITLE);
|
||||
|
||||
// 해시태그 처리
|
||||
var hashtagHtml = '';
|
||||
if (data.rows.HASHTAG) {
|
||||
var tags = data.rows.HASHTAG.split('#');
|
||||
tags.forEach(function(tag) {
|
||||
var trimmed = tag.trim();
|
||||
if(trimmed) {
|
||||
hashtagHtml += '<span class="hashtag">#' + trimmed + '</span>';
|
||||
}
|
||||
});
|
||||
}
|
||||
$('.hashtag-list').html(hashtagHtml);
|
||||
|
||||
$('#contents_path').attr('src', CDN_URL + data.rows.CONTENTS_PATH);
|
||||
|
||||
// 시술 목록 데이터 처리
|
||||
updateProcedureOptions(data.price || []);
|
||||
} else {
|
||||
modalEvent.danger("조회 오류", data.msgDesc);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
modalEvent.danger("조회 오류", "조회 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.");
|
||||
},
|
||||
beforeSend: function() {
|
||||
$(".loading-image-layer").show();
|
||||
},
|
||||
complete: function() {
|
||||
$(".loading-image-layer").hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Choices.js 초기화 및 옵션 업데이트
|
||||
****************************************************************************/
|
||||
function updateProcedureOptions(data) {
|
||||
priceList = data;
|
||||
|
||||
// 기존 Choices 인스턴스 제거
|
||||
if (procedureChoices) {
|
||||
procedureChoices.destroy();
|
||||
}
|
||||
|
||||
// 선택 옵션 데이터 생성
|
||||
const choices = data.map(item => {
|
||||
if (!item.MU_TREATMENT_PROCEDURE_ID || !item.TREATMENT_PROCEDURE_NAME) return null;
|
||||
|
||||
const basePrice = (item.PRICE || 0) + (item.VAT || 0);
|
||||
const discountPrice = item.DISCOUNT_PRICE;
|
||||
|
||||
return {
|
||||
value: item.MU_TREATMENT_PROCEDURE_ID,
|
||||
label: item.TREATMENT_PROCEDURE_NAME,
|
||||
customProperties: {
|
||||
name: item.TREATMENT_PROCEDURE_NAME,
|
||||
price: basePrice,
|
||||
discountPrice: discountPrice,
|
||||
originalData: item
|
||||
}
|
||||
};
|
||||
}).filter(Boolean);
|
||||
|
||||
// Choices.js 초기화
|
||||
procedureChoices = new Choices('#procedure-select', {
|
||||
removeItemButton: true,
|
||||
searchEnabled: true,
|
||||
searchPlaceholderValue: '시술명으로 검색...',
|
||||
placeholder: true,
|
||||
placeholderValue: '시술을 선택하세요',
|
||||
maxItemCount: -1,
|
||||
choices: choices,
|
||||
shouldSort: false,
|
||||
searchResultLimit: 10,
|
||||
searchFields: ['label', 'value'],
|
||||
itemSelectText: '',
|
||||
noChoicesText: '선택 가능한 시술이 없습니다',
|
||||
noResultsText: '검색 결과가 없습니다',
|
||||
loadingText: '시술 정보를 불러오는 중...',
|
||||
|
||||
// 템플릿 커스터마이징 - 선택된 항목에 가격 표시 추가
|
||||
callbackOnCreateTemplates: function(template) {
|
||||
return {
|
||||
// 선택된 항목 템플릿 - 가격 정보 포함
|
||||
item: ({ classNames }, data) => {
|
||||
const customProps = data.customProperties || {};
|
||||
const name = customProps.name || data.label;
|
||||
const price = customProps.price || 0;
|
||||
const discountPrice = customProps.discountPrice;
|
||||
|
||||
// 가격 표시 HTML 생성
|
||||
let priceHtml = '';
|
||||
if (discountPrice && discountPrice < price) {
|
||||
priceHtml = `
|
||||
<span class="selected-price-discount">${discountPrice.toLocaleString()}원</span>
|
||||
<span class="selected-price-original">${price.toLocaleString()}원</span>
|
||||
`;
|
||||
} else {
|
||||
priceHtml = `<span class="selected-price">${price.toLocaleString()}원</span>`;
|
||||
}
|
||||
|
||||
return template(`
|
||||
<div class="${classNames.item} ${data.highlighted ? classNames.highlightedState : classNames.itemSelectable}" data-item data-id="${data.id}" data-value="${data.value}">
|
||||
<span class="selected-item-content">
|
||||
<span class="selected-item-name">${name}</span>
|
||||
<span class="selected-item-price">${priceHtml}</span>
|
||||
</span>
|
||||
<button type="button" class="${classNames.button}" aria-label="Remove item: '${data.value}'" data-button>X</button>
|
||||
|
||||
</div>
|
||||
`);
|
||||
},
|
||||
|
||||
// 드롭다운 선택 옵션 템플릿
|
||||
choice: ({ classNames }, data) => {
|
||||
const customProps = data.customProperties || {};
|
||||
const name = customProps.name || data.label;
|
||||
const price = customProps.price || 0;
|
||||
const discountPrice = customProps.discountPrice;
|
||||
|
||||
let priceHtml = '';
|
||||
if (discountPrice && discountPrice < price) {
|
||||
priceHtml = `
|
||||
<span class="procedure-price-discount">${discountPrice.toLocaleString()}원</span>
|
||||
<span class="procedure-price-original">${price.toLocaleString()}원</span>
|
||||
`;
|
||||
} else {
|
||||
priceHtml = `<span class="procedure-price">${price.toLocaleString()}원</span>`;
|
||||
}
|
||||
|
||||
return template(`
|
||||
<div class="${classNames.item} ${classNames.itemChoice} ${data.disabled ? classNames.itemDisabled : classNames.itemSelectable}" data-select-text="" data-choice ${data.disabled ? 'data-choice-disabled aria-disabled="true"' : 'data-choice-selectable'} data-id="${data.id}" data-value="${data.value}">
|
||||
<div style="display: flex; justify-content: space-between; width: 100%; align-items: center;">
|
||||
<span>${name}</span>
|
||||
${priceHtml}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// 이벤트 리스너 추가
|
||||
const selectElement = document.getElementById('procedure-select');
|
||||
selectElement.addEventListener('change', function(event) {
|
||||
console.log('Selection changed:', event.detail);
|
||||
updateTotalPrice();
|
||||
});
|
||||
|
||||
selectElement.addEventListener('addItem', function(event) {
|
||||
console.log('Item added:', event.detail);
|
||||
updateTotalPrice();
|
||||
});
|
||||
|
||||
selectElement.addEventListener('removeItem', function(event) {
|
||||
console.log('Item removed:', event.detail);
|
||||
updateTotalPrice();
|
||||
});
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 총 금액 업데이트 - 개선된 버전
|
||||
****************************************************************************/
|
||||
function updateTotalPrice() {
|
||||
if (!procedureChoices) {
|
||||
console.log('procedureChoices not initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedValues = procedureChoices.getValue(true);
|
||||
console.log('Selected values:', selectedValues);
|
||||
|
||||
let total = 0;
|
||||
|
||||
selectedValues.forEach(value => {
|
||||
const item = priceList.find(p => p.MU_TREATMENT_PROCEDURE_ID == value);
|
||||
console.log('Found item for value', value, ':', item);
|
||||
|
||||
if (item) {
|
||||
const basePrice = (item.PRICE || 0) + (item.VAT || 0);
|
||||
const discountPrice = item.DISCOUNT_PRICE;
|
||||
|
||||
// 할인가가 있고 더 저렴하면 할인가 사용, 아니면 기본가격 사용
|
||||
const finalPrice = (discountPrice && discountPrice < basePrice) ? discountPrice : basePrice;
|
||||
total += finalPrice;
|
||||
console.log('Added price:', finalPrice);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Total calculated:', total);
|
||||
totalEl.textContent = total.toLocaleString() + '원';
|
||||
reserveBtn.disabled = selectedValues.length === 0;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 예약 페이지로 이동
|
||||
****************************************************************************/
|
||||
function fn_moveReservation(category_div, category_no, post_no) {
|
||||
if (!procedureChoices) {
|
||||
alert('시술 선택 기능이 초기화되지 않았습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedValues = procedureChoices.getValue(true);
|
||||
|
||||
if (selectedValues.length === 0) {
|
||||
alert('시술을 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
const form = document.createElement('form');
|
||||
form.method = 'post';
|
||||
form.action = '/webservice/selectMakeReservation.do';
|
||||
|
||||
// 기본 파라미터 추가
|
||||
const params = [
|
||||
{ name: 'CATEGORY_DIV_CD', value: category_div },
|
||||
{ name: 'CATEGORY_NO', value: category_no },
|
||||
{ name: 'POST_NO', value: post_no }
|
||||
];
|
||||
|
||||
params.forEach(param => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = param.name;
|
||||
input.value = param.value;
|
||||
form.appendChild(input);
|
||||
});
|
||||
|
||||
// 선택된 시술 추가
|
||||
selectedValues.forEach(value => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'PROCEDURE_ID';
|
||||
input.value = value;
|
||||
form.appendChild(input);
|
||||
});
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
// 예약 버튼 이벤트
|
||||
reserveBtn.addEventListener('click', function() {
|
||||
fn_moveReservation(category_div_cd, category_no, post_no);
|
||||
});
|
||||
</script>
|
||||
</th:block>
|
||||
</html>
|
||||
Reference in New Issue
Block a user