|
|
@ -1,6 +1,7 @@ |
|
|
<!DOCTYPE html> |
|
|
<!DOCTYPE html> |
|
|
<html lang="zh-CN"> |
|
|
<html lang="zh-CN"> |
|
|
<head> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<head> |
|
|
<meta charset="utf-8" /> |
|
|
<meta charset="utf-8" /> |
|
|
<meta name="viewport" content="width=device-width,initial-scale=1" /> |
|
|
<meta name="viewport" content="width=device-width,initial-scale=1" /> |
|
|
<title>管理后台</title> |
|
|
<title>管理后台</title> |
|
|
@ -11,6 +12,7 @@ |
|
|
background: #f7f8fb; |
|
|
background: #f7f8fb; |
|
|
color: #222; |
|
|
color: #222; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.card { |
|
|
.card { |
|
|
background: #fff; |
|
|
background: #fff; |
|
|
padding: 16px; |
|
|
padding: 16px; |
|
|
@ -18,11 +20,13 @@ |
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); |
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); |
|
|
margin: auto; |
|
|
margin: auto; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
table { |
|
|
table { |
|
|
width: 100%; |
|
|
width: 100%; |
|
|
border-collapse: collapse; |
|
|
border-collapse: collapse; |
|
|
margin-top: 12px; |
|
|
margin-top: 12px; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
th, |
|
|
th, |
|
|
td { |
|
|
td { |
|
|
padding: 10px 12px; |
|
|
padding: 10px 12px; |
|
|
@ -30,16 +34,19 @@ |
|
|
text-align: left; |
|
|
text-align: left; |
|
|
font-size: 14px; |
|
|
font-size: 14px; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
th { |
|
|
th { |
|
|
background: #fafafa; |
|
|
background: #fafafa; |
|
|
font-weight: 600; |
|
|
font-weight: 600; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.controls { |
|
|
.controls { |
|
|
display: flex; |
|
|
display: flex; |
|
|
gap: 12px; |
|
|
gap: 12px; |
|
|
align-items: center; |
|
|
align-items: center; |
|
|
flex-wrap: wrap; |
|
|
flex-wrap: wrap; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.pagination { |
|
|
.pagination { |
|
|
display: flex; |
|
|
display: flex; |
|
|
gap: 6px; |
|
|
gap: 6px; |
|
|
@ -47,6 +54,7 @@ |
|
|
margin-left: auto; |
|
|
margin-left: auto; |
|
|
flex-wrap: wrap; |
|
|
flex-wrap: wrap; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.btn { |
|
|
.btn { |
|
|
padding: 6px 10px; |
|
|
padding: 6px 10px; |
|
|
border-radius: 6px; |
|
|
border-radius: 6px; |
|
|
@ -55,54 +63,71 @@ |
|
|
cursor: pointer; |
|
|
cursor: pointer; |
|
|
user-select: none; |
|
|
user-select: none; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.btn:disabled { |
|
|
.btn:disabled { |
|
|
opacity: 0.5; |
|
|
opacity: 0.5; |
|
|
cursor: default; |
|
|
cursor: default; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.btn.primary { |
|
|
.btn.primary { |
|
|
background: #007bff; |
|
|
background: #007bff; |
|
|
color: #fff; |
|
|
color: #fff; |
|
|
border-color: #007bff; |
|
|
border-color: #007bff; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.btn.whatsapp { |
|
|
|
|
|
background: #25D366; |
|
|
|
|
|
color: #fff; |
|
|
|
|
|
border-color: #25D366; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
.status-btn { |
|
|
.status-btn { |
|
|
padding: 4px 8px; |
|
|
padding: 4px 8px; |
|
|
border-radius: 6px; |
|
|
border-radius: 6px; |
|
|
border: 1px solid #ccc; |
|
|
border: 1px solid #ccc; |
|
|
cursor: pointer; |
|
|
cursor: pointer; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.status-0 { |
|
|
.status-0 { |
|
|
background: red; |
|
|
background: red; |
|
|
color: #fff; |
|
|
color: #fff; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.status-1 { |
|
|
.status-1 { |
|
|
background: #2f9e44; |
|
|
background: #2f9e44; |
|
|
color: #fff; |
|
|
color: #fff; |
|
|
border-color: #2f9e44; |
|
|
border-color: #2f9e44; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.btn.active { |
|
|
.btn.active { |
|
|
background: #007bff; |
|
|
background: #007bff; |
|
|
color: #fff; |
|
|
color: #fff; |
|
|
border-color: #007bff; |
|
|
border-color: #007bff; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
select, |
|
|
select, |
|
|
input[type="number"] { |
|
|
input[type="number"] { |
|
|
padding: 6px; |
|
|
padding: 6px; |
|
|
border-radius: 6px; |
|
|
border-radius: 6px; |
|
|
border: 1px solid #ddd; |
|
|
border: 1px solid #ddd; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.small { |
|
|
.small { |
|
|
font-size: 13px; |
|
|
font-size: 13px; |
|
|
color: #666; |
|
|
color: #666; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@media (max-width: 640px) { |
|
|
@media (max-width: 640px) { |
|
|
.controls { |
|
|
.controls { |
|
|
flex-direction: column; |
|
|
flex-direction: column; |
|
|
align-items: flex-start; |
|
|
align-items: flex-start; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.pagination { |
|
|
.pagination { |
|
|
margin-left: 0; |
|
|
margin-left: 0; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
#noteModal { |
|
|
#noteModal { |
|
|
display: none; |
|
|
display: none; |
|
|
position: fixed; |
|
|
position: fixed; |
|
|
@ -112,6 +137,7 @@ |
|
|
justify-content: center; |
|
|
justify-content: center; |
|
|
z-index: 9999; |
|
|
z-index: 9999; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
#noteModal .dialog { |
|
|
#noteModal .dialog { |
|
|
background: #fff; |
|
|
background: #fff; |
|
|
padding: 16px; |
|
|
padding: 16px; |
|
|
@ -120,6 +146,7 @@ |
|
|
max-width: 520px; |
|
|
max-width: 520px; |
|
|
box-sizing: border-box; |
|
|
box-sizing: border-box; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
#noteModal textarea { |
|
|
#noteModal textarea { |
|
|
width: 100%; |
|
|
width: 100%; |
|
|
min-width: 60%; |
|
|
min-width: 60%; |
|
|
@ -131,6 +158,7 @@ |
|
|
border: 1px solid #ddd; |
|
|
border: 1px solid #ddd; |
|
|
font-size: 14px; |
|
|
font-size: 14px; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.toast { |
|
|
.toast { |
|
|
position: fixed; |
|
|
position: fixed; |
|
|
top: -20px; |
|
|
top: -20px; |
|
|
@ -147,23 +175,27 @@ |
|
|
z-index: 10000; |
|
|
z-index: 10000; |
|
|
pointer-events: none; |
|
|
pointer-events: none; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.toast.show { |
|
|
.toast.show { |
|
|
opacity: 1; |
|
|
opacity: 1; |
|
|
top: 20px; |
|
|
top: 20px; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
table th, |
|
|
table th, |
|
|
table td { |
|
|
table td { |
|
|
text-align: center; |
|
|
text-align: center; |
|
|
} |
|
|
} |
|
|
</style> |
|
|
</style> |
|
|
</head> |
|
|
|
|
|
<body> |
|
|
|
|
|
|
|
|
</head> |
|
|
|
|
|
|
|
|
|
|
|
<body> |
|
|
<div class="card"> |
|
|
<div class="card"> |
|
|
<table aria-describedby="tableDesc"> |
|
|
<table aria-describedby="tableDesc"> |
|
|
<thead> |
|
|
<thead> |
|
|
<tr> |
|
|
<tr> |
|
|
<th style="width: 40px">#</th> |
|
|
<th style="width: 40px">#</th> |
|
|
<th style="width: 100px">姓名</th> |
|
|
<th style="width: 100px">姓名</th> |
|
|
|
|
|
<th style="width: 100px">WhatsApp</th> |
|
|
<th style="width: 90px">国家/地区代码</th> |
|
|
<th style="width: 90px">国家/地区代码</th> |
|
|
<th style="width: 120px">电话号码</th> |
|
|
<th style="width: 120px">电话号码</th> |
|
|
<th style="width: 120px">微信ID</th> |
|
|
<th style="width: 120px">微信ID</th> |
|
|
@ -196,19 +228,13 @@ |
|
|
<div id="noteModal"> |
|
|
<div id="noteModal"> |
|
|
<div class="dialog"> |
|
|
<div class="dialog"> |
|
|
<h3 style="margin: 0 0 8px">编辑备注</h3> |
|
|
<h3 style="margin: 0 0 8px">编辑备注</h3> |
|
|
<textarea |
|
|
|
|
|
id="noteTextarea" |
|
|
|
|
|
rows="6" |
|
|
|
|
|
placeholder="输入备注..." |
|
|
|
|
|
></textarea> |
|
|
|
|
|
<div |
|
|
|
|
|
style=" |
|
|
|
|
|
|
|
|
<textarea id="noteTextarea" rows="6" placeholder="输入备注..."></textarea> |
|
|
|
|
|
<div style=" |
|
|
margin-top: 10px; |
|
|
margin-top: 10px; |
|
|
display: flex; |
|
|
display: flex; |
|
|
justify-content: flex-end; |
|
|
justify-content: flex-end; |
|
|
gap: 8px; |
|
|
gap: 8px; |
|
|
" |
|
|
|
|
|
> |
|
|
|
|
|
|
|
|
"> |
|
|
<button class="btn" id="noteCancelBtn">取消</button> |
|
|
<button class="btn" id="noteCancelBtn">取消</button> |
|
|
<button class="btn primary" id="noteSaveBtn">保存</button> |
|
|
<button class="btn primary" id="noteSaveBtn">保存</button> |
|
|
</div> |
|
|
</div> |
|
|
@ -217,12 +243,12 @@ |
|
|
</div> |
|
|
</div> |
|
|
<div id="toast" class="toast"></div> |
|
|
<div id="toast" class="toast"></div> |
|
|
<script type="module"> |
|
|
<script type="module"> |
|
|
import { getMemberListApi,updateMemberStateApi,editMemberNoteApi }from './src/api/member.js' |
|
|
|
|
|
|
|
|
import { getMemberListApi, updateMemberStateApi, editMemberNoteApi } from './src/api/member.js' |
|
|
let state = { |
|
|
let state = { |
|
|
pageSize: 20, |
|
|
pageSize: 20, |
|
|
currentPage: 1, |
|
|
currentPage: 1, |
|
|
total: 0, |
|
|
total: 0, |
|
|
items:[] |
|
|
|
|
|
|
|
|
items: [] |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
// DOM |
|
|
// DOM |
|
|
@ -274,24 +300,30 @@ |
|
|
const serial = startIndex + idx + 1; |
|
|
const serial = startIndex + idx + 1; |
|
|
const statusClass = item.isRelated ? "status-1" : "status-0"; |
|
|
const statusClass = item.isRelated ? "status-1" : "status-0"; |
|
|
const statusText = item.isRelated ? "已联系" : "未联系"; |
|
|
const statusText = item.isRelated ? "已联系" : "未联系"; |
|
|
|
|
|
|
|
|
|
|
|
// 构建WhatsApp链接 |
|
|
|
|
|
const whatsappPhone = (item.code || '') + (item.telephone || ''); |
|
|
|
|
|
const whatsappUrl = `https://api.whatsapp.com/send?phone=${encodeURIComponent(whatsappPhone)}&text=${encodeURIComponent('hello欢迎来到赢在美股')}`; |
|
|
|
|
|
|
|
|
return ` |
|
|
return ` |
|
|
<tr> |
|
|
<tr> |
|
|
<td>${serial}</td> |
|
|
<td>${serial}</td> |
|
|
<td>${escapeHtml(item.name || "")}</td> |
|
|
<td>${escapeHtml(item.name || "")}</td> |
|
|
|
|
|
<td> |
|
|
|
|
|
<button class="btn whatsapp" data-action="whatsapp" data-id="${item.id}">WhatsApp</button> |
|
|
|
|
|
</td> |
|
|
<td>${escapeHtml(item.code || "")}</td> |
|
|
<td>${escapeHtml(item.code || "")}</td> |
|
|
<td>${escapeHtml(item.telephone || "")}</td> |
|
|
<td>${escapeHtml(item.telephone || "")}</td> |
|
|
<td>${escapeHtml(item.wechat || "")}</td> |
|
|
<td>${escapeHtml(item.wechat || "")}</td> |
|
|
<td>${escapeHtml(item.email || "")}</td> |
|
|
<td>${escapeHtml(item.email || "")}</td> |
|
|
<td>${escapeHtml(item.createdAt || "")}</td> |
|
|
<td>${escapeHtml(item.createdAt || "")}</td> |
|
|
<td> |
|
|
<td> |
|
|
<button class="status-btn ${statusClass}" data-action="toggle" data-id="${ |
|
|
|
|
|
item.id |
|
|
|
|
|
|
|
|
<button class="status-btn ${statusClass}" data-action="toggle" data-id="${item.id |
|
|
}">${statusText}</button> |
|
|
}">${statusText}</button> |
|
|
</td> |
|
|
</td> |
|
|
<td>${escapeHtml(item.note || "")}</td> |
|
|
<td>${escapeHtml(item.note || "")}</td> |
|
|
<td> |
|
|
<td> |
|
|
<button class="btn" data-action="editNote" data-id="${ |
|
|
|
|
|
item.id |
|
|
|
|
|
|
|
|
<button class="btn" data-action="editNote" data-id="${item.id |
|
|
}">编辑备注</button> |
|
|
}">编辑备注</button> |
|
|
</td> |
|
|
</td> |
|
|
</tr> |
|
|
</tr> |
|
|
@ -302,6 +334,14 @@ |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
tableBody.addEventListener("click", async (e) => { |
|
|
tableBody.addEventListener("click", async (e) => { |
|
|
|
|
|
// WhatsApp跳转 |
|
|
|
|
|
const whatsappBtn = e.target.closest('[data-action="whatsapp"]'); |
|
|
|
|
|
if (whatsappBtn) { |
|
|
|
|
|
const id = whatsappBtn.getAttribute("data-id"); |
|
|
|
|
|
handleWhatsApp(id); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 切换状态 |
|
|
// 切换状态 |
|
|
const toggler = e.target.closest('[data-action="toggle"]'); |
|
|
const toggler = e.target.closest('[data-action="toggle"]'); |
|
|
if (toggler) { |
|
|
if (toggler) { |
|
|
@ -317,6 +357,18 @@ |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// ---------- WhatsApp跳转 ---------- |
|
|
|
|
|
function handleWhatsApp(id) { |
|
|
|
|
|
const item = state.items.find((it) => String(it.id) === String(id)); |
|
|
|
|
|
if (!item) return; |
|
|
|
|
|
|
|
|
|
|
|
const whatsappPhone = (item.code || '') + (item.telephone || ''); |
|
|
|
|
|
const whatsappUrl = `https://api.whatsapp.com/send?phone=${encodeURIComponent(whatsappPhone)}&text=${encodeURIComponent('hello欢迎来到赢在美股')}`; |
|
|
|
|
|
|
|
|
|
|
|
window.open(whatsappUrl, '_blank'); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// ---------- 切换联系状态 ---------- |
|
|
// ---------- 切换联系状态 ---------- |
|
|
async function handleToggle(id, btnEl) { |
|
|
async function handleToggle(id, btnEl) { |
|
|
const item = state.items.find((it) => String(it.id) === String(id)); |
|
|
const item = state.items.find((it) => String(it.id) === String(id)); |
|
|
@ -389,20 +441,17 @@ |
|
|
const pages = buildPageList(current, totalPages, 2); |
|
|
const pages = buildPageList(current, totalPages, 2); |
|
|
|
|
|
|
|
|
let html = ""; |
|
|
let html = ""; |
|
|
html += `<button class="btn" data-action="prev" ${ |
|
|
|
|
|
current === 1 ? "disabled" : "" |
|
|
|
|
|
|
|
|
html += `<button class="btn" data-action="prev" ${current === 1 ? "disabled" : "" |
|
|
}>上一页</button>`; |
|
|
}>上一页</button>`; |
|
|
pages.forEach((p) => { |
|
|
pages.forEach((p) => { |
|
|
if (p === "...") { |
|
|
if (p === "...") { |
|
|
html += `<span class="small" style="padding:6px 8px">...</span>`; |
|
|
html += `<span class="small" style="padding:6px 8px">...</span>`; |
|
|
} else { |
|
|
} else { |
|
|
html += `<button class="btn ${ |
|
|
|
|
|
p === current ? "active" : "" |
|
|
|
|
|
|
|
|
html += `<button class="btn ${p === current ? "active" : "" |
|
|
}" data-page="${p}">${p}</button>`; |
|
|
}" data-page="${p}">${p}</button>`; |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
html += `<button class="btn" data-action="next" ${ |
|
|
|
|
|
current === totalPages ? "disabled" : "" |
|
|
|
|
|
|
|
|
html += `<button class="btn" data-action="next" ${current === totalPages ? "disabled" : "" |
|
|
}>下一页</button>`; |
|
|
}>下一页</button>`; |
|
|
|
|
|
|
|
|
paginationEl.innerHTML = html; |
|
|
paginationEl.innerHTML = html; |
|
|
@ -477,5 +526,6 @@ |
|
|
// 首次渲染 |
|
|
// 首次渲染 |
|
|
update(); |
|
|
update(); |
|
|
</script> |
|
|
</script> |
|
|
</body> |
|
|
|
|
|
|
|
|
</body> |
|
|
|
|
|
|
|
|
</html> |
|
|
</html> |