市场夺宝奇兵
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

653 lines
16 KiB

  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="utf-8" />
  5. <meta name="viewport" content="width=device-width,initial-scale=1" />
  6. <title>管理后台</title>
  7. <style>
  8. .tab-container {
  9. max-width: 1200px;
  10. margin: 0 0 12px 0;
  11. padding: 0 16px;
  12. }
  13. .tabs {
  14. display: flex;
  15. gap: 50px;
  16. padding: 4px;
  17. border-radius: 8px;
  18. width: fit-content;
  19. }
  20. .tab {
  21. padding: 10px 20px;
  22. border-radius: 6px;
  23. border: none;
  24. background: transparent;
  25. color: #131212;
  26. font-weight: 700;
  27. cursor: pointer;
  28. transition: all 0.3s ease;
  29. font-size: 16px;
  30. white-space: nowrap;
  31. }
  32. .tab:hover {
  33. background: rgba(46, 125, 50, 0.1);
  34. color: #25D366;
  35. }
  36. .tab.active {
  37. background: #BAF7D0;
  38. color: #0E4322;
  39. box-shadow: 0 2px 4px rgba(46, 125, 50, 0.3);
  40. }
  41. body {
  42. font-family: Arial, sans-serif;
  43. padding: 24px;
  44. background: #f7f8fb;
  45. color: #222;
  46. }
  47. .card {
  48. background: #fff;
  49. padding: 16px;
  50. border-radius: 8px;
  51. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  52. margin: auto;
  53. min-height: 700px;
  54. border: 5px solid #D9D9D9;
  55. display: flex;
  56. flex-direction: column;
  57. position: relative;
  58. }
  59. table {
  60. width: 100%;
  61. border-collapse: collapse;
  62. margin-top: 12px;
  63. }
  64. th,
  65. td {
  66. padding: 10px 12px;
  67. border-bottom: 1px solid #eee;
  68. text-align: left;
  69. font-size: 14px;
  70. }
  71. th {
  72. background: #fafafa;
  73. font-weight: 600;
  74. }
  75. .controls {
  76. display: flex;
  77. gap: 12px;
  78. align-items: center;
  79. flex-wrap: wrap;
  80. }
  81. .pagination {
  82. display: flex;
  83. gap: 6px;
  84. align-items: center;
  85. margin-left: auto;
  86. flex-wrap: wrap;
  87. }
  88. .btn {
  89. padding: 6px 10px;
  90. border-radius: 6px;
  91. border: 1px solid #ddd;
  92. background: #fff;
  93. cursor: pointer;
  94. user-select: none;
  95. }
  96. .btn:disabled {
  97. opacity: 0.5;
  98. cursor: default;
  99. }
  100. .btn.primary {
  101. background: #007bff;
  102. color: #fff;
  103. border-color: #007bff;
  104. }
  105. .btn.whatsapp {
  106. background: #25D366;
  107. color: #fff;
  108. border-color: #25D366;
  109. }
  110. .status-btn {
  111. padding: 4px 8px;
  112. border-radius: 6px;
  113. border: 1px solid #ccc;
  114. cursor: pointer;
  115. }
  116. .status-0 {
  117. background: red;
  118. color: #fff;
  119. }
  120. .status-1 {
  121. background: #2f9e44;
  122. color: #fff;
  123. border-color: #2f9e44;
  124. }
  125. .btn.active {
  126. background: #007bff;
  127. color: #fff;
  128. border-color: #007bff;
  129. }
  130. select,
  131. input[type="number"] {
  132. padding: 6px;
  133. border-radius: 6px;
  134. border: 1px solid #ddd;
  135. }
  136. .small {
  137. font-size: 13px;
  138. color: #666;
  139. }
  140. @media (max-width: 640px) {
  141. .tabs {
  142. flex-wrap: wrap;
  143. width: 100%;
  144. }
  145. .tab {
  146. flex: 1;
  147. min-width: 120px;
  148. text-align: center;
  149. }
  150. .controls {
  151. flex-direction: column;
  152. align-items: flex-start;
  153. }
  154. .pagination {
  155. margin-left: 0;
  156. }
  157. }
  158. #noteModal {
  159. display: none;
  160. position: fixed;
  161. inset: 0;
  162. background: rgba(0, 0, 0, 0.45);
  163. align-items: center;
  164. justify-content: center;
  165. z-index: 9999;
  166. }
  167. #noteModal .dialog {
  168. background: #fff;
  169. padding: 16px;
  170. border-radius: 8px;
  171. width: 90%;
  172. max-width: 520px;
  173. box-sizing: border-box;
  174. }
  175. #noteModal textarea {
  176. width: 100%;
  177. min-width: 60%;
  178. max-width: 100%;
  179. min-height: 150px;
  180. box-sizing: border-box;
  181. padding: 8px;
  182. border-radius: 6px;
  183. border: 1px solid #ddd;
  184. font-size: 14px;
  185. }
  186. .toast {
  187. position: fixed;
  188. top: -20px;
  189. left: 50%;
  190. transform: translateX(-50%);
  191. background: #4caf50;
  192. color: #fff;
  193. padding: 10px 16px;
  194. border-radius: 6px;
  195. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
  196. font-size: 16px;
  197. opacity: 0;
  198. transition: all 0.3s ease;
  199. z-index: 10000;
  200. pointer-events: none;
  201. }
  202. .toast.show {
  203. opacity: 1;
  204. top: 20px;
  205. }
  206. table th,
  207. table td {
  208. text-align: center;
  209. }
  210. /* 登录验证样式 */
  211. #loginCheckModal {
  212. display: none;
  213. position: fixed;
  214. inset: 0;
  215. background: rgba(0, 0, 0, 0.7);
  216. align-items: center;
  217. justify-content: center;
  218. z-index: 99999;
  219. }
  220. #loginCheckModal .dialog {
  221. background: #fff;
  222. padding: 30px;
  223. border-radius: 12px;
  224. width: 90%;
  225. max-width: 400px;
  226. text-align: center;
  227. box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
  228. }
  229. #loginCheckModal h3 {
  230. margin: 0 0 16px;
  231. color: #e74c3c;
  232. font-size: 20px;
  233. }
  234. #loginCheckModal p {
  235. margin: 0 0 24px;
  236. color: #666;
  237. line-height: 1.5;
  238. }
  239. #loginCheckModal .btn {
  240. padding: 10px 20px;
  241. font-size: 16px;
  242. }
  243. /* 图片上传组件样式 */
  244. .config-section {
  245. display: flex;
  246. align-items: center; /* 垂直居中 */
  247. gap: 30px;
  248. margin-bottom: 40px;
  249. padding: 20px 40px;
  250. }
  251. .section-label {
  252. font-weight: bold;
  253. min-width: 60px;
  254. font-size: 20px;
  255. color: #333;
  256. }
  257. .image-uploader {
  258. position: relative;
  259. /* 图片尺寸需要修改确认几比几 */
  260. width: 240px;
  261. height: 160px;
  262. border: 1px dashed #d9d9d9;
  263. border-radius: 6px;
  264. display: flex;
  265. align-items: center;
  266. justify-content: center;
  267. overflow: hidden;
  268. background: #fff;
  269. transition: all 0.3s;
  270. cursor: pointer;
  271. }
  272. .image-uploader:hover {
  273. border-color: #BAF7D0;
  274. }
  275. .upload-btn {
  276. width: 100%;
  277. height: 100%;
  278. display: flex;
  279. align-items: center;
  280. justify-content: center;
  281. cursor: pointer;
  282. font-size: 36px;
  283. color: #bfbfbf; /* 更淡的灰色 */
  284. font-weight: lighter;
  285. }
  286. .preview-container {
  287. width: 100%;
  288. height: 100%;
  289. position: relative;
  290. }
  291. .preview-container img {
  292. width: 100%;
  293. height: 100%;
  294. object-fit: cover;
  295. }
  296. .delete-overlay {
  297. position: absolute;
  298. top: 0;
  299. left: 0;
  300. width: 100%;
  301. height: 100%;
  302. background: rgba(0,0,0,0.5);
  303. display: none;
  304. align-items: center;
  305. justify-content: center;
  306. transition: all 0.3s;
  307. }
  308. .preview-container:hover .delete-overlay {
  309. display: flex;
  310. }
  311. .delete-btn {
  312. background: transparent;
  313. color: white;
  314. border: 1px solid white;
  315. padding: 6px 16px;
  316. border-radius: 4px;
  317. cursor: pointer;
  318. font-size: 14px;
  319. transition: all 0.3s;
  320. }
  321. .delete-btn:hover {
  322. background: rgba(255,255,255,0.2);
  323. }
  324. .action-bar {
  325. display: flex;
  326. justify-content: center;
  327. margin-top: auto; /* 关键:推到底部 */
  328. padding-bottom: 40px; /* 留出底部间距 */
  329. }
  330. #saveConfigBtn {
  331. width: 130px;
  332. height: 60px;
  333. font-size: 30px;
  334. background: #BAF7D0;
  335. color: #0D4221;
  336. border: none;
  337. font-weight: 600;
  338. cursor: pointer;
  339. transition: all 0.3s;
  340. border-radius: 6px;
  341. letter-spacing: 2px;
  342. }
  343. #saveConfigBtn:hover {
  344. background: #a5e6bc;
  345. box-shadow: 0 4px 12px rgba(186, 247, 208, 0.4);
  346. }
  347. </style>
  348. </head>
  349. <body>
  350. <!-- 登录验证模态框 -->
  351. <div id="loginCheckModal">
  352. <div class="dialog">
  353. <h3>访问被拒绝</h3>
  354. <p>您尚未登录或登录已过期,请先登录系统。</p>
  355. <button class="btn primary" id="goToLoginBtn">前往登录页面</button>
  356. </div>
  357. </div>
  358. <div class="tab-container">
  359. <div class="tabs">
  360. <button class="tab" data-tab="win-us-stock">赢在美股导流</button>
  361. <button class="tab" data-tab="deepchart">DeepChart导流</button>
  362. <button class="tab active" data-tab="content-config">内容配置</button>
  363. </div>
  364. </div>
  365. <div class="card">
  366. <div class="config-section">
  367. <div class="section-label">课程图</div>
  368. <div class="image-uploader" id="imageUploader">
  369. <div class="upload-btn" id="uploadBtn">
  370. <span>+</span>
  371. <input type="file" id="fileInput" accept="image/*" hidden>
  372. </div>
  373. <div class="preview-container" id="previewContainer" style="display: none;">
  374. <img id="previewImage" src="" alt="Preview">
  375. <div class="delete-overlay">
  376. <button class="delete-btn" id="deleteBtn">删除</button>
  377. </div>
  378. </div>
  379. </div>
  380. </div>
  381. <div class="action-bar">
  382. <button id="saveConfigBtn">保存</button>
  383. </div>
  384. </div>
  385. <div id="toast" class="toast"></div>
  386. <script type="module">
  387. import { saveContentConfigApi, getImage1Api, uploadFileApi } from './src/api/member.js'
  388. // 登录验证函数
  389. function checkLoginStatus() {
  390. const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true';
  391. const loginTime = parseInt(localStorage.getItem('loginTime'));
  392. const currentTime = new Date().getTime();
  393. const hoursDiff = (currentTime - loginTime) / (1000 * 60 * 60);
  394. // 检查是否登录且登录时间在24小时内
  395. if (!isLoggedIn || hoursDiff >= 24) {
  396. // 清除过期的登录状态
  397. localStorage.removeItem('isLoggedIn');
  398. localStorage.removeItem('loginTime');
  399. return false;
  400. }
  401. return true;
  402. }
  403. // 显示登录验证模态框
  404. function showLoginCheckModal() {
  405. const modal = document.getElementById('loginCheckModal');
  406. modal.style.display = 'flex';
  407. }
  408. // 隐藏登录验证模态框
  409. function hideLoginCheckModal() {
  410. const modal = document.getElementById('loginCheckModal');
  411. modal.style.display = 'none';
  412. }
  413. // 跳转到登录页面
  414. function redirectToLogin() {
  415. window.location.href = 'login-management.html';
  416. }
  417. // 检查登录状态
  418. if (!checkLoginStatus()) {
  419. showLoginCheckModal();
  420. }
  421. // 绑定前往登录页面按钮事件
  422. document.getElementById('goToLoginBtn').addEventListener('click', redirectToLogin);
  423. // 初始化Tab切换
  424. initTabs();
  425. // Tab切换功能
  426. function initTabs() {
  427. const tabs = document.querySelectorAll('.tab');
  428. tabs.forEach(tab => {
  429. tab.addEventListener('click', function() {
  430. tabs.forEach(t => t.classList.remove('active'));
  431. this.classList.add('active');
  432. const tabId = this.getAttribute('data-tab');
  433. switch(tabId) {
  434. case 'win-us-stock':
  435. // 当前页面,不需要跳转
  436. console.log('切换到赢在美股导流页面');
  437. window.location.href = 'hcdbqb-management.html';
  438. break;
  439. case 'deepchart':
  440. // 跳转到DeepChart导流页面
  441. console.log('切换到DeepChart导流页面');
  442. window.location.href = 'deepchart-management.html';
  443. break;
  444. case 'content-config':
  445. // 跳转到内容配置页面
  446. console.log('切换到内容配置页面');
  447. // window.location.href = 'content-config.html';
  448. break;
  449. }
  450. });
  451. });
  452. }
  453. // --- 新增图片上传逻辑 ---
  454. const uploadBtn = document.getElementById('uploadBtn');
  455. const fileInput = document.getElementById('fileInput');
  456. const previewContainer = document.getElementById('previewContainer');
  457. const previewImage = document.getElementById('previewImage');
  458. const deleteBtn = document.getElementById('deleteBtn');
  459. const saveConfigBtn = document.getElementById('saveConfigBtn');
  460. const toastEl = document.getElementById('toast');
  461. let currentFile = null;
  462. let currentImageUrl = '';
  463. let currentVideoUrl = '';
  464. function showToast(msg) {
  465. toastEl.textContent = msg;
  466. toastEl.classList.add("show");
  467. setTimeout(() => toastEl.classList.remove("show"), 1000);
  468. }
  469. // 页面加载时获取当前配置
  470. if (checkLoginStatus()) {
  471. loadCurrentConfig();
  472. }
  473. async function loadCurrentConfig() {
  474. // 每次请求数据前都检查登录状态
  475. if (!checkLoginStatus()) {
  476. showLoginCheckModal();
  477. return;
  478. }
  479. try {
  480. const res = await getImage1Api({
  481. id:1
  482. });
  483. if (res && res.code === 200 && res.data && res.data.img_url) {
  484. currentImageUrl = res.data.img_url;
  485. currentVideoUrl = res.data.video;
  486. showPreview(currentImageUrl);
  487. }
  488. } catch (e) {
  489. console.error('加载配置失败', e);
  490. }
  491. }
  492. function showPreview(src) {
  493. previewImage.src = src;
  494. previewContainer.style.display = 'block';
  495. uploadBtn.style.display = 'none';
  496. }
  497. function resetUpload() {
  498. fileInput.value = '';
  499. currentFile = null;
  500. currentImageUrl = '';
  501. previewImage.src = '';
  502. previewContainer.style.display = 'none';
  503. uploadBtn.style.display = 'flex';
  504. }
  505. uploadBtn.addEventListener('click', () => {
  506. if (!checkLoginStatus()) {
  507. showLoginCheckModal();
  508. return;
  509. }
  510. fileInput.click();
  511. });
  512. fileInput.addEventListener('change', async (e) => {
  513. if (!checkLoginStatus()) {
  514. showLoginCheckModal();
  515. return;
  516. }
  517. const file = e.target.files[0];
  518. if (file) {
  519. // 1. 立即显示本地预览
  520. const reader = new FileReader();
  521. reader.onload = (e) => {
  522. showPreview(e.target.result);
  523. };
  524. reader.readAsDataURL(file);
  525. // 2. 立即上传文件获取URL
  526. try {
  527. showToast('正在上传图片...');
  528. const formData = new FormData();
  529. formData.append('file', file);
  530. formData.append('app_from', 'en');
  531. formData.append('type', 'image');
  532. //尺寸不确定
  533. formData.append('imgWidth', '600');
  534. formData.append('imgHeight', '400');
  535. const res = await uploadFileApi(formData);
  536. if (res && res.code === 200 && res.data) {
  537. const uploadedUrl = typeof res.data === 'string' ? res.data : res.data.url;
  538. if (uploadedUrl) {
  539. currentImageUrl = uploadedUrl;
  540. currentFile = null;
  541. showToast('图片上传成功');
  542. } else {
  543. throw new Error('未获取到有效的图片地址');
  544. }
  545. } else {
  546. throw new Error(res.msg || '上传接口返回错误');
  547. }
  548. } catch (error) {
  549. console.error('上传失败', error);
  550. showToast('图片上传失败: ' + (error.message || '未知错误'));
  551. resetUpload()
  552. }
  553. }
  554. });
  555. deleteBtn.addEventListener('click', (e) => {
  556. if (!checkLoginStatus()) {
  557. showLoginCheckModal();
  558. return;
  559. }
  560. e.stopPropagation();
  561. resetUpload();
  562. });
  563. saveConfigBtn.addEventListener('click', async () => {
  564. if (!checkLoginStatus()) {
  565. showLoginCheckModal();
  566. return;
  567. }
  568. if (!currentImageUrl) {
  569. showToast('请先上传图片');
  570. return;
  571. }
  572. try {
  573. const formData = new FormData();
  574. formData.append('id', 1);
  575. formData.append('url', currentImageUrl);
  576. formData.append('video', currentVideoUrl);
  577. const res = await saveContentConfigApi(formData);
  578. if (res && res.code === 200) {
  579. showToast('保存配置成功');
  580. } else {
  581. throw new Error(res.msg || '保存失败');
  582. }
  583. } catch (e) {
  584. showToast(e.message || '保存失败');
  585. }
  586. });
  587. </script>
  588. </body>
  589. </html>