generated from ttipo/project-base
feat: 업체 링크 관리 화면과 아임웹 추적 스크립트 추가
- 관리자 화면 '업체 링크' 메뉴: 업체 추가, 링크 발급·복사·중지, 기간별 클릭·신청·구매, 링크별 실적 회원 목록, 상품 불러오기·구매 집계 새로고침 - partner-ref.html (Header Code): 클릭 ID 를 30일 보관하고 로그인 회원과 잇는다 - imweb-free-apply.html: 신청 기록(클릭 ID 포함)과 오픈카톡 클릭 기록 전송 추가 - README 에 귀속 규칙·한계·처음 켤 때 순서 정리 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
<!-- ===== 아임웹 Header Code 삽입용 — 업체 링크 추적 (partner-ref) ===== -->
|
||||
<!--
|
||||
어디에 넣나 : 아임웹 관리자 > 환경설정 > SEO/HTML > Header Code (사이트 전체)
|
||||
header-top.html(UTM 스크립트)과 함께 두어도 서로 영향이 없습니다.
|
||||
|
||||
하는 일
|
||||
1) 업체 단축 링크(moneyguide.wandergo.net/r/코드)를 누르고 들어오면 주소에 붙은
|
||||
mg_ref(클릭 ID)를 이 브라우저에 30일간 보관하고, 주소창에서는 지웁니다.
|
||||
(주소를 그대로 공유해도 다른 사람이 그 업체 실적으로 잡히지 않게)
|
||||
2) 로그인한 회원이 확인되면 "이 회원이 이 링크로 들어왔다"를 서버에 한 번 알립니다.
|
||||
→ 구매는 서버가 아임웹 주문을 1시간마다 확인해 이 기록으로 업체에 귀속합니다.
|
||||
3) 신청 완료 위젯(imweb-free-apply.html)이 window.MG_PARTNER.refs() 로 같은 값을 읽어
|
||||
신청 기록에 함께 보냅니다.
|
||||
|
||||
저장하는 값 : 클릭 ID 와 보관 시각만 (회원 정보는 저장하지 않습니다)
|
||||
-->
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/* 백엔드 주소 */
|
||||
var API_BASE = 'https://moneyguide.wandergo.net/api';
|
||||
|
||||
/* 아래는 수정할 필요 없습니다 — 백엔드(shared/utils/partner-link.ts)와 같은 값입니다 */
|
||||
var PARAM = 'mg_ref';
|
||||
var REF_PATTERN = /^[0-9A-Za-z]{16}$/;
|
||||
var STORE_KEY = 'mg_partner_refs'; /* [{ ref, at }] — 최근 것이 뒤 */
|
||||
var SENT_KEY = 'mg_partner_sent'; /* 이미 서버에 알린 클릭 ID */
|
||||
var WINDOW_MS = 30 * 24 * 60 * 60 * 1000; /* 귀속 기간 30일 */
|
||||
var MAX_REFS = 20;
|
||||
/* 아임웹 전역(MEMBER_UID)은 Header Code 보다 늦게 생깁니다 */
|
||||
var WAIT_MS = 5000;
|
||||
var POLL_MS = 200;
|
||||
|
||||
function readJson(key) {
|
||||
try {
|
||||
var value = JSON.parse(window.localStorage.getItem(key) || '[]');
|
||||
return Array.isArray(value) ? value : [];
|
||||
} catch (e) { return []; }
|
||||
}
|
||||
|
||||
function writeJson(key, value) {
|
||||
try { window.localStorage.setItem(key, JSON.stringify(value)); } catch (e) { }
|
||||
}
|
||||
|
||||
/* 30일이 지났거나 형식이 아닌 값은 버립니다 */
|
||||
function freshRefs() {
|
||||
var now = Date.now();
|
||||
return readJson(STORE_KEY).filter(function (item) {
|
||||
return item && REF_PATTERN.test(item.ref) &&
|
||||
typeof item.at === 'number' && now - item.at <= WINDOW_MS;
|
||||
});
|
||||
}
|
||||
|
||||
/* 1) 주소의 클릭 ID 보관 + 주소창 정리 */
|
||||
var refs = freshRefs();
|
||||
try {
|
||||
var url = new URL(window.location.href);
|
||||
var ref = url.searchParams.get(PARAM);
|
||||
if (ref && REF_PATTERN.test(ref)) {
|
||||
refs = refs.filter(function (item) { return item.ref !== ref; });
|
||||
refs.push({ ref: ref, at: Date.now() });
|
||||
if (refs.length > MAX_REFS) refs = refs.slice(-MAX_REFS);
|
||||
}
|
||||
if (url.searchParams.has(PARAM)) {
|
||||
url.searchParams.delete(PARAM);
|
||||
window.history.replaceState(window.history.state, '',
|
||||
url.pathname + url.search + url.hash);
|
||||
}
|
||||
} catch (e) { }
|
||||
writeJson(STORE_KEY, refs);
|
||||
|
||||
/* 3) 다른 위젯이 읽는 창구 */
|
||||
window.MG_PARTNER = {
|
||||
refs: function () {
|
||||
return freshRefs().map(function (item) { return item.ref; });
|
||||
}
|
||||
};
|
||||
|
||||
/* 2) 로그인 회원 ↔ 클릭 연결 */
|
||||
function report(memberUid) {
|
||||
var current = window.MG_PARTNER.refs();
|
||||
var sent = readJson(SENT_KEY);
|
||||
var pending = current.filter(function (r) { return sent.indexOf(r) === -1; });
|
||||
if (pending.length === 0 || typeof window.fetch !== 'function') return;
|
||||
|
||||
window.fetch(API_BASE + '/partner-touches', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'omit',
|
||||
body: JSON.stringify({ memberUid: memberUid, refs: pending })
|
||||
}).then(function (res) {
|
||||
if (!res.ok) return; /* 실패하면 다음 페이지에서 다시 보냅니다 */
|
||||
/* 보관 중인 것만 남겨 목록이 끝없이 늘지 않게 합니다 */
|
||||
writeJson(SENT_KEY, sent.concat(pending).filter(function (r) {
|
||||
return current.indexOf(r) !== -1;
|
||||
}));
|
||||
}).catch(function () { });
|
||||
}
|
||||
|
||||
if (refs.length === 0) return; /* 보관한 클릭이 없으면 할 일이 없습니다 */
|
||||
|
||||
var waited = 0;
|
||||
(function poll() {
|
||||
var uid = window.MEMBER_UID;
|
||||
if (uid) { report(String(uid)); return; }
|
||||
/* 빈 문자열이면 비로그인 확정 — 로그인한 뒤 다른 페이지에서 다시 시도됩니다 */
|
||||
if (typeof uid === 'string' || waited >= WAIT_MS) return;
|
||||
waited += POLL_MS;
|
||||
window.setTimeout(poll, POLL_MS);
|
||||
})();
|
||||
})();
|
||||
</script>
|
||||
<!-- ===== 코드 끝 ===== -->
|
||||
Reference in New Issue
Block a user