ConfirmationDetailModal.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. 'use client';
  2. import { useState, useEffect } from 'react';
  3. import { X, User, CreditCard, ShoppingBag, Loader2, CheckCircle } from 'lucide-react';
  4. import api from '@/lib/api';
  5. import LocalTime from '@/components/common/LocalTime';
  6. import { PaymentConfirmation } from '@/types/payment';
  7. interface ConfirmationDetailModalProps {
  8. isOpen: boolean;
  9. onClose: () => void;
  10. data: PaymentConfirmation | null;
  11. onApprove: (item: PaymentConfirmation) => void;
  12. // onReject: (item: PaymentConfirmation) => void; // 移除
  13. }
  14. export default function ConfirmationDetailModal({ isOpen, onClose, data, onApprove }: ConfirmationDetailModalProps) {
  15. const [loading, setLoading] = useState(false);
  16. // 详情数据状态
  17. const [paymentInfo, setPaymentInfo] = useState<any>(null);
  18. const [orderInfo, setOrderInfo] = useState<any>(null);
  19. const [userInfo, setUserInfo] = useState<any>(null);
  20. useEffect(() => {
  21. if (isOpen && data) {
  22. fetchDetails();
  23. } else {
  24. // 重置数据
  25. setPaymentInfo(null);
  26. setOrderInfo(null);
  27. setUserInfo(null);
  28. }
  29. }, [isOpen, data]);
  30. const fetchDetails = async () => {
  31. if (!data) return;
  32. setLoading(true);
  33. try {
  34. // 1. 获取用户信息 (已有 user_id)
  35. const userRes = await api.get('/api/user/detail', { params: { user_id: data.user_id } });
  36. setUserInfo(userRes.data.data);
  37. // 2. 获取支付详情 (已有 payment_id)
  38. const payRes = await api.get('/api/vas/payment/detail', { params: { payment_id: data.payment_id } });
  39. const paymentData = payRes.data.data;
  40. setPaymentInfo(paymentData);
  41. // 3. 获取订单详情 (通过支付详情里的 order_id)
  42. if (paymentData && paymentData.order_id) {
  43. const orderRes = await api.get('/api/vas/order/detail', { params: { order_id: paymentData.order_id } });
  44. setOrderInfo(orderRes.data.data);
  45. }
  46. } catch (error) {
  47. console.error("Failed to load details", error);
  48. } finally {
  49. setLoading(false);
  50. }
  51. };
  52. if (!isOpen || !data) return null;
  53. return (
  54. <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4 animate-in fade-in duration-200">
  55. <div className="bg-white rounded-xl shadow-2xl w-full max-w-4xl max-h-[90vh] flex flex-col overflow-hidden">
  56. {/* Header */}
  57. <div className="px-6 py-4 border-b flex justify-between items-center bg-slate-50">
  58. <div>
  59. <h3 className="font-bold text-gray-900 text-lg">支付确认详情</h3>
  60. <p className="text-xs text-gray-500 mt-1">Confirmation ID: #{data.id}</p>
  61. </div>
  62. <button onClick={onClose} className="p-1 hover:bg-gray-200 rounded-full transition">
  63. <X size={24} className="text-gray-500" />
  64. </button>
  65. </div>
  66. {/* Content */}
  67. <div className="flex-1 overflow-y-auto p-6 bg-slate-50/50">
  68. {loading ? (
  69. <div className="flex flex-col items-center justify-center py-20 text-slate-400">
  70. <Loader2 className="w-10 h-10 animate-spin mb-2" />
  71. <p>正在加载关联数据...</p>
  72. </div>
  73. ) : (
  74. <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
  75. {/* 1. 用户提交的确认信息 */}
  76. <div className="md:col-span-2 bg-white p-5 rounded-xl shadow-sm border border-blue-100">
  77. <h4 className="text-sm font-bold text-slate-800 mb-4 flex items-center gap-2">
  78. <CheckCircle size={16} className="text-blue-600" /> 用户提交的确认信息
  79. </h4>
  80. <div className="grid grid-cols-2 sm:grid-cols-4 gap-4 text-sm">
  81. <div>
  82. <span className="block text-xs text-slate-400">提交金额</span>
  83. <span className="font-bold text-slate-900 text-lg">
  84. {(data.amount / 100).toFixed(2)} {data.currency}
  85. </span>
  86. </div>
  87. <div>
  88. <span className="block text-xs text-slate-400">随机立减</span>
  89. <span className="font-medium text-red-500">
  90. -{(data.random_offset / 100).toFixed(2)}
  91. </span>
  92. </div>
  93. <div>
  94. <span className="block text-xs text-slate-400">用户点击时间</span>
  95. <LocalTime date={data.confirmed_at} className="font-medium text-slate-700"/>
  96. </div>
  97. <div>
  98. <span className="block text-xs text-slate-400">当前状态</span>
  99. <span className={`inline-block px-2 py-0.5 rounded text-xs font-bold ${
  100. data.status === 'pending' ? 'bg-yellow-100 text-yellow-700' : 'bg-gray-100 text-gray-700'
  101. }`}>
  102. {data.status.toUpperCase()}
  103. </span>
  104. </div>
  105. </div>
  106. </div>
  107. {/* 2. 支付单详情 (系统记录) */}
  108. <div className="bg-white p-5 rounded-xl shadow-sm border border-slate-200">
  109. <h4 className="text-sm font-bold text-slate-800 mb-4 flex items-center gap-2">
  110. <CreditCard size={16} className="text-slate-500" /> 关联支付单 (Payment)
  111. </h4>
  112. {paymentInfo ? (
  113. <div className="space-y-3 text-sm">
  114. <div className="flex justify-between border-b border-slate-50 pb-2">
  115. <span className="text-slate-500">Payment ID</span>
  116. <span className="font-mono">{paymentInfo.id}</span>
  117. </div>
  118. <div className="flex justify-between border-b border-slate-50 pb-2">
  119. <span className="text-slate-500">渠道 Provider</span>
  120. <span className="font-medium uppercase">{paymentInfo.provider}</span>
  121. </div>
  122. <div className="flex justify-between border-b border-slate-50 pb-2">
  123. <span className="text-slate-500">应付金额</span>
  124. <span className="font-bold">
  125. {(paymentInfo.amount / 100).toFixed(2)} {paymentInfo.currency}
  126. </span>
  127. </div>
  128. <div className="flex justify-between">
  129. <span className="text-slate-500">外部流水号</span>
  130. <span className="font-mono text-xs max-w-[150px] truncate" title={paymentInfo.external_trade_no}>
  131. {paymentInfo.external_trade_no || '-'}
  132. </span>
  133. </div>
  134. </div>
  135. ) : (
  136. <p className="text-slate-400 text-xs">未找到支付记录</p>
  137. )}
  138. </div>
  139. {/* 3. 订单详情 */}
  140. <div className="bg-white p-5 rounded-xl shadow-sm border border-slate-200">
  141. <h4 className="text-sm font-bold text-slate-800 mb-4 flex items-center gap-2">
  142. <ShoppingBag size={16} className="text-slate-500" /> 关联订单 (Order)
  143. </h4>
  144. {orderInfo ? (
  145. <div className="space-y-3 text-sm">
  146. <div className="flex justify-between border-b border-slate-50 pb-2">
  147. <span className="text-slate-500">Order ID</span>
  148. <span className="font-mono">{orderInfo.id}</span>
  149. </div>
  150. <div className="flex justify-between border-b border-slate-50 pb-2">
  151. <span className="text-slate-500">商品名称</span>
  152. <span className="font-medium text-right max-w-[180px] truncate" title={orderInfo.product_title}>
  153. {orderInfo.product_title}
  154. </span>
  155. </div>
  156. <div className="flex justify-between">
  157. <span className="text-slate-500">申请人</span>
  158. <span className="font-medium">
  159. {orderInfo.user_inputs?.first_name} {orderInfo.user_inputs?.last_name}
  160. </span>
  161. </div>
  162. </div>
  163. ) : (
  164. <p className="text-slate-400 text-xs">未找到订单记录</p>
  165. )}
  166. </div>
  167. {/* 4. 用户信息 */}
  168. <div className="md:col-span-2 bg-white p-5 rounded-xl shadow-sm border border-slate-200">
  169. <h4 className="text-sm font-bold text-slate-800 mb-4 flex items-center gap-2">
  170. <User size={16} className="text-slate-500" /> 下单用户 (User)
  171. </h4>
  172. {userInfo ? (
  173. <div className="flex items-center gap-6 text-sm">
  174. <div className="w-12 h-12 bg-slate-100 rounded-full flex items-center justify-center overflow-hidden">
  175. {userInfo.avatar_url ? (
  176. <img src={userInfo.avatar_url} className="w-full h-full object-cover" />
  177. ) : (
  178. <User className="text-slate-400" />
  179. )}
  180. </div>
  181. <div className="grid grid-cols-1 sm:grid-cols-3 gap-x-12 gap-y-2 flex-1">
  182. <div>
  183. <span className="block text-xs text-slate-400">昵称</span>
  184. <span className="font-medium">{userInfo.nickname || '-'}</span>
  185. </div>
  186. <div>
  187. <span className="block text-xs text-slate-400">邮箱</span>
  188. <span className="font-medium">{userInfo.email}</span>
  189. </div>
  190. <div>
  191. <span className="block text-xs text-slate-400">手机号</span>
  192. <span className="font-medium">{userInfo.phone || '-'}</span>
  193. </div>
  194. </div>
  195. </div>
  196. ) : (
  197. <p className="text-slate-400 text-xs">未找到用户信息</p>
  198. )}
  199. </div>
  200. </div>
  201. )}
  202. </div>
  203. {/* Footer Actions */}
  204. <div className="p-4 bg-white border-t flex justify-end gap-3">
  205. <button onClick={onClose} className="px-4 py-2 border border-slate-300 rounded-lg text-slate-700 hover:bg-slate-50 text-sm font-medium">关闭</button>
  206. {data.status === 'pending' && (
  207. // 移除了驳回按钮
  208. <button
  209. onClick={() => { onApprove(data); onClose(); }}
  210. className="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 text-sm font-bold flex items-center gap-2 shadow-sm"
  211. >
  212. <CheckCircle size={16} /> 确认已收款
  213. </button>
  214. )}
  215. </div>
  216. </div>
  217. </div>
  218. );
  219. }