profile.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761
  1. // 我的界面逻辑
  2. (function() {
  3. let currentUsername = null;
  4. let currentUserInfo = null;
  5. function init() {
  6. bindEvents();
  7. // 延迟加载,确保DOM完全渲染
  8. setTimeout(() => {
  9. loadUserInfo();
  10. loadPoints();
  11. loadAIHistory();
  12. loadPurchaseHistory();
  13. }, 200);
  14. }
  15. function bindEvents() {
  16. // 返回按钮
  17. const backBtn = document.getElementById('backBtn');
  18. if (backBtn) {
  19. backBtn.addEventListener('click', () => {
  20. // 如果在 iframe 中,通过 postMessage 通知父窗口切换页面
  21. if (window.parent && window.parent !== window) {
  22. window.parent.postMessage({
  23. type: 'navigation',
  24. page: 'store'
  25. }, '*');
  26. } else {
  27. // 独立页面,直接跳转到主页面
  28. window.location.href = '../../index.html';
  29. }
  30. });
  31. }
  32. // 头像上传功能已移除
  33. // 充值按钮
  34. const rechargeBtn = document.getElementById('rechargeBtn');
  35. if (rechargeBtn) {
  36. rechargeBtn.addEventListener('click', () => {
  37. // 打开充值界面(通过postMessage通知父窗口,如果是独立页面则直接打开)
  38. if (window.parent && window.parent !== window) {
  39. window.parent.postMessage({
  40. type: 'open-recharge-view'
  41. }, '*');
  42. } else {
  43. // 独立页面,需要创建充值iframe
  44. openRechargeView();
  45. }
  46. });
  47. }
  48. // 退出登录按钮
  49. const logoutBtn = document.getElementById('logoutBtn');
  50. if (logoutBtn) {
  51. logoutBtn.addEventListener('click', handleLogout);
  52. }
  53. // 监听消息
  54. window.addEventListener('message', (event) => {
  55. if (event.data && event.data.type === 'recharge-success') {
  56. loadPoints(); // 刷新点数
  57. closeRechargeView(); // 关闭充值界面
  58. } else if (event.data && event.data.type === 'close-recharge-view') {
  59. closeRechargeView();
  60. } else if (event.data && event.data.type === 'open-recharge-view') {
  61. // 如果充值iframe发送消息,也打开充值界面
  62. openRechargeView();
  63. } else if (event.data && event.data.type === 'refresh-points') {
  64. // 刷新点数(购买成功后触发)
  65. loadPoints();
  66. loadPurchaseHistory(); // 同时刷新购买记录
  67. } else if (event.data && event.data.type === 'refresh-ai-history') {
  68. // 刷新AI历史(生图请求成功后触发)
  69. loadAIHistory();
  70. }
  71. });
  72. }
  73. // 打开充值界面
  74. function openRechargeView() {
  75. const rechargeFrame = document.getElementById('rechargeViewFrame');
  76. if (rechargeFrame) {
  77. rechargeFrame.style.display = 'block';
  78. rechargeFrame.style.pointerEvents = 'auto';
  79. rechargeFrame.style.visibility = 'visible';
  80. // 发送消息给充值iframe
  81. const sendRechargeData = () => {
  82. if (rechargeFrame.contentWindow) {
  83. rechargeFrame.contentWindow.postMessage({
  84. type: 'open-recharge-view'
  85. }, '*');
  86. } else {
  87. setTimeout(sendRechargeData, 100);
  88. }
  89. };
  90. sendRechargeData();
  91. const handleLoad = () => {
  92. setTimeout(() => {
  93. sendRechargeData();
  94. }, 50);
  95. };
  96. if (rechargeFrame.contentDocument && rechargeFrame.contentDocument.readyState === 'complete') {
  97. handleLoad();
  98. } else {
  99. rechargeFrame.addEventListener('load', handleLoad, { once: true });
  100. }
  101. }
  102. }
  103. // 关闭充值界面
  104. function closeRechargeView() {
  105. const rechargeFrame = document.getElementById('rechargeViewFrame');
  106. if (rechargeFrame) {
  107. rechargeFrame.style.display = 'none';
  108. rechargeFrame.style.pointerEvents = 'none';
  109. rechargeFrame.style.visibility = 'hidden';
  110. }
  111. }
  112. // 加载用户信息
  113. async function loadUserInfo() {
  114. const username = getCurrentUsername();
  115. if (!username) {
  116. // 未登录,跳转到登录页
  117. window.location.href = '../../index.html';
  118. return;
  119. }
  120. currentUsername = username;
  121. // 先从localStorage读取用户信息作为备用
  122. try {
  123. const loginDataStr = localStorage.getItem('loginData');
  124. if (loginDataStr) {
  125. const loginData = JSON.parse(loginDataStr);
  126. if (loginData.user) {
  127. // 先显示localStorage中的用户信息
  128. displayUserInfo(loginData.user);
  129. }
  130. }
  131. } catch (error) {
  132. console.error('[Profile] 从localStorage读取用户信息失败:', error);
  133. }
  134. // 然后从API获取最新信息
  135. try {
  136. const response = await fetch(`/api/user/info?username=${encodeURIComponent(username)}`);
  137. if (!response.ok) {
  138. throw new Error(`HTTP error! status: ${response.status}`);
  139. }
  140. const result = await response.json();
  141. if (result.success && result.user) {
  142. currentUserInfo = result.user;
  143. displayUserInfo(result.user);
  144. } else {
  145. console.error('[Profile] API返回数据格式错误:', result);
  146. }
  147. } catch (error) {
  148. console.error('[Profile] 加载用户信息失败:', error);
  149. // 如果API调用失败,至少显示localStorage中的信息
  150. if (window.parent && window.parent.HintView) {
  151. window.parent.HintView.error('加载用户信息失败,显示缓存信息', 2000);
  152. }
  153. }
  154. }
  155. // 手机号脱敏处理
  156. function maskPhone(phone) {
  157. if (!phone || phone.length < 7) {
  158. return phone;
  159. }
  160. // 保留前3位和后4位,中间用*代替
  161. const start = phone.substring(0, 3);
  162. const end = phone.substring(phone.length - 4);
  163. const middle = '*'.repeat(phone.length - 7);
  164. return start + middle + end;
  165. }
  166. // 显示用户信息
  167. function displayUserInfo(user) {
  168. if (!user) {
  169. console.error('[Profile] 用户信息为空');
  170. return;
  171. }
  172. // 使用函数来确保元素存在
  173. const setUserInfo = () => {
  174. const avatarImage = document.getElementById('avatarImage');
  175. const usernameInput = document.getElementById('usernameInput');
  176. const phoneInput = document.getElementById('phoneInput');
  177. if (avatarImage) {
  178. let avatarUrl = user.avatar;
  179. if (avatarUrl && !avatarUrl.startsWith('http') && !avatarUrl.startsWith('data:')) {
  180. avatarUrl = 'http://localhost:3000' + (avatarUrl.startsWith('/') ? avatarUrl : '/' + avatarUrl);
  181. }
  182. avatarImage.src = avatarUrl || '../../static/default-avatar.png';
  183. avatarImage.onerror = function() {
  184. this.src = '../../static/default-avatar.png';
  185. };
  186. }
  187. if (usernameInput) {
  188. usernameInput.value = user.username || '';
  189. }
  190. if (phoneInput) {
  191. const phone = user.phone || '';
  192. phoneInput.value = maskPhone(phone);
  193. }
  194. };
  195. // 如果元素还没准备好,等待一下再试
  196. if (!document.getElementById('usernameInput') || !document.getElementById('phoneInput')) {
  197. setTimeout(setUserInfo, 100);
  198. } else {
  199. setUserInfo();
  200. }
  201. }
  202. // 加载点数
  203. async function loadPoints() {
  204. const username = getCurrentUsername();
  205. if (!username) return;
  206. try {
  207. const response = await fetch(`/api/user/points?username=${encodeURIComponent(username)}`);
  208. if (!response.ok) {
  209. throw new Error(`HTTP error! status: ${response.status}`);
  210. }
  211. const result = await response.json();
  212. if (result.success) {
  213. const pointsValue = document.getElementById('pointsValue');
  214. if (pointsValue) {
  215. const points = parseFloat(result.points || 0);
  216. pointsValue.textContent = points.toFixed(2);
  217. }
  218. }
  219. } catch (error) {
  220. console.error('[Profile] 加载点数失败:', error);
  221. }
  222. }
  223. // 处理退出登录
  224. function handleLogout() {
  225. if (confirm('确定要退出登录吗?')) {
  226. // 清除localStorage
  227. localStorage.removeItem('loginData');
  228. // 通知父窗口(如果是在iframe中)
  229. if (window.parent && window.parent !== window) {
  230. window.parent.postMessage({
  231. type: 'logout'
  232. }, '*');
  233. }
  234. // 跳转到登录页
  235. window.location.href = '../../index.html';
  236. }
  237. }
  238. // 加载AI生图历史
  239. async function loadAIHistory() {
  240. const username = getCurrentUsername();
  241. if (!username) return;
  242. const historyLoading = document.getElementById('historyLoading');
  243. const historyEmpty = document.getElementById('historyEmpty');
  244. const historyGrid = document.getElementById('historyGrid');
  245. if (historyLoading) {
  246. historyLoading.style.display = 'block';
  247. }
  248. if (historyEmpty) {
  249. historyEmpty.style.display = 'none';
  250. }
  251. if (historyGrid) {
  252. historyGrid.innerHTML = '';
  253. }
  254. try {
  255. const response = await fetch(`/api/ai/history?username=${encodeURIComponent(username)}`);
  256. if (!response.ok) {
  257. throw new Error(`HTTP error! status: ${response.status}`);
  258. }
  259. const result = await response.json();
  260. if (historyLoading) {
  261. historyLoading.style.display = 'none';
  262. }
  263. if (result.success && result.history && result.history.length > 0) {
  264. if (historyGrid) {
  265. result.history.forEach(item => {
  266. const historyItem = createHistoryItem(item);
  267. historyGrid.appendChild(historyItem);
  268. });
  269. }
  270. } else {
  271. if (historyEmpty) {
  272. historyEmpty.style.display = 'block';
  273. }
  274. }
  275. // 如果有正在处理的任务,定期刷新
  276. if (result.success && result.history) {
  277. const hasProcessing = result.history.some(item => item.status === 'rendering' || item.status === 'queued');
  278. if (hasProcessing) {
  279. setTimeout(loadAIHistory, 3000); // 3秒后刷新
  280. }
  281. }
  282. } catch (error) {
  283. console.error('[Profile] 加载AI历史失败:', error);
  284. if (historyLoading) {
  285. historyLoading.style.display = 'none';
  286. }
  287. if (historyEmpty) {
  288. historyEmpty.style.display = 'block';
  289. }
  290. }
  291. }
  292. // 创建历史记录项
  293. function createHistoryItem(item) {
  294. const div = document.createElement('div');
  295. div.className = 'history-item';
  296. div.dataset.taskId = item.id;
  297. // 格式化时间
  298. const formatTime = (dateString) => {
  299. if (!dateString) return '';
  300. const date = new Date(dateString);
  301. const now = new Date();
  302. const diff = now - date;
  303. const minutes = Math.floor(diff / 60000);
  304. const hours = Math.floor(diff / 3600000);
  305. const days = Math.floor(diff / 86400000);
  306. if (minutes < 1) return '刚刚';
  307. if (minutes < 60) return `${minutes}分钟前`;
  308. if (hours < 24) return `${hours}小时前`;
  309. if (days < 7) return `${days}天前`;
  310. return date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
  311. };
  312. const timeText = formatTime(item.createdAt);
  313. // 预览图URL(模糊显示)
  314. const previewUrl = item.previewUrl || '';
  315. if (item.status === 'rendering' || item.status === 'queued') {
  316. div.classList.add(item.status);
  317. const statusText = item.status === 'rendering' ? '正在生成' : '等待中';
  318. div.innerHTML = `
  319. ${previewUrl ? `<img class="history-item-preview" src="${previewUrl}" alt="预览图" onerror="this.style.display='none'">` : ''}
  320. <div class="history-item-loading-overlay">
  321. <div class="loading-spinner-container">
  322. <div class="loading-spinner"></div>
  323. </div>
  324. <div class="loading-status-text">${statusText}</div>
  325. </div>
  326. <div class="history-item-time">${timeText}</div>
  327. `;
  328. } else if (item.status === 'completed' && item.imageUrl) {
  329. div.innerHTML = `
  330. <img class="history-item-image" src="${item.imageUrl}" alt="AI生成图" onerror="this.src='../../static/default-avatar.png'">
  331. <div class="history-item-overlay">
  332. <div class="history-item-actions">
  333. <button class="history-action-btn preview-btn" title="预览">
  334. <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  335. <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
  336. <circle cx="12" cy="12" r="3"/>
  337. </svg>
  338. <span>预览</span>
  339. </button>
  340. <button class="history-action-btn download-btn" title="下载">
  341. <svg width="22" height="22" viewBox="0 0 16 16" fill="none">
  342. <path d="M8 11V3M8 11L5 8M8 11L11 8M3 13H13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
  343. </svg>
  344. <span>下载</span>
  345. </button>
  346. </div>
  347. </div>
  348. <div class="history-item-time">${timeText}</div>
  349. `;
  350. // 点击预览按钮
  351. const previewBtn = div.querySelector('.preview-btn');
  352. if (previewBtn) {
  353. previewBtn.addEventListener('click', (e) => {
  354. e.stopPropagation();
  355. showImagePreviewModal(item.imageUrl, item.id || 'ai-image');
  356. });
  357. }
  358. // 点击下载按钮 - 直接下载PNG
  359. const downloadBtn = div.querySelector('.download-btn');
  360. if (downloadBtn) {
  361. downloadBtn.addEventListener('click', (e) => {
  362. e.stopPropagation();
  363. downloadImage(item.imageUrl, item.id || 'ai-image');
  364. });
  365. }
  366. } else if (item.status === 'failed') {
  367. div.classList.add('failed');
  368. // 如果已经重试过,显示"已重试"标记
  369. const retryButtonHtml = item.retried
  370. ? `<div class="retried-badge">已重试</div>`
  371. : `<button class="retry-btn" data-task-id="${item.id}">
  372. <svg width="14" height="14" viewBox="0 0 16 16" fill="none">
  373. <path d="M2 8C2 4.686 4.686 2 8 2C10.5 2 12.5 3.5 13.5 5.5M14 8C14 11.314 11.314 14 8 14C5.5 14 3.5 12.5 2.5 10.5M14 2V6H10M2 14V10H6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
  374. </svg>
  375. 重新生图
  376. </button>`;
  377. div.innerHTML = `
  378. ${previewUrl ? `<img class="history-item-preview failed-preview" src="${previewUrl}" alt="预览图" onerror="this.style.display='none'">` : ''}
  379. <div class="failed-content">
  380. <div class="failed-icon">❌</div>
  381. <div class="failed-text">生成失败</div>
  382. ${retryButtonHtml}
  383. </div>
  384. <div class="history-item-time">${timeText}</div>
  385. `;
  386. // 绑定重试按钮事件
  387. const retryBtn = div.querySelector('.retry-btn');
  388. if (retryBtn) {
  389. retryBtn.addEventListener('click', async (e) => {
  390. e.stopPropagation();
  391. await retryAIGeneration(item.id);
  392. });
  393. }
  394. }
  395. return div;
  396. }
  397. // 重新生图(免费)
  398. async function retryAIGeneration(taskId) {
  399. const username = getCurrentUsername();
  400. if (!username) {
  401. alert('请先登录');
  402. return;
  403. }
  404. try {
  405. const response = await fetch('/api/ai/retry', {
  406. method: 'POST',
  407. headers: {
  408. 'Content-Type': 'application/json'
  409. },
  410. body: JSON.stringify({
  411. taskId: taskId,
  412. username: username
  413. })
  414. });
  415. const result = await response.json();
  416. if (result.success) {
  417. // 刷新历史列表
  418. loadAIHistory();
  419. } else {
  420. alert('重试失败: ' + (result.error || '未知错误'));
  421. }
  422. } catch (error) {
  423. console.error('[Profile] 重试失败:', error);
  424. alert('重试失败: ' + error.message);
  425. }
  426. }
  427. // 下载图片(直接下载PNG)
  428. function downloadImage(url, filename) {
  429. const a = document.createElement('a');
  430. a.href = url;
  431. a.download = `${filename}.png`;
  432. document.body.appendChild(a);
  433. a.click();
  434. document.body.removeChild(a);
  435. }
  436. // 显示图片预览弹窗
  437. function showImagePreviewModal(imageUrl, filename) {
  438. const modal = document.createElement('div');
  439. modal.className = 'image-preview-modal';
  440. modal.innerHTML = `
  441. <div class="image-preview-backdrop"></div>
  442. <div class="image-preview-content">
  443. <img src="${imageUrl}" alt="预览图">
  444. <button class="image-preview-close">×</button>
  445. <button class="image-preview-download-btn" title="下载">
  446. <svg width="24" height="24" viewBox="0 0 16 16" fill="none">
  447. <path d="M8 11V3M8 11L5 8M8 11L11 8M3 13H13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
  448. </svg>
  449. </button>
  450. </div>
  451. `;
  452. document.body.appendChild(modal);
  453. requestAnimationFrame(() => {
  454. modal.classList.add('show');
  455. });
  456. const closeModal = () => {
  457. modal.classList.remove('show');
  458. setTimeout(() => {
  459. if (modal.parentNode) modal.parentNode.removeChild(modal);
  460. }, 300);
  461. };
  462. modal.querySelector('.image-preview-backdrop').onclick = closeModal;
  463. modal.querySelector('.image-preview-close').onclick = closeModal;
  464. modal.querySelector('.image-preview-download-btn').onclick = (e) => {
  465. e.stopPropagation();
  466. downloadImage(imageUrl, filename);
  467. };
  468. document.addEventListener('keydown', function escHandler(e) {
  469. if (e.key === 'Escape') {
  470. closeModal();
  471. document.removeEventListener('keydown', escHandler);
  472. }
  473. });
  474. }
  475. // 加载购买记录
  476. async function loadPurchaseHistory() {
  477. const username = getCurrentUsername();
  478. if (!username) return;
  479. const purchaseLoading = document.getElementById('purchaseLoading');
  480. const purchaseEmpty = document.getElementById('purchaseEmpty');
  481. const purchaseGrid = document.getElementById('purchaseGrid');
  482. if (purchaseLoading) {
  483. purchaseLoading.style.display = 'block';
  484. }
  485. if (purchaseEmpty) {
  486. purchaseEmpty.style.display = 'none';
  487. }
  488. if (purchaseGrid) {
  489. purchaseGrid.innerHTML = '';
  490. }
  491. try {
  492. const response = await fetch(`/api/pay/purchase-history?username=${encodeURIComponent(username)}`);
  493. if (!response.ok) {
  494. throw new Error(`HTTP error! status: ${response.status}`);
  495. }
  496. const result = await response.json();
  497. if (purchaseLoading) {
  498. purchaseLoading.style.display = 'none';
  499. }
  500. if (result.success && result.history && result.history.length > 0) {
  501. if (purchaseGrid) {
  502. result.history.forEach(item => {
  503. const purchaseItem = createPurchaseItem(item);
  504. purchaseGrid.appendChild(purchaseItem);
  505. });
  506. }
  507. } else {
  508. if (purchaseEmpty) {
  509. purchaseEmpty.style.display = 'block';
  510. }
  511. }
  512. } catch (error) {
  513. console.error('[Profile] 加载购买记录失败:', error);
  514. if (purchaseLoading) {
  515. purchaseLoading.style.display = 'none';
  516. }
  517. if (purchaseEmpty) {
  518. purchaseEmpty.style.display = 'block';
  519. }
  520. }
  521. }
  522. // 创建购买记录项
  523. function createPurchaseItem(item) {
  524. const div = document.createElement('div');
  525. div.className = 'purchase-item';
  526. if (item.deleted) {
  527. div.classList.add('deleted');
  528. }
  529. const previewHtml = item.previewUrl
  530. ? `<img class="purchase-item-image" src="${item.previewUrl}" alt="${item.name}" onerror="this.src='../../static/default-avatar.png'">`
  531. : `<div class="purchase-item-placeholder">
  532. <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  533. <path d="M14 2H6C5.46957 2 4.96086 2.21071 4.58579 2.58579C4.21071 2.96086 4 3.46957 4 4V20C4 20.5304 4.21071 21.0391 4.58579 21.4142C4.96086 21.7893 5.46957 22 6 22H18C18.5304 22 19.0391 21.7893 19.4142 21.4142C19.7893 21.0391 20 20.5304 20 20V8L14 2Z"/>
  534. </svg>
  535. </div>`;
  536. const deletedBadge = item.deleted ? '<div class="purchase-item-deleted-badge">已删除</div>' : '';
  537. const priceText = item.points > 0 ? `<div class="purchase-item-price">${item.points} Ani币</div>` : '';
  538. // 格式化时间(购买记录暂时没有时间,可以添加)
  539. const formatTime = (dateString) => {
  540. if (!dateString) return '';
  541. const date = new Date(dateString);
  542. const now = new Date();
  543. const diff = now - date;
  544. const minutes = Math.floor(diff / 60000);
  545. const hours = Math.floor(diff / 3600000);
  546. const days = Math.floor(diff / 86400000);
  547. if (minutes < 1) return '刚刚';
  548. if (minutes < 60) return `${minutes}分钟前`;
  549. if (hours < 24) return `${hours}小时前`;
  550. if (days < 7) return `${days}天前`;
  551. return date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
  552. };
  553. const timeText = item.purchasedAt ? formatTime(item.purchasedAt) : '';
  554. div.innerHTML = `
  555. ${previewHtml}
  556. <div class="purchase-item-info">
  557. <div class="purchase-item-name">${item.name}</div>
  558. <div class="purchase-item-category">${item.category}</div>
  559. ${priceText}
  560. </div>
  561. <div class="purchase-item-overlay">
  562. <button class="purchase-item-add-btn" data-path="${item.path}" data-category="${item.category}" data-name="${item.name}">
  563. <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2">
  564. <path d="M8 3V13M3 8H13" stroke-linecap="round"/>
  565. </svg>
  566. 添加
  567. </button>
  568. </div>
  569. ${timeText ? `<div class="purchase-item-time">${timeText}</div>` : ''}
  570. ${deletedBadge}
  571. `;
  572. // 绑定添加按钮事件
  573. const addBtn = div.querySelector('.purchase-item-add-btn');
  574. if (addBtn) {
  575. addBtn.addEventListener('click', async (e) => {
  576. e.stopPropagation();
  577. const resourcePath = addBtn.dataset.path;
  578. const categoryDir = addBtn.dataset.category;
  579. const itemName = addBtn.dataset.name;
  580. await handleAddToDisk(resourcePath, categoryDir, itemName, addBtn);
  581. });
  582. }
  583. return div;
  584. }
  585. // 处理添加到网盘
  586. async function handleAddToDisk(resourcePath, categoryDir, itemName, buttonEl) {
  587. const username = getCurrentUsername();
  588. if (!username) {
  589. if (window.parent && window.parent.HintView) {
  590. window.parent.HintView.error('请先登录', 2000);
  591. }
  592. return;
  593. }
  594. // 禁用按钮
  595. if (buttonEl) {
  596. buttonEl.disabled = true;
  597. buttonEl.textContent = '添加中...';
  598. }
  599. try {
  600. const response = await fetch('/api/pay/purchase', {
  601. method: 'POST',
  602. headers: {
  603. 'Content-Type': 'application/json'
  604. },
  605. body: JSON.stringify({
  606. username: username,
  607. resourcePath: resourcePath,
  608. categoryDir: categoryDir,
  609. itemName: itemName,
  610. points: 0 // 已购买,不需要扣除点数
  611. })
  612. });
  613. const result = await response.json();
  614. if (result.success) {
  615. if (window.parent && window.parent.HintView) {
  616. window.parent.HintView.success('添加成功!文件已添加到网盘', 2000);
  617. }
  618. // 通知父窗口刷新网盘
  619. if (window.parent && window.parent.postMessage) {
  620. window.parent.postMessage({ type: 'refresh-disk' }, '*');
  621. }
  622. } else {
  623. throw new Error(result.message || '添加失败');
  624. }
  625. } catch (error) {
  626. console.error('[Profile] 添加到网盘失败:', error);
  627. if (window.parent && window.parent.HintView) {
  628. window.parent.HintView.error(error.message || '添加失败,请稍后重试', 2000);
  629. }
  630. } finally {
  631. // 恢复按钮状态
  632. if (buttonEl) {
  633. buttonEl.disabled = false;
  634. buttonEl.innerHTML = `
  635. <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2">
  636. <path d="M8 3V13M3 8H13" stroke-linecap="round"/>
  637. </svg>
  638. 添加
  639. `;
  640. }
  641. }
  642. }
  643. // 获取当前登录用户名
  644. function getCurrentUsername() {
  645. try {
  646. const loginDataStr = localStorage.getItem('loginData');
  647. if (!loginDataStr) {
  648. return null;
  649. }
  650. const loginData = JSON.parse(loginDataStr);
  651. const now = Date.now();
  652. if (now >= loginData.expireTime) {
  653. localStorage.removeItem('loginData');
  654. return null;
  655. }
  656. return loginData.user ? loginData.user.username : null;
  657. } catch (error) {
  658. console.error('[Profile] 获取用户名失败:', error);
  659. return null;
  660. }
  661. }
  662. // 初始化
  663. if (document.readyState === 'loading') {
  664. document.addEventListener('DOMContentLoaded', init);
  665. } else {
  666. init();
  667. }
  668. })();