2026-02-13 contents bbs 수정

This commit is contained in:
pjs
2026-02-14 08:19:58 +09:00
parent 8ea2293766
commit d50f35c676
16 changed files with 3523 additions and 993 deletions

View File

@@ -0,0 +1,729 @@
<!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="layout_css">
<!-- 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">
const CDN_URL = [[${@environment.getProperty('url.cdn') }]];
</script>
</th:block>
<th:block layout:fragment="layout_content">
<div id="service-header">
시술안내/가격 &nbsp;&gt;&nbsp; <span id="header-category-nm"></span> &nbsp;&gt;&nbsp; <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"></div>
<div class="info-desc" id="contents"></div>
<div class="hashtag-section">
<div class="hashtag-container">
<div class="hashtag-list">
</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="layout_script">
<!-- 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');
// 초기화 - Opener 데이터 로드 (DOM 로드 후 실행)
document.addEventListener('DOMContentLoaded', function () {
fn_initPreview();
});
function fn_initPreview() {
if (!window.opener) {
alert('비정상적인 접근입니다.');
window.close();
return;
}
const opener = window.opener;
const $ = opener.jQuery; // Use jQuery from opener if possible, or use current document
// 1. 기본 정보 설정
document.getElementById('header-category-nm').innerText = opener.$("select[name=category] option:selected").text();
document.getElementById('header-title').innerText = opener.$("#title").val();
document.getElementById('title').innerText = opener.$("#title").val();
document.getElementById('contents').innerText = opener.$("#content").val();
document.getElementById('thumbnail-bottom-txt').innerText = opener.$("#thumbnailBottomTxt").val();
// 2. 이미지 설정
let thumbSrc = opener.$(".img_box img").attr("src");
if (!thumbSrc) thumbSrc = opener.$("#thumbnailImg").attr("src");
if (thumbSrc) document.getElementById('serviceThumb').src = thumbSrc;
let contentSrc = opener.$(".file_box img").attr("src");
if (!contentSrc) contentSrc = opener.$("#contentsImg").attr("src");
if (contentSrc) document.getElementById('contents_path').src = contentSrc;
else document.getElementById('contents_path').style.display = 'none';
// 3. 해시태그 설정
let hashtagStr = opener.$("#hashtag").val();
let hashtagHtml = '';
if (hashtagStr) {
let tags = hashtagStr.split('#');
tags.forEach(function (tag) {
let trimmed = tag.trim();
if (trimmed) {
hashtagHtml += '<span class="hashtag">#' + trimmed + '</span>';
}
});
}
document.querySelector('.hashtag-list').innerHTML = hashtagHtml;
// 4. 시술 목록 및 가격 설정
let treatmentList = opener.getTreatmentListForPreview ? opener.getTreatmentListForPreview() : opener.treatmentList; // Access global variable from opener or fall back
if (treatmentList && treatmentList.length > 0) {
updateProcedureOptions(treatmentList);
}
}
/****************************************************************************
* Choices.js 초기화 및 옵션 업데이트
****************************************************************************/
function updateProcedureOptions(data) {
priceList = data;
let minPrice = -1;
// 선택 옵션 데이터 생성
const choices = data.map(item => {
const price = parseInt(item.price) || 0;
const discountPrice = parseInt(item.discountPrice) || 0;
const finalPrice = (discountPrice > 0 && discountPrice < price) ? discountPrice : price;
if (minPrice === -1 || finalPrice < minPrice) {
minPrice = finalPrice;
}
return {
value: item.muTreatmentProcedureId || item.muTreatmentId, // Adjust based on your object structure
label: item.treatmentProcedureName,
customProperties: {
name: item.treatmentProcedureName,
price: price,
discountPrice: discountPrice
}
};
}).filter(Boolean);
if (minPrice === -1) minPrice = 0;
document.getElementById('price').textContent = minPrice.toLocaleString();
// 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;
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', updateTotalPrice);
selectElement.addEventListener('addItem', updateTotalPrice);
selectElement.addEventListener('removeItem', updateTotalPrice);
}
function updateTotalPrice() {
if (!procedureChoices) return;
const selectedValues = procedureChoices.getValue(true);
let total = 0;
selectedValues.forEach(value => {
// Find item in local priceList (which is data passed to updateProcedureOptions)
// Note: priceList elements structure depends on how we mapped it.
// Oh wait, priceList = data; which comes from opener.treatmentList.
// Items in treatmentList have { muTreatmentProcedureId, treatmentProcedureName, price, discountPrice ... }
const item = priceList.find(p => (p.muTreatmentProcedureId || p.muTreatmentId) == value);
if (item) {
const basePrice = parseInt(item.price) || 0;
const discountPrice = parseInt(item.discountPrice) || 0;
const finalPrice = (discountPrice > 0 && discountPrice < basePrice) ? discountPrice : basePrice;
total += finalPrice;
}
});
totalEl.textContent = total.toLocaleString() + '원';
reserveBtn.disabled = selectedValues.length === 0;
}
</script>
</th:block>
</html>

View File

@@ -1,116 +1,178 @@
<!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/homeLayout}">
<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/homeLayout}">
<th:block layout:fragment="layout_css">
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.css" rel="stylesheet">
<link rel="stylesheet" href="/css/web/ContentsBbsReg.css">
<link rel="stylesheet" href="/css/web/grid.css?v1.1">
<link rel="stylesheet" href="/css/web/ContentsBbsUpd.css">
<link rel="stylesheet" href="/css/web/grid.css?v1.1">
</th:block>
<th:block layout:fragment="layout_top_script">
<script src="/js/web/jquery.twbsPagination.js" type="text/javascript"></script>
<script>
let menuClass = "[[${param.menuClass}]]"==""?"":"[[${param.menuClass}]]";
let categoryDivCd = "[[${param.categoryDivCd}]]"==""?"":"[[${param.categoryDivCd}]]";
<!-- Pagination usage removed -->
<script>
let menuClass = "[[${param.menuClass}]]" == "" ? "" : "[[${param.menuClass}]]";
let categoryDivCd = "[[${param.categoryDivCd}]]" == "" ? "" : "[[${param.categoryDivCd}]]";
let selectUseYn = "[[${selectUseYn}]]"==""?"N":"[[${selectUseYn}]]";
let insertUseYn = "[[${insertUseYn}]]"==""?"N":"[[${insertUseYn}]]";
let updateUseYn = "[[${updateUseYn}]]"==""?"N":"[[${updateUseYn}]]";
let deleteUseYn = "[[${deleteUseYn}]]"==""?"N":"[[${deleteUseYn}]]";
let downloadUseYn = "[[${downloadUseYn}]]"==""?"N":"[[${downloadUseYn}]]";
let categorytitle = "[[${title}]]";
</script>
let selectUseYn = "[[${selectUseYn}]]" == "" ? "N" : "[[${selectUseYn}]]";
let insertUseYn = "[[${insertUseYn}]]" == "" ? "N" : "[[${insertUseYn}]]";
let updateUseYn = "[[${updateUseYn}]]" == "" ? "N" : "[[${updateUseYn}]]";
let deleteUseYn = "[[${deleteUseYn}]]" == "" ? "N" : "[[${deleteUseYn}]]";
let downloadUseYn = "[[${downloadUseYn}]]" == "" ? "N" : "[[${downloadUseYn}]]";
let categorytitle = "[[${title}]]";
const CDN_URL = "[[${@environment.getProperty('url.cdn')}]]";
</script>
</th:block>
<th:block layout:fragment="layout_content">
<!-- 센터쪽 -->
<div class="center_box">
<p class="page_title">[[${title}]]</p>
<!-- 센터쪽 -->
<div class="center_box">
<p class="page_title">[[${title}]]</p>
<!-- 테이블 -->
<div class="content_box">
<div class="content clear">
<div class="wp60">
<div class="top">
<label>썸네일 첨부파일</label>
<label for="file" class="file_btn"><img src="/image/web/add.svg" alt="파일찾기"></label>
</div>
<div class="btm">
<div class="img_box"><!-- img 미리보기 --></div>
<input type="file" id="file" accept="image/jpeg, image/jpg, image/png" style="display: none;" multiple>
<button id="delete_btn">삭제</button>
</div>
<p class="thumbnail-bottom-txt">
썸네일 하단글
</p>
<textarea id="thumbnail-bottom-txt" placeholder="썸네일 하단글을 입력해주세요." ></textarea>
<p class="content-file">
컨텐츠 첨부파일
<label for="content_file" class="file_btn"><img src="/image/web/add.svg" alt="파일찾기"></label>
</p>
<input type="file" id="content_file" style="display: none;" placeholder="첨부파일을 입력해주세요."/>
<div class="file_box"><!-- img 미리보기 --></div>
<button id="content_delete_btn">삭제</button>
</div>
<div class="wp40">
<div class="consultation-info">
<p id="main_category">
카테고리
</p>
<select th:name="categorylist">
<option value="">선택하세요</option>
<option th:each="category : ${categorylist}" th:value="${category.categoryNo}" th:text="${category.categoryNm}"></option>
</select>
<p id="main_title">
제목
</p>
<input type="text" id="title" placeholder="제목을 입력해주세요."/>
<p id="main_content">
내용
</p>
<textarea id="content" placeholder="내용을 입력해주세요."></textarea>
<p id="main_hashtag">
해쉬태그
</p>
<input type="text" id="hashtag" placeholder="해쉬태그를 입력해주세요."/>
<div>
<label for="oldCrmItemId">OLD CRM 연동ID</label>
<input type="text" id="oldCrmItemId" placeholder="OLD_CRM_ITEM_ID"/>
<!-- 테이블 -->
<div class="content_box">
<div class="update-container">
<!-- Left Panel: Data Forms -->
<div class="left-panel">
<!-- Row 1: Category & Title -->
<div class="form-grid-row">
<div class="form-group" style="flex: 0 0 150px;">
<label>카테고리</label>
<select name="category">
<option value="">선택하세요</option>
<option th:each="category : ${categorylist}" th:value="${category.categoryNo}"
th:text="${category.categoryNm}"></option>
</select>
<input type="hidden" id="categoryNo" />
</div>
<div>
<label for="ordNo">홈페이지 출력순서</label>
<div class="form-group">
<label>제목</label>
<input type="text" id="title" placeholder="제목을 입력해주세요." />
</div>
</div>
<!-- Row 2: Content & Order -->
<div class="form-grid-row">
<div class="form-group" style="flex: 1;">
<label>내용</label>
<input type="text" id="content" placeholder="내용을 입력해주세요." />
</div>
<div class="form-group" style="flex: 0 0 150px;">
<label>홈페이지 출력순서</label>
<input type="text" id="ordNo" />
</div>
<p id="main_procedure">
시술선택
<button class="add_btn ml50" onclick="javascript:listOpen();">
<img src="/image/web/add.svg" alt="추가">
</button>
<button class="add_btn" onclick="javascript:fn_removeRow();">
<img src="/image/web/subtract.svg" alt="삭제">
</button>
</p>
<div id="treatmentlist">
</div>
</div>
<div class="button_box">
<button class="registration_btn btnSave">등록</button>
<button class="cancel_btn btnCancle">취소</button>
</div>
<!-- Row 3: Hashtag & Old ID -->
<div class="form-grid-row">
<div class="form-group">
<label>해쉬태그</label>
<input type="text" id="hashtag" placeholder="해쉬태그를 입력해주세요." />
</div>
<div class="form-group">
<label>OLD CRM 연동ID</label>
<input type="text" id="oldCrmItemId" placeholder="OLD_CRM_ITEM_ID" />
</div>
</div>
<!-- Row 4: Procedure Selection (Grid) -->
<div class="grid-section">
<div class="grid-header">
<label>시술선택</label>
<div class="grid-controls">
<button class="add_btn" onclick="javascript:listOpen();">
<img src="/image/web/add.svg" alt="추가">
</button>
<button class="add_btn" onclick="javascript:fn_removeRow();">
<img src="/image/web/subtract.svg" alt="삭제">
</button>
</div>
</div>
<div class="custom-grid-container">
<table class="treatment-table">
<colgroup>
<col style="width: 50px;">
<col style="width: auto;">
<col style="width: 120px;">
<col style="width: 120px;">
</colgroup>
<thead>
<tr>
<th>
<input type="checkbox" id="checkAll" onclick="fn_checkAll(this)">
</th>
<th>시술명</th>
<th>가격</th>
<th>할인가</th>
</tr>
</thead>
<tbody id="treatmentListBody">
<!-- Rows will be added here dynamically -->
</tbody>
</table>
</div>
</div>
<!-- Bottom Buttons -->
<div class="bottom-actions">
<button class="btn-basic btnPreview" onclick="fn_openPreview();"
style="width: 120px; height: 36px; border-radius: 4px; background: #fff; color: #333; border: 1px solid #ccc; margin-right: auto;">미리보기</button>
<button class="registration_btn btnSave"
style="width: 80px; height: 36px; border-radius: 4px; background: #3985EA; color: white;">등록</button>
<button class="cancel_btn btnCancle"
style="width: 80px; height: 36px; border-radius: 4px;">취소</button>
</div>
</div>
<!-- Right Panel: Images -->
<div class="right-panel">
<!-- Thumbnail Section -->
<div class="panel-section">
<div class="top-label">
<span>썸네일 첨부파일</span>
<div>
<label for="file" class="file_btn" style="cursor: pointer;"><img
src="/image/web/add.svg" alt="파일찾기"></label>
<button id="delete_btn"
style="border:none; background:none; cursor:pointer; margin-left:5px; font-size:12px; color:#999;">삭제</button>
</div>
</div>
<div class="img-preview-wrapper img_box">
<img id="thumbnailImg" src="" />
</div>
<input type="file" id="file" accept="image/jpeg, image/jpg, image/png" style="display: none;"
multiple>
<label style="margin-top: 10px; font-size: 13px; font-weight: 700;">썸네일 하단글</label>
<textarea id="thumbnailBottomTxt" placeholder="썸네일 하단글을 입력해주세요."></textarea>
</div>
<!-- Content File Section -->
<div class="panel-section">
<div class="top-label">
<span>컨텐츠 첨부파일</span>
<div>
<label for="content_file" class="file_btn" style="cursor: pointer;"><img
src="/image/web/add.svg" alt="파일찾기"></label>
<button id="content_delete_btn"
style="border:none; background:none; cursor:pointer; margin-left:5px; font-size:12px; color:#999;">삭제</button>
</div>
</div>
<div class="img-preview-wrapper file_box">
<!-- Using same class for consistent styling -->
<img id="contentsImg" src="" />
</div>
<input type="file" id="content_file" style="display: none;" placeholder="첨부파일을 입력해주세요." />
</div>
</div>
</div>
</div>
</div>
<form id="excelForm" method="POST" target="_blank"></form>
</div>
</div>
<form id="excelForm" method="POST" target="_blank"></form>
</th:block>
<th:block layout:fragment="layout_popup">
</th:block>
<th:block layout:fragment="layout_script">
<script src="/js/web/ag-grid-community-29.3.5.min.js"></script>
<script src="/js/web/contentsBbs/ContentsBbsReg.js"></script>
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.js"></script>
<script src="/js/web/contentsBbs/ContentsBbsPop.js"></script>
<script src="/js/web/contentsBbs/ContentsBbsReg.js"></script>
<script src="/js/web/contentsBbs/ContentsBbsPop.js"></script>
</th:block>
</html>

View File

@@ -1,128 +1,183 @@
<!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/homeLayout}">
<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/homeLayout}">
<th:block layout:fragment="layout_css">
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.css" rel="stylesheet">
<link rel="stylesheet" href="/css/web/ContentsBbsReg.css">
<link rel="stylesheet" href="/css/web/grid.css?v1.1">
<link rel="stylesheet" href="/css/web/ContentsBbsUpd.css">
<link rel="stylesheet" href="/css/web/grid.css?v1.1">
</th:block>
<th:block layout:fragment="layout_top_script">
<script src="/js/web/jquery.twbsPagination.js" type="text/javascript"></script>
<script>
let menuClass = "[[${param.menuClass}]]"==""?"":"[[${param.menuClass}]]";
<!-- Pagination usage removed -->
<script>
let menuClass = "[[${param.menuClass}]]" == "" ? "" : "[[${param.menuClass}]]";
let selectUseYn = "[[${selectUseYn}]]"==""?"N":"[[${selectUseYn}]]";
let insertUseYn = "[[${insertUseYn}]]"==""?"N":"[[${insertUseYn}]]";
let updateUseYn = "[[${updateUseYn}]]"==""?"N":"[[${updateUseYn}]]";
let deleteUseYn = "[[${deleteUseYn}]]"==""?"N":"[[${deleteUseYn}]]";
let downloadUseYn = "[[${downloadUseYn}]]"==""?"N":"[[${downloadUseYn}]]";
let categoryDivCd = "[[${param.categoryDivCd}]]";
let categoryNo = "[[${param.categoryNo}]]";
let postNo = "[[${param.postNo}]]";
let categorytitle = "[[${title}]]";
const CDN_URL = "[[${@environment.getProperty('url.cdn')}]]";
</script>
let selectUseYn = "[[${selectUseYn}]]" == "" ? "N" : "[[${selectUseYn}]]";
let insertUseYn = "[[${insertUseYn}]]" == "" ? "N" : "[[${insertUseYn}]]";
let updateUseYn = "[[${updateUseYn}]]" == "" ? "N" : "[[${updateUseYn}]]";
let deleteUseYn = "[[${deleteUseYn}]]" == "" ? "N" : "[[${deleteUseYn}]]";
let downloadUseYn = "[[${downloadUseYn}]]" == "" ? "N" : "[[${downloadUseYn}]]";
let categoryDivCd = "[[${param.categoryDivCd}]]";
let categoryNo = "[[${param.categoryNo}]]";
let postNo = "[[${param.postNo}]]";
let categorytitle = "[[${title}]]";
const CDN_URL = "[[${@environment.getProperty('url.cdn')}]]";
</script>
</th:block>
<th:block layout:fragment="layout_content">
<!-- 센터쪽 -->
<div class="center_box">
<p class="page_title">[[${title}]]</p>
<!-- 센터쪽 -->
<div class="center_box">
<p class="page_title">[[${title}]]</p>
<!-- 테이블 -->
<div class="content_box">
<div class="content clear">
<div class="wp60">
<div class="top">
<label>썸네일 첨부파일</label>
<label for="file" class="file_btn"><img src="/image/web/add.svg" alt="파일찾기"></label>
</div>
<div class="btm">
<div class="img_box">
<img id="thumbnailImg" src="" width="800px" height="450px" />
</div>
<input type="file" id="file" accept="image/jpeg, image/jpg, image/png" style="display: none;" multiple>
<button id="delete_btn">삭제</button>
</div>
<p class="thumbnail-bottom-txt">
썸네일 하단글
</p>
<textarea id="thumbnailBottomTxt" placeholder="썸네일 하단글을 입력해주세요." ></textarea>
<p class="content-file">
컨텐츠 첨부파일
<label for="content_file" class="file_btn"><img src="/image/web/add.svg" alt="파일찾기"></label>
</p>
<input type="file" id="content_file" style="display: none;" placeholder="첨부파일을 입력해주세요."/>
<div class="file_box">
<img id="contentsImg" src="" width="100%" height="100%" />
</div>
<button id="content_delete_btn">삭제</button>
</div>
<div class="wp40">
<div class="consultation-info">
<p id="main_category">
카테고리
</p>
<select th:name="category" th:disabled="true">
<option value="">선택하세요</option>
<option th:each="item : ${category}" th:value="${item['categoryNo']}" th:text="${item['categoryNm']}" ></option>
</select>
<!-- disabled된 select의 값을 전송하기 위한 hidden input -->
<input type="hidden" id="categoryNo" />
<p id="main_title">
제목
</p>
<input type="text" id="title" placeholder="제목을 입력해주세요."/>
<p id="main_content">
내용
</p>
<textarea id="content" placeholder="내용을 입력해주세요."></textarea>
<p id="main_hashtag">
해쉬태그
</p>
<input type="text" id="hashtag" placeholder="해쉬태그를 입력해주세요."/>
<div>
<label for="oldCrmItemId">OLD CRM 연동ID</label>
<input type="text" id="oldCrmItemId" placeholder="OLD_CRM_ITEM_ID"/>
<!-- 테이블 -->
<div class="content_box">
<div class="update-container">
<!-- Left Panel: Data Forms -->
<div class="left-panel">
<!-- Row 1: Category & Title -->
<div class="form-grid-row">
<div class="form-group" style="flex: 0 0 150px;">
<label>카테고리</label>
<select th:name="category" th:disabled="true">
<option value="">선택하세요</option>
<option th:each="item : ${category}" th:value="${item['categoryNo']}"
th:text="${item['categoryNm']}"></option>
</select>
<input type="hidden" id="categoryNo" />
</div>
<div>
<label for="ordNo">홈페이지 출력순서</label>
<div class="form-group">
<label>제목</label>
<input type="text" id="title" placeholder="제목을 입력해주세요." />
</div>
</div>
<!-- Row 2: Content & Order -->
<div class="form-grid-row">
<div class="form-group" style="flex: 1;">
<label>내용</label>
<input type="text" id="content" placeholder="내용을 입력해주세요." />
</div>
<div class="form-group" style="flex: 0 0 70px;">
<label>출력순서</label>
<input type="text" id="ordNo" />
</div>
<p id="main_procedure">
시술선택
<button class="add_btn ml50" onclick="javascript:listOpen();">
<img src="/image/web/add.svg" alt="추가">
</button>
<button class="add_btn" onclick="javascript:fn_removeRow();">
<img src="/image/web/subtract.svg" alt="삭제">
</button>
</p>
<div id="treatmentlist">
</div>
</div>
<div class="button_box">
<button class="registration_btn btnSave">수정</button>
<button class="cancel_btn btnCancle">취소</button>
</div>
<!-- Row 3: Hashtag & Old ID -->
<div class="form-grid-row">
<div class="form-group">
<label>해쉬태그</label>
<input type="text" id="hashtag" placeholder="해쉬태그를 입력해주세요." />
</div>
<div class="form-group">
<label>OLD CRM 연동ID</label>
<input type="text" id="oldCrmItemId" placeholder="OLD_CRM_ITEM_ID" />
</div>
</div>
<!-- Row 4: Treatment Grid -->
<div class="grid-section">
<div class="grid-header">
<p>시술선택</p>
<div>
<button class="add_btn" onclick="javascript:listOpen();"
style="border:none; background:none; cursor:pointer;">
<img src="/image/web/add.svg" alt="추가">
</button>
<button class="add_btn" onclick="javascript:fn_removeRow();"
style="border:none; background:none; cursor:pointer; margin-left: 5px;">
<img src="/image/web/subtract.svg" alt="삭제">
</button>
</div>
</div>
<div class="custom-grid-container">
<table class="treatment-table">
<colgroup>
<col style="width: 50px;">
<col style="width: 250px;">
<col style="width: 120px;">
<col style="width: 120px;">
</colgroup>
<thead>
<tr>
<th>
<input type="checkbox" id="checkAll" onclick="fn_checkAll(this)">
</th>
<th>시술명</th>
<th>가격</th>
<th>할인가</th>
</tr>
</thead>
<tbody id="treatmentListBody">
<!-- Rows will be added here dynamically -->
</tbody>
</table>
</div>
</div>
<!-- Bottom Buttons -->
<div class="bottom-actions">
<button class="btn-basic btnPreview" onclick="fn_openPreview();"
style="width: 120px; height: 36px; border-radius: 4px; background: #fff; color: #333; border: 1px solid #ccc; margin-right: auto;">미리보기</button>
<button class="registration_btn btnSave"
style="width: 80px; height: 36px; border-radius: 4px; background: #3985EA; color: white;">수정</button>
<button class="cancel_btn btnCancle"
style="width: 80px; height: 36px; border-radius: 4px;">취소</button>
</div>
</div>
<!-- Right Panel: Images -->
<div class="right-panel">
<!-- Thumbnail Section -->
<div class="panel-section">
<div class="top-label">
<span>썸네일 첨부파일</span>
<div>
<label for="file" class="file_btn" style="cursor: pointer;"><img
src="/image/web/add.svg" alt="파일찾기"></label>
<button id="delete_btn"
style="border:none; background:none; cursor:pointer; margin-left:5px; font-size:12px; color:#999;">삭제</button>
</div>
</div>
<div class="img-preview-wrapper img_box">
<img id="thumbnailImg" src="" />
</div>
<input type="file" id="file" accept="image/jpeg, image/jpg, image/png" style="display: none;"
multiple>
<label style="margin-top: 10px; font-size: 13px; font-weight: 700;">썸네일 하단글</label>
<textarea id="thumbnailBottomTxt" placeholder="썸네일 하단글을 입력해주세요."></textarea>
</div>
<!-- Content File Section -->
<div class="panel-section">
<div class="top-label">
<span>컨텐츠 첨부파일</span>
<div>
<label for="content_file" class="file_btn" style="cursor: pointer;"><img
src="/image/web/add.svg" alt="파일찾기"></label>
<button id="content_delete_btn"
style="border:none; background:none; cursor:pointer; margin-left:5px; font-size:12px; color:#999;">삭제</button>
</div>
</div>
<div class="img-preview-wrapper file_box">
<!-- Using same class for consistent styling -->
<img id="contentsImg" src="" />
</div>
<input type="file" id="content_file" style="display: none;" placeholder="첨부파일을 입력해주세요." />
</div>
</div>
</div>
</div>
</div>
<form id="excelForm" method="POST" target="_blank"></form>
</div>
</div>
<form id="excelForm" method="POST" target="_blank"></form>
</th:block>
<th:block layout:fragment="layout_popup">
</th:block>
<th:block layout:fragment="layout_script">
<script src="/js/web/ag-grid-community-29.3.5.min.js"></script>
<script src="/js/web/contentsBbs/ContentsBbsUpd.js"></script>
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.js"></script>
<script src="/js/web/contentsBbs/ContentsBbsPop.js"></script>
<script src="/js/web/contentsBbs/ContentsBbsUpd.js"></script>
<script src="/js/web/contentsBbs/ContentsBbsPop.js"></script>
</th:block>
</html>