page.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. 'use client';
  2. import { useState, useEffect } from 'react';
  3. import api from '@/lib/api';
  4. import { RefreshCw, Search, CheckCheck } from 'lucide-react';
  5. import Pagination from '@/components/common/Pagination';
  6. import ConfirmationTable from '@/components/admin/payments/ConfirmationTable';
  7. import ConfirmationDetailModal from '@/components/admin/payments/ConfirmationDetailModal';
  8. import { PaymentConfirmation } from '@/types/payment';
  9. export default function PaymentConfirmationsPage() {
  10. const [list, setList] = useState<PaymentConfirmation[]>([]);
  11. const [loading, setLoading] = useState(true);
  12. // 分页与搜索
  13. const [page, setPage] = useState(1);
  14. const [pageSize] = useState(10);
  15. const [total, setTotal] = useState(0);
  16. const [keyword, setKeyword] = useState('');
  17. // 弹窗状态
  18. const [selectedItem, setSelectedItem] = useState<PaymentConfirmation | null>(null);
  19. const [isDetailOpen, setIsDetailOpen] = useState(false);
  20. useEffect(() => {
  21. fetchData(1);
  22. }, []);
  23. const fetchData = async (targetPage: number) => {
  24. setLoading(true);
  25. try {
  26. const res = await api.post('/api/vas/payment_confirmation/list_all', {}, {
  27. params: {
  28. page: targetPage,
  29. size: pageSize,
  30. keyword: keyword
  31. }
  32. });
  33. const data = res.data.data;
  34. if (data && Array.isArray(data.items)) {
  35. setList(data.items);
  36. setTotal(data.total || 0);
  37. } else {
  38. setList([]);
  39. setTotal(0);
  40. }
  41. setPage(targetPage);
  42. } catch (error) {
  43. console.error("Fetch confirmations failed", error);
  44. setList([]);
  45. } finally {
  46. setLoading(false);
  47. }
  48. };
  49. const handleSearch = () => fetchData(1);
  50. // 打开详情
  51. const handleViewDetail = (item: PaymentConfirmation) => {
  52. setSelectedItem(item);
  53. setIsDetailOpen(true);
  54. };
  55. // 确认收款
  56. const handleApprove = async (item: PaymentConfirmation) => {
  57. const moneyStr = `${(item.amount / 100).toFixed(2)} ${item.currency}`;
  58. if (!confirm(`确认已收到支付单 #${item.payment_id} 的款项 (${moneyStr}) 吗?`)) return;
  59. try {
  60. // 使用新的确认接口
  61. await api.post('/api/vas/payment/confirm_by_admin', {
  62. status: 'confirmed',
  63. admin_confirmed_at: new Date().toISOString()
  64. }, {
  65. params: { id: item.id } // 这里的 id 是 confirmation record id
  66. });
  67. alert('已确认收款');
  68. fetchData(page);
  69. } catch (e: any) {
  70. alert('操作失败: ' + (e.response?.data?.message || '未知错误'));
  71. }
  72. };
  73. return (
  74. <div className="p-4 md:p-6">
  75. {/* Header */}
  76. <div className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-6">
  77. <div>
  78. <h1 className="text-2xl font-bold text-slate-800 flex items-center gap-2">
  79. <CheckCheck className="text-blue-600" /> 支付确认管理
  80. </h1>
  81. <p className="text-sm text-slate-500 mt-1">处理用户提交的“我已付款”确认请求</p>
  82. </div>
  83. <div className="flex flex-col sm:flex-row gap-3 w-full md:w-auto">
  84. <div className="relative w-full sm:w-auto md:w-64">
  85. <input
  86. type="text"
  87. placeholder="搜索 Payment ID / User ID"
  88. className="w-full pl-9 pr-4 py-2 border rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none transition"
  89. value={keyword}
  90. onChange={e => setKeyword(e.target.value)}
  91. onKeyDown={e => e.key === 'Enter' && handleSearch()}
  92. />
  93. <Search size={16} className="absolute left-3 top-2.5 text-gray-400" />
  94. </div>
  95. <button
  96. onClick={() => fetchData(page)}
  97. className="flex items-center justify-center gap-2 px-4 py-2 bg-white border border-slate-300 rounded-lg hover:bg-slate-50 text-slate-600 transition w-full sm:w-auto"
  98. title="刷新"
  99. >
  100. <RefreshCw size={18} />
  101. <span className="md:hidden text-sm font-medium">刷新列表</span>
  102. </button>
  103. </div>
  104. </div>
  105. {/* Table */}
  106. <ConfirmationTable
  107. data={list}
  108. loading={loading}
  109. onApprove={handleApprove}
  110. onViewDetail={handleViewDetail}
  111. />
  112. <div className="mt-4">
  113. <Pagination
  114. currentPage={page}
  115. total={total}
  116. pageSize={pageSize}
  117. onPageChange={fetchData}
  118. />
  119. </div>
  120. {/* Modal */}
  121. <ConfirmationDetailModal
  122. isOpen={isDetailOpen}
  123. onClose={() => setIsDetailOpen(false)}
  124. data={selectedItem}
  125. onApprove={handleApprove}
  126. />
  127. </div>
  128. );
  129. }