18 Commits
0bd8fb4e07
...
fe3da7705a
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
fe3da7705a |
修改配置接口未传视频修改
|
2 months ago |
|
|
4e6f0ce250 |
修改视频url由后端提供
|
2 months ago |
|
|
2a6c4b8d7d |
更新跳转功能
|
2 months ago |
|
|
5356d190d9 |
打包命令修改
|
2 months ago |
|
|
74ceee9090 |
修改一个图片
|
2 months ago |
|
|
0515acfe2f |
修改到正式的url
|
2 months ago |
|
|
e8ea3f3ce1 |
先用内网穿透测试
|
2 months ago |
|
|
a5ffe7b585 |
写完跳转
|
2 months ago |
|
|
e62ef5b43b |
部分跳转功能实现
|
2 months ago |
|
|
19b5b3420c |
修改图片
|
2 months ago |
|
|
d250946c5d |
Merge branch 'fanwenqing/feature-20260202203448-美股市场改版前台' into milestone-20260202-美股市场改版
|
2 months ago |
|
|
d5b2db7691 |
注册接口对接
|
2 months ago |
|
|
9632b2143f |
写好样式开始写跳转逻辑
|
2 months ago |
|
|
af6dea92fc |
Merge branch 'lizida/feature-20260202202735-milestone-20260202-美股市场改版' into milestone-20260202-美股市场改版
|
2 months ago |
|
|
220c167c73 |
美股市场落地页后台
|
2 months ago |
|
|
2b77619f2f |
写好操作指南样式
|
2 months ago |
|
|
ab6cf983ee |
写好主页面
|
2 months ago |
|
|
6b6f72d081 |
写完首页一部分样式
|
2 months ago |
6 changed files with 2848 additions and 456 deletions
-
654content-config.html
-
760deepchart-management.html
-
97hcdbqb-management.html
-
1751index.html
-
38src/api/member.js
-
4vite.config.js
@ -0,0 +1,654 @@ |
|||||
|
<!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 = ''; |
||||
|
let currentVideoUrl = ''; |
||||
|
|
||||
|
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; |
||||
|
currentVideoUrl = res.data.video; |
||||
|
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); |
||||
|
formData.append('video', currentVideoUrl); |
||||
|
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> |
||||
@ -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, "&") |
||||
|
.replace(/</g, "<") |
||||
|
.replace(/>/g, ">") |
||||
|
.replace(/"/g, """) |
||||
|
.replace(/'/g, "'"); |
||||
|
} |
||||
|
|
||||
|
// 首次渲染 - 检查登录状态 |
||||
|
if (checkLoginStatus()) { |
||||
|
update(); |
||||
|
} else { |
||||
|
showLoginCheckModal(); |
||||
|
} |
||||
|
</script> |
||||
|
</body> |
||||
|
|
||||
|
</html> |
||||
1751
index.html
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
Write
Preview
Loading…
Cancel
Save
Reference in new issue