Discounts.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. import { useEffect, useState } from 'react';
  2. import { useNavigate } from 'react-router-dom';
  3. import { deleteDiscount, fetchDiscounts, upsertDiscount } from '../api';
  4. import type { Discount } from '../types';
  5. import './Discounts.css';
  6. export function Discounts() {
  7. const [list, setList] = useState<Discount[]>([]);
  8. const [domain, setDomain] = useState('');
  9. const [discount, setDiscount] = useState('');
  10. const [note, setNote] = useState('');
  11. const [editing, setEditing] = useState<Discount | null>(null);
  12. const [error, setError] = useState('');
  13. const navigate = useNavigate();
  14. const load = () => fetchDiscounts().then(setList).catch(() => {});
  15. useEffect(() => { load(); }, []);
  16. const handleSubmit = async (e: React.FormEvent) => {
  17. e.preventDefault();
  18. setError('');
  19. const d = parseFloat(discount);
  20. if (isNaN(d) || d <= 0 || d > 1) {
  21. setError('折扣系数需在 0~1 之间,如 0.8 表示八折');
  22. return;
  23. }
  24. try {
  25. await upsertDiscount(domain.trim(), d, note.trim() || undefined);
  26. setDomain(''); setDiscount(''); setNote(''); setEditing(null);
  27. load();
  28. } catch (err: any) {
  29. setError(err.message);
  30. }
  31. };
  32. const startEdit = (item: Discount) => {
  33. setEditing(item);
  34. setDomain(item.domain);
  35. setDiscount(String(item.discount));
  36. setNote(item.note ?? '');
  37. };
  38. const handleDelete = async (id: number) => {
  39. await deleteDiscount(id);
  40. load();
  41. };
  42. return (
  43. <div className="discounts-page">
  44. <header className="discounts-header">
  45. <span className="discounts-title">折扣管理</span>
  46. </header>
  47. <form className="discount-form" onSubmit={handleSubmit}>
  48. <input
  49. className="discount-input"
  50. placeholder="域名,如 aigc.wangxunai.com"
  51. value={domain}
  52. onChange={e => setDomain(e.target.value)}
  53. required
  54. />
  55. <input
  56. className="discount-input discount-input--short"
  57. placeholder="折扣系数,如 0.8"
  58. value={discount}
  59. onChange={e => setDiscount(e.target.value)}
  60. required
  61. />
  62. <input
  63. className="discount-input"
  64. placeholder="备注(可选)"
  65. value={note}
  66. onChange={e => setNote(e.target.value)}
  67. />
  68. <button className="discount-btn discount-btn--primary" type="submit">
  69. {editing ? '保存' : '添加'}
  70. </button>
  71. {editing && (
  72. <button className="discount-btn" type="button" onClick={() => {
  73. setEditing(null); setDomain(''); setDiscount(''); setNote('');
  74. }}>取消</button>
  75. )}
  76. </form>
  77. {error && <div className="discount-error">{error}</div>}
  78. <div className="discount-table-wrap">
  79. <table className="discount-table">
  80. <thead>
  81. <tr>
  82. <th>域名</th>
  83. <th>折扣系数</th>
  84. <th>折扣</th>
  85. <th>备注</th>
  86. <th>更新时间</th>
  87. <th>操作</th>
  88. </tr>
  89. </thead>
  90. <tbody>
  91. {list.map(item => (
  92. <tr key={item.id}>
  93. <td className="td-domain">
  94. <button
  95. className="td-domain-link"
  96. onClick={() => navigate(`/discounts/${encodeURIComponent(item.domain)}/model-prices`)}
  97. >
  98. {item.domain}
  99. </button>
  100. </td>
  101. <td className="td-discount">{item.discount}</td>
  102. <td className="td-discount">{Math.round(item.discount * 10)}折</td>
  103. <td className="td-note">{item.note ?? '—'}</td>
  104. <td className="td-time">{new Date(item.updated_at).toLocaleString()}</td>
  105. <td className="td-actions">
  106. <button className="discount-btn discount-btn--sm discount-btn--config" onClick={() => navigate(`/discounts/${encodeURIComponent(item.domain)}/model-prices`)}>模型价格</button>
  107. <button className="discount-btn discount-btn--sm" onClick={() => startEdit(item)}>编辑</button>
  108. <button className="discount-btn discount-btn--sm discount-btn--danger" onClick={() => handleDelete(item.id)}>删除</button>
  109. </td>
  110. </tr>
  111. ))}
  112. </tbody>
  113. </table>
  114. {list.length === 0 && <div className="empty-msg">暂无折扣配置,所有域名按原价返回</div>}
  115. </div>
  116. </div>
  117. );
  118. }