fix(cafe24): 통합 목록 행 순서를 품목(진열) 순서로, 깨진 썸네일은 "없음"

- options 응답의 옵션값 순서는 등록 순서라 쇼핑몰 순서와 달랐다. 품목 목록
  순서(카페24가 진열순서대로 돌려줌)를 기준으로 행을 만들고, 품목이 안 잡힌
  옵션값은 뒤에 붙인다. 옵션 PUT 짝맞춤용 index 는 그대로 유지하고, 드래그
  변경 판정은 처음 그린 위치(data-pos) 기준으로.
- 카페24가 경로만 주고 파일이 없는 썸네일은 onerror 로 "없음" 표시.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-18 23:32:34 +09:00
parent 8125a59366
commit f31204e9b9
2 changed files with 28 additions and 14 deletions
+27 -13
View File
@@ -128,10 +128,13 @@
return "";
}
// 카페24가 경로는 주지만 파일이 없는 경우가 있다(깨진 그림) — 실패하면 "없음"으로 바꾼다.
var THUMB_EMPTY = '<div class="cf24-opt-thumb-empty">없음</div>';
function thumbHtml(src, title) {
return src
? '<img class="cf24-opt-thumb" src="' + esc(src) + '" alt="" title="' + esc(title || "") + '" />'
: '<div class="cf24-opt-thumb-empty">없음</div>';
? '<img class="cf24-opt-thumb" src="' + esc(src) + '" alt="" title="' + esc(title || "") +
'" onerror="this.outerHTML=\'' + THUMB_EMPTY.replace(/"/g, "&quot;") + '\'" />'
: THUMB_EMPTY;
}
// 썸네일 파일 선택 → 업로드 → hidden 두 개(option_image_file, option_link_image)에 경로
@@ -176,27 +179,38 @@
/* ═══════════════════════════════════════════════════════════
통합 목록 — 옵션값 1 : 품목 1
═══════════════════════════════════════════════════════════ */
// 행 순서는 **품목 목록 순서**(카페24가 진열순서대로 돌려준다 — 쇼핑몰에 보이는 순서)를
// 따른다. options 응답의 옵션값 순서는 등록 순서라 실제와 다를 수 있다(실물 확인).
// index 는 options 응답 안 위치 — 옵션 PUT 은 이 위치로 짝을 맞춰야 하므로 유지한다.
function unifiedRows() {
var opt = state.option;
if (!opt || !opt.has_option) return [];
var group = opt.options[0];
return group.option_value.map(function (v, vi) {
var variant = null;
for (var i = 0; i < state.variants.length; i++) {
var cand = state.variants[i];
if (cand.options.length && cand.options[0].value === v.option_text) { variant = cand; break; }
}
return { index: vi, value: v, variant: variant };
var byText = {};
group.option_value.forEach(function (v, vi) { byText[v.option_text] = { index: vi, value: v, variant: null }; });
var rows = [];
state.variants.forEach(function (variant) {
if (!variant.options.length) return;
var r = byText[variant.options[0].value];
if (!r || r.variant) return; // 옵션값이 없거나 이미 배정됨
r.variant = variant;
rows.push(r);
});
group.option_value.forEach(function (v) {
var r = byText[v.option_text];
if (rows.indexOf(r) < 0) rows.push(r); // 품목이 안 잡힌 옵션값은 뒤에
});
return rows;
}
function rowHtml(r, isNew, canDrag) {
function rowHtml(r, isNew, canDrag, position) {
var v = r.value || { option_text: "", option_image_file: "", option_link_image: "", option_color: "" };
var variant = r.variant;
var thumbSrc = v.option_image_file || v.option_link_image ||
(state.option && state.option.has_option ? variantImageFor(state.option.options[0].option_name, v.option_text) : "");
var code = variant ? variant.variant_code : "";
return '<tr class="cf24-u-row" data-index="' + (r.index == null ? "" : r.index) + '" data-code="' + esc(code) + '">' +
// data-index: options 응답 안 위치(옵션 PUT 짝맞춤용) / data-pos: 처음 그린 순서(순서 변경 판정용)
return '<tr class="cf24-u-row" data-index="' + (r.index == null ? "" : r.index) + '" data-pos="' + (position == null ? "" : position) + '" data-code="' + esc(code) + '">' +
'<td class="cf24-u-drag">' + (canDrag ? '<span class="cf24-drag-handle" draggable="true" title="드래그하여 순서 변경">⋮⋮</span>' : "") + "</td>" +
'<td class="cf24-u-thumb"><span class="cf24-opt-thumb-slot">' + thumbHtml(thumbSrc, v.option_image_file ? "옵션 버튼 이미지" : v.option_link_image ? "옵션 연결 이미지" : "품목 이미지") + "</span>" +
'<input type="hidden" name="option_image_file" value="' + esc(v.option_image_file || "") + '" />' +
@@ -234,7 +248,7 @@
'<th class="cf24-u-drag"></th><th class="cf24-u-thumb">썸네일</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><th class="cf24-u-del"></th>' +
"</tr></thead><tbody id=\"cf24-u-body\">";
rows.forEach(function (r) { html += rowHtml(r, false, canDrag); });
rows.forEach(function (r, i) { html += rowHtml(r, false, canDrag, i); });
if (isNew) html += rowHtml({ index: null, value: null, variant: null }, true, true);
html += "</tbody></table></div>" +
'<p class="cf24-side-note cf24-u-note">' +
@@ -319,7 +333,7 @@
function markOrder(tbody) {
var changed = false;
Array.prototype.forEach.call(tbody.children, function (tr, i) {
var orig = tr.dataset.index === "" ? null : parseInt(tr.dataset.index, 10);
var orig = tr.dataset.pos === "" || tr.dataset.pos == null ? null : parseInt(tr.dataset.pos, 10);
if (orig !== null && orig !== i) changed = true;
});
tbody.classList.toggle("is-order-dirty", changed);