TicketModal.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. 'use client';
  2. import { useState, useEffect } from 'react';
  3. import api from '@/lib/api';
  4. import { Loader2, X, AlertTriangle } from 'lucide-react';
  5. interface TicketModalProps {
  6. isOpen: boolean;
  7. onClose: () => void;
  8. onSuccess?: () => void; // 新增:成功回调,用于刷新列表
  9. defaultOrderId?: string;
  10. }
  11. export default function TicketModal({ isOpen, onClose, onSuccess, defaultOrderId = '' }: TicketModalProps) {
  12. const [loading, setLoading] = useState<boolean>(false);
  13. const [errorMsg, setErrorMsg] = useState<string>('');
  14. const [form, setForm] = useState({
  15. order_id: '',
  16. type: 'refund', // 对应 API Enum: refund | dispute | change_request
  17. reason: ''
  18. });
  19. // 初始化
  20. useEffect(() => {
  21. if (isOpen) {
  22. setForm({
  23. order_id: defaultOrderId || '',
  24. type: 'refund',
  25. reason: ''
  26. });
  27. setErrorMsg('');
  28. }
  29. }, [isOpen, defaultOrderId]);
  30. const handleSubmit = async (e: React.FormEvent) => {
  31. e.preventDefault();
  32. setLoading(true);
  33. setErrorMsg('');
  34. try {
  35. // API: /api/vas/ticket/create
  36. await api.post('/api/vas/ticket/create', form);
  37. // 成功处理
  38. if (onSuccess) onSuccess(); // 触发父组件刷新
  39. onClose();
  40. } catch (error: any) {
  41. console.error(error);
  42. const msg = error.response?.data?.message || '提交失败,请稍后重试';
  43. setErrorMsg(msg);
  44. } finally {
  45. setLoading(false);
  46. }
  47. };
  48. if (!isOpen) return null;
  49. return (
  50. <div className="fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6">
  51. <div className="fixed inset-0 bg-black/40 backdrop-blur-sm transition-opacity" onClick={onClose} />
  52. <div className="relative w-full max-w-lg transform overflow-hidden rounded-xl bg-white text-left shadow-2xl transition-all animate-in zoom-in duration-200">
  53. {/* Header */}
  54. <div className="px-6 py-4 border-b flex justify-between items-center bg-gray-50">
  55. <h3 className="text-lg font-bold text-gray-900">提交工单</h3>
  56. <button onClick={onClose} className="text-gray-400 hover:text-gray-600 transition">
  57. <X size={24} />
  58. </button>
  59. </div>
  60. <div className="p-6">
  61. {errorMsg && (
  62. <div className="mb-4 p-3 bg-red-50 text-red-700 text-sm rounded-lg flex items-center">
  63. <AlertTriangle size={16} className="mr-2 flex-shrink-0" />
  64. {errorMsg}
  65. </div>
  66. )}
  67. <form onSubmit={handleSubmit} className="space-y-5">
  68. <div>
  69. <label className="block text-xs font-bold uppercase text-gray-500 mb-1">关联订单号 <span className="text-red-500">*</span></label>
  70. <input
  71. type="text" required
  72. className="w-full rounded-lg border border-gray-300 py-2.5 px-3 text-gray-900 shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none text-sm bg-gray-50 focus:bg-white transition"
  73. value={form.order_id}
  74. onChange={e => setForm({ ...form, order_id: e.target.value })}
  75. placeholder="请输入相关的订单编号"
  76. />
  77. </div>
  78. <div>
  79. <label className="block text-xs font-bold uppercase text-gray-500 mb-1">工单类型 <span className="text-red-500">*</span></label>
  80. <select
  81. className="w-full rounded-lg border border-gray-300 py-2.5 px-3 text-gray-900 shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none text-sm bg-white"
  82. value={form.type}
  83. onChange={e => setForm({ ...form, type: e.target.value })}
  84. >
  85. {/* 仅保留 API 支持的 Enum */}
  86. <option value="refund">申请退款 (Refund)</option>
  87. <option value="dispute">交易纠纷 (Dispute)</option>
  88. <option value="change_request">变更请求 (Change Request)</option>
  89. </select>
  90. </div>
  91. <div>
  92. <label className="block text-xs font-bold uppercase text-gray-500 mb-1">详细描述 <span className="text-red-500">*</span></label>
  93. <textarea
  94. required
  95. rows={4}
  96. className="w-full rounded-lg border border-gray-300 py-2.5 px-3 text-gray-900 shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none text-sm resize-none"
  97. placeholder="请详细描述您遇到的问题,以便我们更快为您处理..."
  98. value={form.reason}
  99. onChange={e => setForm({ ...form, reason: e.target.value })}
  100. />
  101. </div>
  102. <div className="pt-2 flex items-center justify-end gap-3 border-t mt-6">
  103. <button
  104. type="button"
  105. onClick={onClose}
  106. className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-200 transition"
  107. >
  108. 取消
  109. </button>
  110. <button
  111. type="submit"
  112. disabled={loading}
  113. className="inline-flex items-center justify-center px-6 py-2 text-sm font-bold text-white bg-blue-600 rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed shadow-md transition"
  114. >
  115. {loading ? <Loader2 className="animate-spin w-4 h-4 mr-2" /> : null}
  116. 提交工单
  117. </button>
  118. </div>
  119. </form>
  120. </div>
  121. </div>
  122. </div>
  123. );
  124. }