Browse Source

Merge branch 'lizida/feature-20260202202735-milestone-20260202-美股市场改版' into milestone-20260202-美股市场改版

dev
qiqi 3 weeks ago
parent
commit
af6dea92fc
  1. 651
      content-config.html
  2. 760
      deepchart-management.html
  3. 97
      hcdbqb-management.html
  4. 30
      src/api/member.js

651
content-config.html

@ -0,0 +1,651 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>管理后台</title>
<style>
.tab-container {
max-width: 1200px;
margin: 0 0 12px 0;
padding: 0 16px;
}
.tabs {
display: flex;
gap: 50px;
padding: 4px;
border-radius: 8px;
width: fit-content;
}
.tab {
padding: 10px 20px;
border-radius: 6px;
border: none;
background: transparent;
color: #131212;
font-weight: 700;
cursor: pointer;
transition: all 0.3s ease;
font-size: 16px;
white-space: nowrap;
}
.tab:hover {
background: rgba(46, 125, 50, 0.1);
color: #25D366;
}
.tab.active {
background: #BAF7D0;
color: #0E4322;
box-shadow: 0 2px 4px rgba(46, 125, 50, 0.3);
}
body {
font-family: Arial, sans-serif;
padding: 24px;
background: #f7f8fb;
color: #222;
}
.card {
background: #fff;
padding: 16px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
margin: auto;
min-height: 700px;
border: 5px solid #D9D9D9;
display: flex;
flex-direction: column;
position: relative;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 12px;
}
th,
td {
padding: 10px 12px;
border-bottom: 1px solid #eee;
text-align: left;
font-size: 14px;
}
th {
background: #fafafa;
font-weight: 600;
}
.controls {
display: flex;
gap: 12px;
align-items: center;
flex-wrap: wrap;
}
.pagination {
display: flex;
gap: 6px;
align-items: center;
margin-left: auto;
flex-wrap: wrap;
}
.btn {
padding: 6px 10px;
border-radius: 6px;
border: 1px solid #ddd;
background: #fff;
cursor: pointer;
user-select: none;
}
.btn:disabled {
opacity: 0.5;
cursor: default;
}
.btn.primary {
background: #007bff;
color: #fff;
border-color: #007bff;
}
.btn.whatsapp {
background: #25D366;
color: #fff;
border-color: #25D366;
}
.status-btn {
padding: 4px 8px;
border-radius: 6px;
border: 1px solid #ccc;
cursor: pointer;
}
.status-0 {
background: red;
color: #fff;
}
.status-1 {
background: #2f9e44;
color: #fff;
border-color: #2f9e44;
}
.btn.active {
background: #007bff;
color: #fff;
border-color: #007bff;
}
select,
input[type="number"] {
padding: 6px;
border-radius: 6px;
border: 1px solid #ddd;
}
.small {
font-size: 13px;
color: #666;
}
@media (max-width: 640px) {
.tabs {
flex-wrap: wrap;
width: 100%;
}
.tab {
flex: 1;
min-width: 120px;
text-align: center;
}
.controls {
flex-direction: column;
align-items: flex-start;
}
.pagination {
margin-left: 0;
}
}
#noteModal {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.45);
align-items: center;
justify-content: center;
z-index: 9999;
}
#noteModal .dialog {
background: #fff;
padding: 16px;
border-radius: 8px;
width: 90%;
max-width: 520px;
box-sizing: border-box;
}
#noteModal textarea {
width: 100%;
min-width: 60%;
max-width: 100%;
min-height: 150px;
box-sizing: border-box;
padding: 8px;
border-radius: 6px;
border: 1px solid #ddd;
font-size: 14px;
}
.toast {
position: fixed;
top: -20px;
left: 50%;
transform: translateX(-50%);
background: #4caf50;
color: #fff;
padding: 10px 16px;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
font-size: 16px;
opacity: 0;
transition: all 0.3s ease;
z-index: 10000;
pointer-events: none;
}
.toast.show {
opacity: 1;
top: 20px;
}
table th,
table td {
text-align: center;
}
/* 登录验证样式 */
#loginCheckModal {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.7);
align-items: center;
justify-content: center;
z-index: 99999;
}
#loginCheckModal .dialog {
background: #fff;
padding: 30px;
border-radius: 12px;
width: 90%;
max-width: 400px;
text-align: center;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
#loginCheckModal h3 {
margin: 0 0 16px;
color: #e74c3c;
font-size: 20px;
}
#loginCheckModal p {
margin: 0 0 24px;
color: #666;
line-height: 1.5;
}
#loginCheckModal .btn {
padding: 10px 20px;
font-size: 16px;
}
/* 图片上传组件样式 */
.config-section {
display: flex;
align-items: center; /* 垂直居中 */
gap: 30px;
margin-bottom: 40px;
padding: 20px 40px;
}
.section-label {
font-weight: bold;
min-width: 60px;
font-size: 20px;
color: #333;
}
.image-uploader {
position: relative;
/* 图片尺寸需要修改确认几比几 */
width: 240px;
height: 160px;
border: 1px dashed #d9d9d9;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
background: #fff;
transition: all 0.3s;
cursor: pointer;
}
.image-uploader:hover {
border-color: #BAF7D0;
}
.upload-btn {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 36px;
color: #bfbfbf; /* 更淡的灰色 */
font-weight: lighter;
}
.preview-container {
width: 100%;
height: 100%;
position: relative;
}
.preview-container img {
width: 100%;
height: 100%;
object-fit: cover;
}
.delete-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
display: none;
align-items: center;
justify-content: center;
transition: all 0.3s;
}
.preview-container:hover .delete-overlay {
display: flex;
}
.delete-btn {
background: transparent;
color: white;
border: 1px solid white;
padding: 6px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
}
.delete-btn:hover {
background: rgba(255,255,255,0.2);
}
.action-bar {
display: flex;
justify-content: center;
margin-top: auto; /* 关键:推到底部 */
padding-bottom: 40px; /* 留出底部间距 */
}
#saveConfigBtn {
width: 130px;
height: 60px;
font-size: 30px;
background: #BAF7D0;
color: #0D4221;
border: none;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
border-radius: 6px;
letter-spacing: 2px;
}
#saveConfigBtn:hover {
background: #a5e6bc;
box-shadow: 0 4px 12px rgba(186, 247, 208, 0.4);
}
</style>
</head>
<body>
<!-- 登录验证模态框 -->
<div id="loginCheckModal">
<div class="dialog">
<h3>访问被拒绝</h3>
<p>您尚未登录或登录已过期,请先登录系统。</p>
<button class="btn primary" id="goToLoginBtn">前往登录页面</button>
</div>
</div>
<div class="tab-container">
<div class="tabs">
<button class="tab" data-tab="win-us-stock">赢在美股导流</button>
<button class="tab" data-tab="deepchart">DeepChart导流</button>
<button class="tab active" data-tab="content-config">内容配置</button>
</div>
</div>
<div class="card">
<div class="config-section">
<div class="section-label">课程图</div>
<div class="image-uploader" id="imageUploader">
<div class="upload-btn" id="uploadBtn">
<span>+</span>
<input type="file" id="fileInput" accept="image/*" hidden>
</div>
<div class="preview-container" id="previewContainer" style="display: none;">
<img id="previewImage" src="" alt="Preview">
<div class="delete-overlay">
<button class="delete-btn" id="deleteBtn">删除</button>
</div>
</div>
</div>
</div>
<div class="action-bar">
<button id="saveConfigBtn">保存</button>
</div>
</div>
<div id="toast" class="toast"></div>
<script type="module">
import { saveContentConfigApi, getImage1Api, uploadFileApi } from './src/api/member.js'
// 登录验证函数
function checkLoginStatus() {
const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true';
const loginTime = parseInt(localStorage.getItem('loginTime'));
const currentTime = new Date().getTime();
const hoursDiff = (currentTime - loginTime) / (1000 * 60 * 60);
// 检查是否登录且登录时间在24小时内
if (!isLoggedIn || hoursDiff >= 24) {
// 清除过期的登录状态
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('loginTime');
return false;
}
return true;
}
// 显示登录验证模态框
function showLoginCheckModal() {
const modal = document.getElementById('loginCheckModal');
modal.style.display = 'flex';
}
// 隐藏登录验证模态框
function hideLoginCheckModal() {
const modal = document.getElementById('loginCheckModal');
modal.style.display = 'none';
}
// 跳转到登录页面
function redirectToLogin() {
window.location.href = 'login-management.html';
}
// 检查登录状态
if (!checkLoginStatus()) {
showLoginCheckModal();
}
// 绑定前往登录页面按钮事件
document.getElementById('goToLoginBtn').addEventListener('click', redirectToLogin);
// 初始化Tab切换
initTabs();
// Tab切换功能
function initTabs() {
const tabs = document.querySelectorAll('.tab');
tabs.forEach(tab => {
tab.addEventListener('click', function() {
tabs.forEach(t => t.classList.remove('active'));
this.classList.add('active');
const tabId = this.getAttribute('data-tab');
switch(tabId) {
case 'win-us-stock':
// 当前页面,不需要跳转
console.log('切换到赢在美股导流页面');
window.location.href = 'hcdbqb-management.html';
break;
case 'deepchart':
// 跳转到DeepChart导流页面
console.log('切换到DeepChart导流页面');
window.location.href = 'deepchart-management.html';
break;
case 'content-config':
// 跳转到内容配置页面
console.log('切换到内容配置页面');
// window.location.href = 'content-config.html';
break;
}
});
});
}
// --- 新增图片上传逻辑 ---
const uploadBtn = document.getElementById('uploadBtn');
const fileInput = document.getElementById('fileInput');
const previewContainer = document.getElementById('previewContainer');
const previewImage = document.getElementById('previewImage');
const deleteBtn = document.getElementById('deleteBtn');
const saveConfigBtn = document.getElementById('saveConfigBtn');
const toastEl = document.getElementById('toast');
let currentFile = null;
let currentImageUrl = '';
function showToast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
setTimeout(() => toastEl.classList.remove("show"), 1000);
}
// 页面加载时获取当前配置
if (checkLoginStatus()) {
loadCurrentConfig();
}
async function loadCurrentConfig() {
// 每次请求数据前都检查登录状态
if (!checkLoginStatus()) {
showLoginCheckModal();
return;
}
try {
const res = await getImage1Api({
id:1
});
if (res && res.code === 200 && res.data && res.data.img_url) {
currentImageUrl = res.data.img_url;
showPreview(currentImageUrl);
}
} catch (e) {
console.error('加载配置失败', e);
}
}
function showPreview(src) {
previewImage.src = src;
previewContainer.style.display = 'block';
uploadBtn.style.display = 'none';
}
function resetUpload() {
fileInput.value = '';
currentFile = null;
currentImageUrl = '';
previewImage.src = '';
previewContainer.style.display = 'none';
uploadBtn.style.display = 'flex';
}
uploadBtn.addEventListener('click', () => {
if (!checkLoginStatus()) {
showLoginCheckModal();
return;
}
fileInput.click();
});
fileInput.addEventListener('change', async (e) => {
if (!checkLoginStatus()) {
showLoginCheckModal();
return;
}
const file = e.target.files[0];
if (file) {
// 1. 立即显示本地预览
const reader = new FileReader();
reader.onload = (e) => {
showPreview(e.target.result);
};
reader.readAsDataURL(file);
// 2. 立即上传文件获取URL
try {
showToast('正在上传图片...');
const formData = new FormData();
formData.append('file', file);
formData.append('app_from', 'en');
formData.append('type', 'image');
//尺寸不确定
formData.append('imgWidth', '600');
formData.append('imgHeight', '400');
const res = await uploadFileApi(formData);
if (res && res.code === 200 && res.data) {
const uploadedUrl = typeof res.data === 'string' ? res.data : res.data.url;
if (uploadedUrl) {
currentImageUrl = uploadedUrl;
currentFile = null;
showToast('图片上传成功');
} else {
throw new Error('未获取到有效的图片地址');
}
} else {
throw new Error(res.msg || '上传接口返回错误');
}
} catch (error) {
console.error('上传失败', error);
showToast('图片上传失败: ' + (error.message || '未知错误'));
resetUpload()
}
}
});
deleteBtn.addEventListener('click', (e) => {
if (!checkLoginStatus()) {
showLoginCheckModal();
return;
}
e.stopPropagation();
resetUpload();
});
saveConfigBtn.addEventListener('click', async () => {
if (!checkLoginStatus()) {
showLoginCheckModal();
return;
}
if (!currentImageUrl) {
showToast('请先上传图片');
return;
}
try {
const formData = new FormData();
formData.append('id', 1);
formData.append('url', currentImageUrl);
const res = await saveContentConfigApi(formData);
if (res && res.code === 200) {
showToast('保存配置成功');
} else {
throw new Error(res.msg || '保存失败');
}
} catch (e) {
showToast(e.message || '保存失败');
}
});
</script>
</body>
</html>

760
deepchart-management.html

@ -0,0 +1,760 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>管理后台</title>
<style>
.tab-container {
max-width: 1200px;
margin: 0 0 12px 0;
padding: 0 16px;
}
.tabs {
display: flex;
gap: 50px;
padding: 4px;
border-radius: 8px;
width: fit-content;
}
.tab {
padding: 10px 20px;
border-radius: 6px;
border: none;
background: transparent;
color: #131212;
font-weight: 700;
cursor: pointer;
transition: all 0.3s ease;
font-size: 16px;
white-space: nowrap;
}
.tab:hover {
background: rgba(46, 125, 50, 0.1);
color: #25D366;
}
.tab.active {
background: #BAF7D0;
color: #0E4322;
box-shadow: 0 2px 4px rgba(46, 125, 50, 0.3);
}
body {
font-family: Arial, sans-serif;
padding: 24px;
background: #f7f8fb;
color: #222;
}
.card {
background: #fff;
padding: 16px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
margin: auto;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 12px;
}
th,
td {
padding: 10px 12px;
border-bottom: 1px solid #eee;
text-align: left;
font-size: 14px;
}
th {
background: #fafafa;
font-weight: 600;
}
.controls {
display: flex;
gap: 12px;
align-items: center;
flex-wrap: wrap;
}
.pagination {
display: flex;
gap: 6px;
align-items: center;
margin-left: auto;
flex-wrap: wrap;
}
.btn {
padding: 6px 10px;
border-radius: 6px;
border: 1px solid #ddd;
background: #fff;
cursor: pointer;
user-select: none;
}
.btn:disabled {
opacity: 0.5;
cursor: default;
}
.btn.primary {
background: #007bff;
color: #fff;
border-color: #007bff;
}
.btn.whatsapp {
background: #25D366;
color: #fff;
border-color: #25D366;
}
.status-btn {
padding: 4px 8px;
border-radius: 6px;
border: 1px solid #ccc;
cursor: pointer;
}
.status-0 {
background: red;
color: #fff;
}
.status-1 {
background: #2f9e44;
color: #fff;
border-color: #2f9e44;
}
.btn.active {
background: #007bff;
color: #fff;
border-color: #007bff;
}
select,
input[type="number"] {
padding: 6px;
border-radius: 6px;
border: 1px solid #ddd;
}
.small {
font-size: 13px;
color: #666;
}
@media (max-width: 640px) {
.tabs {
flex-wrap: wrap;
width: 100%;
}
.tab {
flex: 1;
min-width: 120px;
text-align: center;
}
.controls {
flex-direction: column;
align-items: flex-start;
}
.pagination {
margin-left: 0;
}
}
#noteModal {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.45);
align-items: center;
justify-content: center;
z-index: 9999;
}
#noteModal .dialog {
background: #fff;
padding: 16px;
border-radius: 8px;
width: 90%;
max-width: 520px;
box-sizing: border-box;
}
#noteModal textarea {
width: 100%;
min-width: 60%;
max-width: 100%;
min-height: 150px;
box-sizing: border-box;
padding: 8px;
border-radius: 6px;
border: 1px solid #ddd;
font-size: 14px;
}
.toast {
position: fixed;
top: -20px;
left: 50%;
transform: translateX(-50%);
background: #4caf50;
color: #fff;
padding: 10px 16px;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
font-size: 16px;
opacity: 0;
transition: all 0.3s ease;
z-index: 10000;
pointer-events: none;
}
.toast.show {
opacity: 1;
top: 20px;
}
table th,
table td {
text-align: center;
}
/* 登录验证样式 */
#loginCheckModal {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.7);
align-items: center;
justify-content: center;
z-index: 99999;
}
#loginCheckModal .dialog {
background: #fff;
padding: 30px;
border-radius: 12px;
width: 90%;
max-width: 400px;
text-align: center;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
#loginCheckModal h3 {
margin: 0 0 16px;
color: #e74c3c;
font-size: 20px;
}
#loginCheckModal p {
margin: 0 0 24px;
color: #666;
line-height: 1.5;
}
#loginCheckModal .btn {
padding: 10px 20px;
font-size: 16px;
}
</style>
</head>
<body>
<!-- 登录验证模态框 -->
<div id="loginCheckModal">
<div class="dialog">
<h3>访问被拒绝</h3>
<p>您尚未登录或登录已过期,请先登录系统。</p>
<button class="btn primary" id="goToLoginBtn">前往登录页面</button>
</div>
</div>
<div class="tab-container">
<div class="tabs">
<button class="tab" data-tab="win-us-stock">赢在美股导流</button>
<button class="tab active" data-tab="deepchart">DeepChart导流</button>
<button class="tab" data-tab="content-config">内容配置</button>
</div>
</div>
<div class="card">
<table aria-describedby="tableDesc">
<thead>
<tr>
<th style="width: 30px">#</th>
<th style="width: 60px">姓名</th>
<th style="width: 80px">WhatsApp</th>
<th style="width: 90px">国家/地区代码</th>
<th style="width: 110px">电话号码</th>
<th style="width: 110px">微信ID</th>
<th style="width: 110px">邮箱</th>
<th style="width: 70px">获客来源</th>
<th style="width: 130px">来源地址</th>
<th style="width: 150px">添加时间</th>
<th style="width: 90px">是否联系</th>
<th style="width: 90px">备注</th>
<th style="width: 100px">操作</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
<div class="controls" style="margin-bottom: 8px; margin-top: 12px">
<div class="small">
每页显示
<select id="pageSizeSelect">
<option value="20" selected>20</option>
<option value="50">50</option>
<option value="100">100</option>
<option value="200">200</option>
</select>
</div>
<div class="small"><span id="totalCount">0</span></div>
<div class="pagination" id="pagination"></div>
</div>
<!-- 备注编辑模态 -->
<div id="noteModal">
<div class="dialog">
<h3 style="margin: 0 0 8px">编辑备注</h3>
<textarea id="noteTextarea" rows="6" placeholder="输入备注..."></textarea>
<div style="
margin-top: 10px;
display: flex;
justify-content: flex-end;
gap: 8px;
">
<button class="btn" id="noteCancelBtn">取消</button>
<button class="btn primary" id="noteSaveBtn">保存</button>
</div>
</div>
</div>
</div>
<div id="toast" class="toast"></div>
<script type="module">
import { getMemberListApi, updateMemberStateApi, editMemberNoteApi } from './src/api/member.js'
// 登录验证函数
function checkLoginStatus() {
const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true';
const loginTime = parseInt(localStorage.getItem('loginTime'));
const currentTime = new Date().getTime();
const hoursDiff = (currentTime - loginTime) / (1000 * 60 * 60);
// 检查是否登录且登录时间在24小时内
if (!isLoggedIn || hoursDiff >= 24) {
// 清除过期的登录状态
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('loginTime');
return false;
}
return true;
}
// 显示登录验证模态框
function showLoginCheckModal() {
const modal = document.getElementById('loginCheckModal');
modal.style.display = 'flex';
}
// 隐藏登录验证模态框
function hideLoginCheckModal() {
const modal = document.getElementById('loginCheckModal');
modal.style.display = 'none';
}
// 跳转到登录页面
function redirectToLogin() {
window.location.href = 'login-management.html';
}
// 检查登录状态
if (!checkLoginStatus()) {
showLoginCheckModal();
}
// 绑定前往登录页面按钮事件
document.getElementById('goToLoginBtn').addEventListener('click', redirectToLogin);
// 初始化Tab切换
initTabs();
// Tab切换功能
function initTabs() {
const tabs = document.querySelectorAll('.tab');
tabs.forEach(tab => {
tab.addEventListener('click', function() {
tabs.forEach(t => t.classList.remove('active'));
this.classList.add('active');
const tabId = this.getAttribute('data-tab');
switch(tabId) {
case 'win-us-stock':
// 当前页面,不需要跳转
console.log('切换到赢在美股导流页面');
window.location.href = 'hcdbqb-management.html';
break;
case 'deepchart':
// 跳转到DeepChart导流页面
console.log('切换到DeepChart导流页面');
// window.location.href = 'deepchart-management.html';
break;
case 'content-config':
// 跳转到内容配置页面
console.log('切换到内容配置页面');
window.location.href = 'content-config.html';
break;
}
});
});
}
let state = {
pageSize: 20,
currentPage: 1,
total: 0,
items: []
};
// DOM
const tableBody = document.getElementById("tableBody");
const paginationEl = document.getElementById("pagination");
const totalCountEl = document.getElementById("totalCount");
const currentPageEl = document.getElementById("currentPage");
const showingRangeEl = document.getElementById("showingRange");
const pageSizeSelect = document.getElementById("pageSizeSelect");
const gotoInput = document.getElementById("gotoInput");
const noteModal = document.getElementById("noteModal");
const noteTextarea = document.getElementById("noteTextarea");
const noteCancelBtn = document.getElementById("noteCancelBtn");
const noteSaveBtn = document.getElementById("noteSaveBtn");
const toastEl = document.getElementById("toast");
let editingRowId = null;
function showToast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
setTimeout(() => toastEl.classList.remove("show"), 1000);
}
async function fetchPage(page, pageSize) {
// 每次请求数据前都检查登录状态
if (!checkLoginStatus()) {
showLoginCheckModal();
return;
}
try {
const res = await getMemberListApi({
source: 1,
page: page,
page_size: pageSize,
})
if (typeof res.code !== "undefined" && res.code !== 200) {
throw new Error(res.msg || "接口返回错误");
}
console.log(res)
const payload = res.data || {};
state.items = payload.list;
state.total = payload.total;
} catch (err) {
alert("获取数据失败");
state.total = 0;
}
}
// 渲染表格行
function renderTable() {
const startIndex = (state.currentPage - 1) * state.pageSize;
const items = state.items || [];
tableBody.innerHTML = items
.map((item, idx) => {
const serial = startIndex + idx + 1;
const statusClass = item.isRelated ? "status-1" : "status-0";
const statusText = item.isRelated ? "已联系" : "未联系";
// 构建WhatsApp链接 - 移除国家代码中的+号
const cleanCode = (item.code || '').replace(/\+/g, '');
const whatsappPhone = cleanCode + (item.telephone || '');
const whatsappUrl = `https://api.whatsapp.com/send?phone=${encodeURIComponent(whatsappPhone)}&text=${encodeURIComponent('hello欢迎来到赢在美股')}`;
return `
<tr>
<td>${serial}</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.telephone || "")}</td>
<td>${escapeHtml(item.wechat || "")}</td>
<td>${escapeHtml(item.email || "")}</td>
<td>${escapeHtml(item.type === 0 ? "其它" : item.type === 1 ? "视频" : item.type === 2 ? "直播" : item.type === 3 ? "帖子" : "其它")}</td>
<td>${escapeHtml(item.url || "")}</td>
<td>${escapeHtml(item.createdAt || "")}</td>
<td>
<button class="status-btn ${statusClass}" data-action="toggle" data-id="${item.id
}">${statusText}</button>
</td>
<td>${escapeHtml(item.note || "")}</td>
<td>
<button class="btn" data-action="editNote" data-id="${item.id
}">编辑备注</button>
</td>
</tr>
`;
})
.join("");
totalCountEl.textContent = state.total;
}
tableBody.addEventListener("click", async (e) => {
// 每次操作前检查登录状态
if (!checkLoginStatus()) {
showLoginCheckModal();
return;
}
// 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"]');
if (toggler) {
const id = toggler.getAttribute("data-id");
await handleToggle(id, toggler);
return;
}
// 编辑备注
const editBtn = e.target.closest('[data-action="editNote"]');
if (editBtn) {
const id = editBtn.getAttribute("data-id");
openNoteModal(id);
return;
}
});
// ---------- WhatsApp跳转 ----------
function handleWhatsApp(id) {
const item = state.items.find((it) => String(it.id) === String(id));
if (!item) return;
// 移除国家代码中的+号
const cleanCode = (item.code || '').replace(/\+/g, '');
const whatsappPhone = cleanCode + (item.telephone || '');
const whatsappUrl = `https://api.whatsapp.com/send?phone=${encodeURIComponent(whatsappPhone)}&text=${encodeURIComponent('hello欢迎来到赢在美股')}`;
window.open(whatsappUrl, '_blank');
}
// ---------- 切换联系状态 ----------
async function handleToggle(id, btnEl) {
const item = state.items.find((it) => String(it.id) === String(id));
if (!item) return;
const prev = item.isRelated;
const newVal = prev ? 0 : 1;
item.isRelated = newVal;
btnEl.textContent = newVal ? "已联系" : "未联系";
btnEl.classList.toggle("status-1", !!newVal);
btnEl.classList.toggle("status-0", !newVal);
try {
const res = await updateMemberStateApi({ id: item.id, state: newVal })
if (typeof res.code !== "undefined" && res.code !== 200) {
throw new Error(res.msg || "更新失败");
}
showToast("状态修改成功");
} catch (err) {
item.isRelated = prev;
btnEl.textContent = prev ? "已联系" : "未联系";
btnEl.classList.toggle("status-1", !!prev);
btnEl.classList.toggle("status-0", !prev);
alert("更新失败");
}
}
// ---------- 备注模态相关 ----------
function openNoteModal(id) {
// 打开模态框前检查登录状态
if (!checkLoginStatus()) {
showLoginCheckModal();
return;
}
const item = state.items.find((it) => String(it.id) === String(id));
if (!item) return;
editingRowId = id;
noteTextarea.value = item.note || "";
noteModal.style.display = "flex";
noteTextarea.focus();
}
function closeNoteModal() {
editingRowId = null;
noteTextarea.value = "";
noteModal.style.display = "none";
}
noteCancelBtn.addEventListener("click", closeNoteModal);
noteSaveBtn.addEventListener("click", async () => {
// 保存前检查登录状态
if (!checkLoginStatus()) {
showLoginCheckModal();
return;
}
if (!editingRowId) return closeNoteModal();
const newNote = noteTextarea.value.trim();
const item = state.items.find((it) => String(it.id) === String(editingRowId));
if (!item) return closeNoteModal();
try {
const res = await editMemberNoteApi({ id: item.id, note: newNote })
if (typeof res.code !== "undefined" && res.code !== 200) {
throw new Error(res.msg || "保存失败");
}
const prevNote = item.note;
item.note = newNote;
renderTable();
showToast("备注保存成功");
} catch (err) {
alert("保存备注失败");
} finally {
closeNoteModal();
}
});
// 渲染分页控件(页码、上一页、下一页)
function renderPagination() {
const totalPages = Math.max(1, Math.ceil(state.total / state.pageSize));
const current = Math.min(Math.max(1, state.currentPage), totalPages);
state.currentPage = current;
const pages = buildPageList(current, totalPages, 2);
let html = "";
html += `<button class="btn" data-action="prev" ${current === 1 ? "disabled" : ""
}>上一页</button>`;
pages.forEach((p) => {
if (p === "...") {
html += `<span class="small" style="padding:6px 8px">...</span>`;
} else {
html += `<button class="btn ${p === current ? "active" : ""
}" data-page="${p}">${p}</button>`;
}
});
html += `<button class="btn" data-action="next" ${current === totalPages ? "disabled" : ""
}>下一页</button>`;
paginationEl.innerHTML = html;
}
function buildPageList(current, total, delta) {
const pages = [];
const left = Math.max(1, current - delta);
const right = Math.min(total, current + delta);
if (left > 1) {
pages.push(1);
if (left > 2) pages.push("...");
}
for (let i = left; i <= right; i++) pages.push(i);
if (right < total) {
if (right < total - 1) pages.push("...");
pages.push(total);
}
return pages;
}
paginationEl.addEventListener("click", (e) => {
// 分页操作前检查登录状态
if (!checkLoginStatus()) {
showLoginCheckModal();
return;
}
const btn = e.target.closest("button");
if (!btn) return;
const action = btn.getAttribute("data-action");
if (action === "prev") {
if (state.currentPage > 1) state.currentPage--;
} else if (action === "next") {
const totalPages = Math.max(
1,
Math.ceil(state.total / state.pageSize)
);
if (state.currentPage < totalPages) state.currentPage++;
} else {
const p = Number(btn.getAttribute("data-page"));
if (!isNaN(p)) state.currentPage = p;
}
update();
});
// 每页条数变更
pageSizeSelect.addEventListener("change", () => {
// 操作前检查登录状态
if (!checkLoginStatus()) {
showLoginCheckModal();
return;
}
const newSize = parseInt(pageSizeSelect.value, 10);
state.pageSize = newSize;
state.currentPage = 1;
update();
gotoInput.value = "";
});
async function update() {
// 更新数据前检查登录状态
if (!checkLoginStatus()) {
showLoginCheckModal();
return;
}
const p = Math.max(1, state.currentPage);
state.currentPage = p;
await fetchPage(state.currentPage, state.pageSize);
renderTable();
renderPagination();
}
// 简单安全的文本转义(防 XSS)
function escapeHtml(s) {
return String(s)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
// 首次渲染 - 检查登录状态
if (checkLoginStatus()) {
update();
} else {
showLoginCheckModal();
}
</script>
</body>
</html>

97
hcdbqb-management.html

@ -6,6 +6,43 @@
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>管理后台</title> <title>管理后台</title>
<style> <style>
.tab-container {
max-width: 1200px;
margin: 0 0 12px 0;
padding: 0 16px;
}
.tabs {
display: flex;
gap: 50px;
padding: 4px;
border-radius: 8px;
width: fit-content;
}
.tab {
padding: 10px 20px;
border-radius: 6px;
border: none;
background: transparent;
color: #131212;
font-weight: 700;
cursor: pointer;
transition: all 0.3s ease;
font-size: 16px;
white-space: nowrap;
}
.tab:hover {
background: rgba(46, 125, 50, 0.1);
color: #25D366;
}
.tab.active {
background: #BAF7D0;
color: #0E4322;
box-shadow: 0 2px 4px rgba(46, 125, 50, 0.3);
}
body { body {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
padding: 24px; padding: 24px;
@ -118,6 +155,16 @@
} }
@media (max-width: 640px) { @media (max-width: 640px) {
.tabs {
flex-wrap: wrap;
width: 100%;
}
.tab {
flex: 1;
min-width: 120px;
text-align: center;
}
.controls { .controls {
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
@ -235,24 +282,31 @@
<button class="btn primary" id="goToLoginBtn">前往登录页面</button> <button class="btn primary" id="goToLoginBtn">前往登录页面</button>
</div> </div>
</div> </div>
<div class="tab-container">
<div class="tabs">
<button class="tab active" data-tab="win-us-stock">赢在美股导流</button>
<button class="tab" data-tab="deepchart">DeepChart导流</button>
<button class="tab" data-tab="content-config">内容配置</button>
</div>
</div>
<div class="card"> <div class="card">
<table aria-describedby="tableDesc"> <table aria-describedby="tableDesc">
<thead> <thead>
<tr> <tr>
<th style="width: 30px">#</th>
<th style="width: 20px">#</th>
<th style="width: 60px">姓名</th> <th style="width: 60px">姓名</th>
<th style="width: 80px">WhatsApp</th> <th style="width: 80px">WhatsApp</th>
<th style="width: 90px">国家/地区代码</th>
<th style="width: 80px">国家/地区代码</th>
<th style="width: 110px">电话号码</th> <th style="width: 110px">电话号码</th>
<th style="width: 110px">微信ID</th> <th style="width: 110px">微信ID</th>
<th style="width: 110px">邮箱</th> <th style="width: 110px">邮箱</th>
<th style="width: 70px">获客来源</th>
<th style="width: 100px">报名意向</th>
<th style="width: 80px">获客来源</th>
<th style="width: 130px">来源地址</th> <th style="width: 130px">来源地址</th>
<th style="width: 150px">添加时间</th> <th style="width: 150px">添加时间</th>
<th style="width: 90px">是否联系</th> <th style="width: 90px">是否联系</th>
<th style="width: 90px">备注</th> <th style="width: 90px">备注</th>
<th style="width: 100px">操作</th>
<th style="width: 70px">操作</th>
</tr> </tr>
</thead> </thead>
<tbody id="tableBody"></tbody> <tbody id="tableBody"></tbody>
@ -336,6 +390,37 @@
// 绑定前往登录页面按钮事件 // 绑定前往登录页面按钮事件
document.getElementById('goToLoginBtn').addEventListener('click', redirectToLogin); document.getElementById('goToLoginBtn').addEventListener('click', redirectToLogin);
// 初始化Tab切换
initTabs();
// Tab切换功能
function initTabs() {
const tabs = document.querySelectorAll('.tab');
tabs.forEach(tab => {
tab.addEventListener('click', function() {
tabs.forEach(t => t.classList.remove('active'));
this.classList.add('active');
const tabId = this.getAttribute('data-tab');
switch(tabId) {
case 'win-us-stock':
console.log('切换到赢在美股导流页面');
// window.location.href = 'hcdbqb-management.html';
break;
case 'deepchart':
// 跳转到DeepChart导流页面
console.log('切换到DeepChart导流页面');
window.location.href = 'deepchart-management.html';
break;
case 'content-config':
// 跳转到内容配置页面
console.log('切换到内容配置页面');
window.location.href = 'content-config.html';
break;
}
});
});
}
let state = { let state = {
pageSize: 20, pageSize: 20,
currentPage: 1, currentPage: 1,
@ -373,6 +458,7 @@
try { try {
const res = await getMemberListApi({ const res = await getMemberListApi({
source: 0,
page: page, page: page,
page_size: pageSize, page_size: pageSize,
}) })
@ -415,6 +501,7 @@
<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.registration_intention || "")}</td>
<td>${escapeHtml(item.type === 0 ? "其它" : item.type === 1 ? "视频" : item.type === 2 ? "直播" : item.type === 3 ? "帖子" : "其它")}</td> <td>${escapeHtml(item.type === 0 ? "其它" : item.type === 1 ? "视频" : item.type === 2 ? "直播" : item.type === 3 ? "帖子" : "其它")}</td>
<td>${escapeHtml(item.url || "")}</td> <td>${escapeHtml(item.url || "")}</td>
<td>${escapeHtml(item.createdAt || "")}</td> <td>${escapeHtml(item.createdAt || "")}</td>

30
src/api/member.js

@ -22,7 +22,8 @@ export function getImageApi(data) {
export function getMemberListApi(data){ export function getMemberListApi(data){
return request({ return request({
url: `${API_BASE_URL}/api/member/getList`,
// url: `${API_BASE_URL}/api/member/getList`,
url: `http://g8ec6e97.natappfree.cc/api/member/getList`,
method: "post", method: "post",
data: data, data: data,
}) })
@ -41,4 +42,31 @@ export function editMemberNoteApi(data){
method: "post", method: "post",
data: data, data: data,
}) })
}
export function saveContentConfigApi(data) {
return request({
// url: `${API_BASE_URL}/api/config/update`,
url: `http://g8ec6e97.natappfree.cc/api/config/update`,
method: "post",
data: data,
});
}
export function getImage1Api(data) {
return request({
// url: `${API_BASE_URL}/api/config/get`,
url: `http://g8ec6e97.natappfree.cc/api/config/get`,
method: "post",
data: data,
});
}
export function uploadFileApi(data) {
return request({
// url: `${API_BASE_URL}/api/config/get`,
url: `http://39.101.133.168:8828/cloud/api/file/upload`,
method: "post",
data: data,
});
} }
Loading…
Cancel
Save