currency.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. // 充值与货币管理模块
  2. class CurrencyManager {
  3. constructor(options = {}) {
  4. this.apiBaseUrl = options.apiBaseUrl || 'http://localhost:3000';
  5. this.packages = [
  6. { points: 100, bonus: 20, price: 5 },
  7. { points: 1000, bonus: 200, price: 50 },
  8. { points: 10000, bonus: 800, price: 500 }
  9. ];
  10. // 保存原始值用于比较
  11. this.originalPackages = [];
  12. this.init();
  13. }
  14. init() {
  15. this.bindEvents();
  16. this.loadSettings();
  17. }
  18. bindEvents() {
  19. const addBtn = document.getElementById('addBtn');
  20. if (addBtn) {
  21. addBtn.addEventListener('click', () => this.addPackage());
  22. }
  23. }
  24. async loadSettings() {
  25. try {
  26. const response = await fetch(`${this.apiBaseUrl}/api/admin/currency/settings`);
  27. if (response.ok) {
  28. const result = await response.json();
  29. if (result.success && result.packages) {
  30. this.packages = result.packages;
  31. }
  32. }
  33. } catch (error) {
  34. console.log('[CurrencyManager] 使用默认设置');
  35. }
  36. // 深拷贝保存原始值
  37. this.originalPackages = JSON.parse(JSON.stringify(this.packages));
  38. this.render();
  39. }
  40. render() {
  41. this.renderPackages();
  42. this.renderPreview();
  43. }
  44. renderPackages() {
  45. const list = document.getElementById('packageList');
  46. if (!list) return;
  47. list.innerHTML = this.packages.map((pkg, i) => `
  48. <div class="package-item" data-index="${i}">
  49. <div class="package-index">${i + 1}</div>
  50. <div class="package-fields">
  51. <div class="package-field">
  52. <label>价格</label>
  53. <span class="unit">¥</span>
  54. <input type="number" value="${pkg.price}" data-field="price" min="0.01" step="0.01">
  55. </div>
  56. <div class="package-field">
  57. <label>基础</label>
  58. <input type="number" value="${pkg.points}" data-field="points" min="1">
  59. <span class="unit">Ani币</span>
  60. </div>
  61. <div class="package-field">
  62. <label>赠送</label>
  63. <input type="number" value="${pkg.bonus}" data-field="bonus" min="0">
  64. <span class="unit">Ani币</span>
  65. </div>
  66. <div class="package-total">= ${pkg.points + pkg.bonus} Ani币</div>
  67. </div>
  68. <div class="package-actions">
  69. <button class="btn-confirm" data-index="${i}" title="确认修改">✓</button>
  70. <button class="btn-remove" data-index="${i}" title="删除">×</button>
  71. </div>
  72. </div>
  73. `).join('');
  74. // 绑定输入事件
  75. list.querySelectorAll('input').forEach(input => {
  76. input.addEventListener('input', (e) => this.onInputChange(e));
  77. });
  78. // 绑定确认按钮
  79. list.querySelectorAll('.btn-confirm').forEach(btn => {
  80. btn.addEventListener('click', (e) => {
  81. const index = parseInt(e.target.dataset.index);
  82. this.savePackage(index);
  83. });
  84. });
  85. // 绑定删除按钮
  86. list.querySelectorAll('.btn-remove').forEach(btn => {
  87. btn.addEventListener('click', (e) => {
  88. const index = parseInt(e.target.dataset.index);
  89. this.removePackage(index);
  90. });
  91. });
  92. }
  93. onInputChange(e) {
  94. const item = e.target.closest('.package-item');
  95. const index = parseInt(item.dataset.index);
  96. const field = e.target.dataset.field;
  97. const value = parseFloat(e.target.value) || 0;
  98. this.packages[index][field] = value;
  99. // 更新总计显示
  100. const pkg = this.packages[index];
  101. const totalEl = item.querySelector('.package-total');
  102. if (totalEl) {
  103. totalEl.textContent = `= ${pkg.points + pkg.bonus} Ani币`;
  104. }
  105. // 检查是否有变化,显示确认按钮
  106. const confirmBtn = item.querySelector('.btn-confirm');
  107. const original = this.originalPackages[index];
  108. const hasChange = !original ||
  109. pkg.price !== original.price ||
  110. pkg.points !== original.points ||
  111. pkg.bonus !== original.bonus;
  112. if (confirmBtn) {
  113. confirmBtn.classList.toggle('show', hasChange);
  114. }
  115. this.renderPreview();
  116. }
  117. renderPreview() {
  118. const preview = document.getElementById('preview');
  119. if (!preview) return;
  120. preview.innerHTML = this.packages.map(pkg => `
  121. <div class="preview-card">
  122. <div class="points">${pkg.points} Ani币</div>
  123. <div class="card-bottom">
  124. ${pkg.bonus > 0 ? `<div class="bonus">送 ${pkg.bonus} Ani币</div>` : ''}
  125. <div class="price">¥${pkg.price}</div>
  126. </div>
  127. </div>
  128. `).join('');
  129. }
  130. addPackage() {
  131. const last = this.packages[this.packages.length - 1] || { points: 100, bonus: 0, price: 10 };
  132. const newPkg = {
  133. points: last.points * 2,
  134. bonus: Math.floor(last.bonus * 1.5),
  135. price: last.price * 2
  136. };
  137. this.packages.push(newPkg);
  138. this.originalPackages.push(null); // 新增的没有原始值
  139. this.render();
  140. // 显示新增项的确认按钮
  141. setTimeout(() => {
  142. const items = document.querySelectorAll('.package-item');
  143. const lastItem = items[items.length - 1];
  144. if (lastItem) {
  145. const confirmBtn = lastItem.querySelector('.btn-confirm');
  146. if (confirmBtn) confirmBtn.classList.add('show');
  147. }
  148. }, 0);
  149. }
  150. async removePackage(index) {
  151. if (this.packages.length <= 1) {
  152. this.showMsg('至少保留一个套餐', 'error');
  153. return;
  154. }
  155. this.packages.splice(index, 1);
  156. this.originalPackages.splice(index, 1);
  157. // 保存到服务器
  158. await this.saveAll();
  159. this.render();
  160. }
  161. async savePackage(index) {
  162. await this.saveAll();
  163. // 更新原始值
  164. this.originalPackages[index] = JSON.parse(JSON.stringify(this.packages[index]));
  165. // 隐藏确认按钮
  166. const items = document.querySelectorAll('.package-item');
  167. const item = items[index];
  168. if (item) {
  169. const confirmBtn = item.querySelector('.btn-confirm');
  170. if (confirmBtn) confirmBtn.classList.remove('show');
  171. }
  172. }
  173. async saveAll() {
  174. try {
  175. const response = await fetch(`${this.apiBaseUrl}/api/admin/currency/settings`, {
  176. method: 'POST',
  177. headers: { 'Content-Type': 'application/json' },
  178. body: JSON.stringify({ packages: this.packages })
  179. });
  180. const result = await response.json();
  181. if (result.success) {
  182. this.showMsg('保存成功', 'success');
  183. // 更新所有原始值
  184. this.originalPackages = JSON.parse(JSON.stringify(this.packages));
  185. } else {
  186. this.showMsg('保存失败: ' + (result.message || ''), 'error');
  187. }
  188. } catch (error) {
  189. this.showMsg('保存失败', 'error');
  190. }
  191. }
  192. showMsg(text, type) {
  193. const msgBox = document.getElementById('msgBox');
  194. if (msgBox) {
  195. msgBox.textContent = text;
  196. msgBox.className = 'msg ' + type;
  197. msgBox.style.display = 'block';
  198. setTimeout(() => {
  199. msgBox.style.display = 'none';
  200. }, 2000);
  201. }
  202. }
  203. }
  204. if (typeof module !== 'undefined' && module.exports) {
  205. module.exports = CurrencyManager;
  206. } else {
  207. window.CurrencyManager = CurrencyManager;
  208. }