feat(cafe24): 옵션/품목 편집을 별도 모달 창으로

380px 정보 패널은 옵션값·품목 표를 담기에 좁다. 패널에는 「팝업에서 관리」
버튼만 두고, 최대 1180px 모달(왼쪽 옵션 정의 / 오른쪽 품목 표: 이미지·옵션·
품목코드·자체코드·추가금액·진열·판매)에서 편집한다.
- 모달은 페이지에 하나(패널 조각 교체와 무관), 열 때마다 카페24에서 새로 읽음.
- 닫기: X · 배경 클릭 · Esc. 저장 안 한 변경(.is-dirty)이 있으면 확인창.
- 다른 상품을 고르면 열려 있던 창은 닫힘.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-18 18:31:59 +09:00
parent 18015418d9
commit e838e8ee15
4 changed files with 242 additions and 86 deletions
+10 -6
View File
@@ -80,13 +80,17 @@
</form> </form>
</section> </section>
{# ── 옵션 / 품목 — 펼칠 때 JSON 으로 불러온다(카페24 호출 2회) ── #} {# ── 옵션 / 품목 — 패널이 좁아 별도 모달(products.html 의 #cf24-options-modal)에서
<details class="cf24-side-sec cf24-side-details" id="cf24-options-details"> 편집한다. 버튼을 누를 때 JSON 으로 불러온다(카페24 호출 2회). ── #}
<summary class="cf24-side-title">옵션 / 품목 <span class="cf24-muted" id="cf24-options-count"></span></summary> <section class="cf24-side-sec">
<div class="cf24-options-body" id="cf24-options-body"> <div class="cf24-side-actions">
<p class="cf24-muted">불러오는 중…</p> <h4 class="cf24-side-title" style="margin:0;">옵션 / 품목 <span class="cf24-muted" id="cf24-options-count"></span></h4>
<button type="button" class="erp-btn erp-btn-primary" id="cf24-options-open">팝업에서 관리</button>
</div> </div>
</details> <p class="cf24-side-note" style="margin:8px 0 0;">
옵션명·옵션값 이름/썸네일·표시방식, 품목별 자체코드·추가금액·진열/판매를 넓은 창에서 수정합니다.
</p>
</section>
</div> </div>
{% else %} {% else %}
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %} {% extends "erp_base.html" %}
{% block head_extra %} {% block head_extra %}
<link rel="stylesheet" href="/static/cafe24.css?v=20260918b" /> <link rel="stylesheet" href="/static/cafe24.css?v=20260918c" />
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@@ -109,6 +109,19 @@
{% endif %} {% endif %}
</section> </section>
</div> </div>
{# ── 옵션/품목 편집 모달 — 오른쪽 패널(380px)은 품목 표를 담기에 좁아 별도 창에서
편집한다. 페이지에 하나만 두고(패널 조각 교체와 무관), 열 때마다 카페24에서 새로
읽는다. 내용은 products.html 의 JS(setupOptions → render) 가 그린다. #}
<div class="cf24-modal" id="cf24-options-modal" hidden aria-hidden="true">
<div class="cf24-modal-box" role="dialog" aria-modal="true" aria-labelledby="cf24-options-title">
<div class="cf24-modal-head">
<h3 class="cf24-modal-title">옵션 / 품목 — <span id="cf24-options-title"></span></h3>
<button type="button" class="cf24-modal-close" id="cf24-options-close" aria-label="닫기"></button>
</div>
<div class="cf24-modal-body cf24-options-body" id="cf24-options-body"></div>
</div>
</div>
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
@@ -725,6 +738,46 @@
}); });
} }
// ── 옵션/품목 모달 — 페이지에 하나. 열기/닫기만 여기서, 내용은 setupOptions 가 그린다.
var optModal = (function () {
var el = document.getElementById("cf24-options-modal");
var body = el ? el.querySelector("#cf24-options-body") : null;
var title = el ? el.querySelector("#cf24-options-title") : null;
var api = {
el: el, body: body,
isOpen: function () { return !!el && !el.hidden; },
open: function (name) {
if (!el) return;
if (title) title.textContent = name || "";
el.hidden = false;
el.setAttribute("aria-hidden", "false");
document.body.classList.add("cf24-modal-open");
},
close: function (force) {
if (!el || el.hidden) return Promise.resolve(true);
var dirty = !force && el.querySelector(".is-dirty");
var ask = dirty ? window.erpConfirm("저장하지 않은 변경이 있습니다. 창을 닫을까요?") : Promise.resolve(true);
return ask.then(function (ok) {
if (!ok) return false;
el.hidden = true;
el.setAttribute("aria-hidden", "true");
document.body.classList.remove("cf24-modal-open");
if (body) body.innerHTML = "";
return true;
});
}
};
if (el) {
el.querySelector("#cf24-options-close").addEventListener("click", function () { api.close(false); });
// 어두운 배경 클릭 = 닫기 (창 안 클릭은 무시)
el.addEventListener("click", function (e) { if (e.target === el) api.close(false); });
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && api.isOpen()) api.close(false);
});
}
return api;
})();
window.cf24BindSide = function () { window.cf24BindSide = function () {
var sideRoot = document.getElementById("cf24-side-pane"); var sideRoot = document.getElementById("cf24-side-pane");
var root = sideRoot && sideRoot.querySelector("#cf24-side"); var root = sideRoot && sideRoot.querySelector("#cf24-side");
@@ -860,21 +913,22 @@
}); });
})(); })();
// ── 옵션 / 품목 — 펼칠 때 불러온다 ── // ── 옵션 / 품목 — 「팝업에서 관리」 버튼으로 모달을 열고 그때 불러온다 ──
(function setupOptions() { (function setupOptions() {
var details = root.querySelector("#cf24-options-details"); var openBtn = root.querySelector("#cf24-options-open");
var body = root.querySelector("#cf24-options-body"); var body = optModal.body;
var count = root.querySelector("#cf24-options-count"); var count = root.querySelector("#cf24-options-count");
if (!details || !body) return; if (!openBtn || !body) return;
var loaded = false;
var state = { option: null, variants: [], displayTypes: {} }; var state = { option: null, variants: [], displayTypes: {} };
details.addEventListener("toggle", function () { openBtn.addEventListener("click", function () {
if (details.open && !loaded) { loaded = true; load(); } var nameEl = document.getElementById("cf24-name-text");
optModal.open((nameEl ? nameEl.textContent : "") + " (상품번호 " + no + ")");
load();
}); });
function load() { function load() {
body.innerHTML = '<p class="cf24-muted">불러오는 중…</p>'; body.innerHTML = '<p class="cf24-muted">카페24에서 불러오는 중…</p>';
apiJson("GET", "/cafe24/products/" + no + "/options") apiJson("GET", "/cafe24/products/" + no + "/options")
.then(function (data) { .then(function (data) {
state.option = data.option; state.option = data.option;
@@ -883,10 +937,9 @@
render(); render();
}) })
.catch(function (err) { .catch(function (err) {
loaded = false;
body.innerHTML = '<p class="cf24-err">옵션을 불러오지 못했습니다: ' + escHtml(err.message) + body.innerHTML = '<p class="cf24-err">옵션을 불러오지 못했습니다: ' + escHtml(err.message) +
'</p><button type="button" class="erp-btn erp-btn-outline" id="cf24-opt-retry">다시 시도</button>'; '</p><button type="button" class="erp-btn erp-btn-outline" id="cf24-opt-retry">다시 시도</button>';
body.querySelector("#cf24-opt-retry").addEventListener("click", function () { loaded = true; load(); }); body.querySelector("#cf24-opt-retry").addEventListener("click", load);
}); });
} }
@@ -953,8 +1006,10 @@
return ""; return "";
} }
// 옵션 정의 편집 // 옵션 정의 편집 (모달 왼쪽 열)
var html = '<form class="cf24-side-form" id="cf24-opt-form">'; var html = '<div class="cf24-modal-grid"><section class="cf24-modal-col">' +
'<h4 class="cf24-side-title">옵션 정의</h4>' +
'<form class="cf24-side-form" id="cf24-opt-form">';
opt.options.forEach(function (o, gi) { opt.options.forEach(function (o, gi) {
html += '<div class="cf24-opt-group" data-group="' + gi + '">' + html += '<div class="cf24-opt-group" data-group="' + gi + '">' +
'<div class="cf24-opt-head">' + '<div class="cf24-opt-head">' +
@@ -981,35 +1036,37 @@
html += '<p class="cf24-side-note">옵션값 추가·삭제는 카페24 API 가 지원하지 않습니다(옵션 삭제 후 다시 만들어야 합니다). 썸네일은 표시방식이 「미리보기(이미지)」일 때 쇼핑몰에 보입니다.</p>' + html += '<p class="cf24-side-note">옵션값 추가·삭제는 카페24 API 가 지원하지 않습니다(옵션 삭제 후 다시 만들어야 합니다). 썸네일은 표시방식이 「미리보기(이미지)」일 때 쇼핑몰에 보입니다.</p>' +
'<div class="cf24-side-actions"><span class="cf24-muted" id="cf24-opt-msg"></span>' + '<div class="cf24-side-actions"><span class="cf24-muted" id="cf24-opt-msg"></span>' +
' <button class="erp-btn erp-btn-primary" type="submit">옵션 저장</button></div>' + ' <button class="erp-btn erp-btn-primary" type="submit">옵션 저장</button></div>' +
"</form>"; "</form>" +
'<div class="cf24-opt-danger"><button type="button" class="erp-btn erp-btn-outline" id="cf24-opt-delete">옵션 전체 삭제</button></div>' +
"</section>";
// 품목 — 패널 폭(380px)에 표는 좁다(실측: 이름이 이미지 아래로 꺾여 잘림). // 품목 표 (모달 오른쪽 열) — 넓은 창이라 한 줄에 다 들어간다.
// 품목마다 2줄 카드: 1줄 아이콘 + 옵션값 이름, 2줄 자체코드·추가금액·진열·판매. html += '<section class="cf24-modal-col"><h4 class="cf24-side-title">품목 (' + variants.length + ')</h4>';
html += '<div class="cf24-opt-group"><h5 class="cf24-side-title">품목 (' + variants.length + ')</h5>';
if (!variants.length) { if (!variants.length) {
html += '<p class="cf24-muted">품목이 아직 조회되지 않았습니다. 잠시 뒤 「옵션 / 품목」을 접었다 펴서 다시 불러오세요.</p>'; html += '<p class="cf24-muted">품목이 아직 조회되지 않았습니다. 잠시 뒤 창을 닫았다 다시 여세요.</p>';
} else { } else {
html += '<div class="cf24-variants-wrap">'; html += '<div class="cf24-variants-wrap"><table class="cf24-variants"><thead><tr>' +
'<th class="cf24-v-imgcol"></th><th>옵션</th><th class="cf24-v-syscode">품목코드</th>' +
'<th class="cf24-v-code">자체 품목코드</th><th class="cf24-v-amount">추가금액</th>' +
'<th class="cf24-v-flag">진열</th><th class="cf24-v-flag">판매</th></tr></thead><tbody>';
variants.forEach(function (v) { variants.forEach(function (v) {
var label = v.options.map(function (o) { return o.value; }).join(" / ") || v.variant_code; var label = v.options.map(function (o) { return o.value; }).join(" / ") || v.variant_code;
var amount = String(parseInt(v.additional_amount || "0", 10) || 0); var amount = String(parseInt(v.additional_amount || "0", 10) || 0);
html += '<div class="cf24-v-item" data-code="' + escHtml(v.variant_code) + '">' + html += '<tr class="cf24-v-item" data-code="' + escHtml(v.variant_code) + '">' +
'<div class="cf24-v-head" title="' + escHtml(v.variant_code) + '">' + '<td class="cf24-v-imgcol">' + (v.image ? '<img class="cf24-v-img" src="' + escHtml(v.image) + '" alt="" />' : '<span class="cf24-v-img cf24-v-img-empty"></span>') + "</td>" +
(v.image ? '<img class="cf24-v-img" src="' + escHtml(v.image) + '" alt="" />' : '<span class="cf24-v-img cf24-v-img-empty"></span>') + '<td class="cf24-v-name">' + escHtml(label) + "</td>" +
'<span class="cf24-v-name">' + escHtml(label) + "</span></div>" + '<td class="cf24-v-syscode"><code>' + escHtml(v.variant_code) + "</code></td>" +
'<div class="cf24-v-row">' + '<td class="cf24-v-code"><input type="text" name="custom_variant_code" maxlength="40" value="' + escHtml(v.custom_variant_code) + '" /></td>' +
'<label class="cf24-v-code"><span>자체코드</span><input type="text" name="custom_variant_code" maxlength="40" value="' + escHtml(v.custom_variant_code) + '" /></label>' + '<td class="cf24-v-amount"><input type="text" name="additional_amount" inputmode="numeric" value="' + escHtml(amount) + '" /></td>' +
'<label class="cf24-v-amount"><span>추가금액</span><input type="text" name="additional_amount" inputmode="numeric" value="' + escHtml(amount) + '" /></label>' + '<td class="cf24-v-flag"><button type="button" class="cf24-v-toggle' + (v.display ? " is-on" : "") + '" data-flag="display" data-on="' + (v.display ? 1 : 0) + '" data-initial="' + (v.display ? 1 : 0) + '">' + (v.display ? "진열" : "미진열") + "</button></td>" +
'<span class="cf24-v-flags">' + '<td class="cf24-v-flag"><button type="button" class="cf24-v-toggle' + (v.selling ? " is-on" : "") + '" data-flag="selling" data-on="' + (v.selling ? 1 : 0) + '" data-initial="' + (v.selling ? 1 : 0) + '">' + (v.selling ? "판매" : "중지") + "</button></td>" +
'<button type="button" class="cf24-v-toggle' + (v.display ? " is-on" : "") + '" data-flag="display" data-on="' + (v.display ? 1 : 0) + '" data-initial="' + (v.display ? 1 : 0) + '">' + (v.display ? "진열" : "미진열") + "</button>" + "</tr>";
'<button type="button" class="cf24-v-toggle' + (v.selling ? " is-on" : "") + '" data-flag="selling" data-on="' + (v.selling ? 1 : 0) + '" data-initial="' + (v.selling ? 1 : 0) + '">' + (v.selling ? "판매" : "중지") + "</button>" +
"</span></div></div>";
}); });
html += "</div>" + html += "</tbody></table></div>" +
'<div class="cf24-side-actions" style="margin-top:8px;"><span class="cf24-muted" id="cf24-var-msg"></span>' + '<div class="cf24-side-actions" style="margin-top:8px;"><span class="cf24-muted" id="cf24-var-msg"></span>' +
' <button type="button" class="erp-btn erp-btn-primary" id="cf24-var-save">품목 저장</button></div>'; ' <button type="button" class="erp-btn erp-btn-primary" id="cf24-var-save">품목 저장</button></div>';
} }
html += '<div class="cf24-opt-danger"><button type="button" class="erp-btn erp-btn-outline" id="cf24-opt-delete">옵션 전체 삭제</button></div></div>'; html += "</section></div>";
body.innerHTML = html; body.innerHTML = html;
var optForm = body.querySelector("#cf24-opt-form"); var optForm = body.querySelector("#cf24-opt-form");
@@ -1191,6 +1248,7 @@
var sidePane = document.getElementById("cf24-side-pane"); var sidePane = document.getElementById("cf24-side-pane");
function select(no, push) { function select(no, push) {
optModal.close(true); // 다른 상품으로 넘어가면 열려 있던 옵션 창은 무효
pane.innerHTML = '<p class="cf24-muted" style="padding:16px;">불러오는 중…</p>'; pane.innerHTML = '<p class="cf24-muted" style="padding:16px;">불러오는 중…</p>';
if (sidePane) sidePane.innerHTML = ""; if (sidePane) sidePane.innerHTML = "";
var url = "/cafe24/products/" + no + "/pane" + (listQuery ? "?" + listQuery : ""); var url = "/cafe24/products/" + no + "/pane" + (listQuery ? "?" + listQuery : "");
+135 -45
View File
@@ -1106,24 +1106,143 @@
font-size: 12px; font-size: 12px;
} }
/* 품목 — 380px 패널에 표는(실측: 옵션값 이름이 아이콘 아래로 꺾여 "그/1개"로 /* ── 옵션/품목 모달 — 380px 패널은 품목 표를 담기에(실측: 옵션값 이름이
잘렸다). 품목마다 2줄 카드로 그린다. 1줄: 아이콘 + 이름(남는 폭 전부), 아이콘 아래로 꺾여 잘렸다) 넓은 창에서 편집한다. 왼쪽 옵션 정의 / 오른쪽 품목 표. */
2줄: 자체코드 · 추가금액 · 진열/판매 토글. */ .cf24-modal {
.cf24-v-item { position: fixed;
padding: 6px 0; inset: 0;
border-bottom: 1px solid var(--color-subtle-ash, #e5e5e5); z-index: 1000;
}
.cf24-v-head {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; justify-content: center;
margin-bottom: 4px; padding: 24px;
background: rgba(10, 10, 10, 0.45);
}
.cf24-modal[hidden] {
display: none;
}
body.cf24-modal-open {
overflow: hidden; /* 뒤 화면 스크롤 잠금 */
}
.cf24-modal-box {
display: flex;
flex-direction: column;
width: min(1180px, 96vw);
max-height: 90vh;
background: var(--color-canvas-white, #fff);
border-radius: var(--r-xl, 14px);
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
.cf24-modal-head {
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--sp-12, 12px);
padding: 14px 18px;
border-bottom: 1px solid var(--color-subtle-ash, #e5e5e5);
flex: 0 0 auto;
}
.cf24-modal-title {
margin: 0;
font-size: var(--text-heading, 18px);
letter-spacing: -0.45px;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.cf24-modal-close {
flex: 0 0 auto;
width: 32px;
height: 32px;
border: 0;
border-radius: 50%;
background: transparent;
font-size: 16px;
cursor: pointer;
color: var(--color-midtone-gray, #737373);
}
.cf24-modal-close:hover {
background: var(--color-ghost-gray, #f2f2f2);
color: var(--color-rich-black, #0a0a0a);
}
.cf24-modal-body {
padding: 16px 18px 20px;
overflow: auto;
flex: 1 1 auto;
min-height: 0;
}
.cf24-modal-grid {
display: grid;
grid-template-columns: 400px minmax(0, 1fr);
gap: 24px;
align-items: start;
}
@media (max-width: 960px) {
.cf24-modal-grid {
grid-template-columns: minmax(0, 1fr);
}
}
.cf24-modal-col {
min-width: 0; min-width: 0;
} }
/* 옵션이 없는 상품의 생성 폼 — 넓은 모달에서 끝까지 늘어나지 않게 */
.cf24-modal-body .cf24-opt-create {
max-width: 560px;
}
/* 품목 표 — 모달의 오른쪽 열(약 700px)에 맞춘다 */
.cf24-variants {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.cf24-variants th,
.cf24-variants td {
padding: 6px 6px;
border-bottom: 1px solid var(--color-subtle-ash, #e5e5e5);
vertical-align: middle;
text-align: left;
}
.cf24-variants th {
font-weight: 500;
font-size: 12px;
color: var(--color-midtone-gray, #737373);
white-space: nowrap;
position: sticky;
top: 0;
background: var(--color-canvas-white, #fff);
}
.cf24-variants-wrap {
max-height: 62vh;
overflow: auto;
}
.cf24-v-imgcol { width: 40px; }
.cf24-v-syscode { width: 110px; }
.cf24-v-syscode code { font-size: 11px; color: var(--color-midtone-gray, #737373); }
.cf24-v-code { width: 150px; }
.cf24-v-amount { width: 96px; }
.cf24-v-amount input { text-align: right; }
.cf24-v-flag { width: 58px; text-align: center; }
.cf24-v-img { .cf24-v-img {
flex: 0 0 auto;
width: 32px; width: 32px;
height: 32px; height: 32px;
object-fit: cover; object-fit: cover;
@@ -1138,47 +1257,18 @@
} }
.cf24-v-name { .cf24-v-name {
flex: 1 1 auto;
min-width: 0;
font-size: 13px;
font-weight: 500; font-weight: 500;
word-break: break-word; word-break: break-word;
} }
.cf24-v-row { .cf24-variants input[type="text"] {
display: flex; height: 30px;
align-items: flex-end;
gap: 6px;
}
.cf24-v-row label {
display: flex;
flex-direction: column;
gap: 2px;
font-size: 11px;
color: var(--color-midtone-gray, #737373);
min-width: 0;
}
.cf24-v-row input[type="text"] {
height: 28px;
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
padding: 0 6px; padding: 0 8px;
border: 1px solid var(--color-subtle-ash, #e5e5e5); border: 1px solid var(--color-subtle-ash, #e5e5e5);
border-radius: var(--r-sm, 4px); border-radius: var(--r-sm, 4px);
font-size: 12px; font-size: 13px;
}
.cf24-v-code { flex: 1 1 110px; }
.cf24-v-amount { flex: 0 0 84px; }
.cf24-v-amount input { text-align: right; }
.cf24-v-flags {
flex: 0 0 auto;
display: flex;
gap: 4px;
padding-bottom: 3px; /* 토글을 입력칸 세로 중심에 맞춘다 */
} }
/* 진열/판매 토글 — 저장 전까지는 화면만 바뀐다(배지와 같은 색 규칙) */ /* 진열/판매 토글 — 저장 전까지는 화면만 바뀐다(배지와 같은 색 규칙) */
+7 -3
View File
@@ -148,7 +148,11 @@ Admin API(OAuth)로는 스킨 파일을 읽거나 쓸 방법이 없다(`config.p
- 오른쪽 **상품 정보 패널**(`_side.html`)은 "소스 수정 외 기능"을 모아둔 별도 공간이다. - 오른쪽 **상품 정보 패널**(`_side.html`)은 "소스 수정 외 기능"을 모아둔 별도 공간이다.
상품 클릭 시 `/products/{no}/pane` 이 편집기 조각과 패널 조각을 **한 응답**(`_panes.html`, 상품 클릭 시 `/products/{no}/pane` 이 편집기 조각과 패널 조각을 **한 응답**(`_panes.html`,
`[data-pane=editor]`/`[data-pane=side]`)으로 돌려주고 JS 가 각각 끼운다 — 카페24 상품 `[data-pane=editor]`/`[data-pane=side]`)으로 돌려주고 JS 가 각각 끼운다 — 카페24 상품
조회는 1회. 옵션/품목은 `<details>` 를 펼칠 때 JSON 으로 따로 불러온다(호출 2회 절약). 조회는 1회. 옵션/품목은 패널의 「팝업에서 관리」 버튼으로 **모달**(`#cf24-options-modal`,
최대 1180px — 왼쪽 옵션 정의 / 오른쪽 품목 표)을 열 때 JSON 으로 따로 불러온다(호출 2회
절약). 380px 패널은 품목 표를 담기에 좁아(실측) 별도 창으로 뺐다. 모달은 페이지에 하나만
두고(패널 조각 교체와 무관) 다른 상품을 고르면 닫힌다. 저장 안 한 변경(`.is-dirty`)이
있으면 닫을 때 확인창.
- 1360px 이하에서는 패널이 편집기 아래로 내려가고(높이 38vh, 안에서 스크롤), 1100px 이하는 - 1360px 이하에서는 패널이 편집기 아래로 내려가고(높이 38vh, 안에서 스크롤), 1100px 이하는
한 열이다. 한 열이다.
- 패널의 모든 쓰기는 JSON API(`routes_product_info.py`)이며 화면은 **응답값**으로 다시 - 패널의 모든 쓰기는 JSON API(`routes_product_info.py`)이며 화면은 **응답값**으로 다시
@@ -460,8 +464,8 @@ cafe24_oauth_tokens 저장
화면의 옵션값 썸네일은 **`option_image_file` → `option_link_image` → 그 옵션값을 가진 화면의 옵션값 썸네일은 **`option_image_file` → `option_link_image` → 그 옵션값을 가진
품목의 `image`** 순으로 있는 것을 보여준다. 카페24 관리자에서 "옵션 연결 이미지"로만 품목의 `image`** 순으로 있는 것을 보여준다. 카페24 관리자에서 "옵션 연결 이미지"로만
등록한 상품은 `option_image_file` 이 비어 있고 품목 `image` 에만 나타난다(실물 — 등록한 상품은 `option_image_file` 이 비어 있고 품목 `image` 에만 나타난다(실물 —
버튼 이미지만 보면 "없음"으로 오인했던 사고). 품목은 380px 패널에 표가 좁아 버튼 이미지만 보면 "없음"으로 오인했던 사고). 품목은 모달 오른쪽 열에 표(이미지 ·
품목마다 2줄 카드(아이콘+이름 / 코드·금액·진열·판매)로 그린다. "옵션별 상품코드/추가금액/진열" 은 품목 옵션 · 품목코드 · 자체코드 · 추가금액 · 진열 · 판매)로 그린다. "옵션별 상품코드/추가금액/진열" 은 품목
(`custom_variant_code`/`additional_amount`/`display`)이다 — 카페24 모델상 둘은 다른 (`custom_variant_code`/`additional_amount`/`display`)이다 — 카페24 모델상 둘은 다른
리소스라 화면에서도 「옵션 저장」과 「품목 저장」이 따로 있다. 리소스라 화면에서도 「옵션 저장」과 「품목 저장」이 따로 있다.