|
|
@@ -3,252 +3,343 @@
|
|
|
import { useState, useEffect } from 'react';
|
|
|
import api from '@/lib/api';
|
|
|
import { useRouter } from 'next/navigation';
|
|
|
-import { Loader2, ArrowLeft, Search, CheckCircle, User } from 'lucide-react';
|
|
|
+import { Loader2, ArrowLeft, Search, CheckCircle, Package, Info } from 'lucide-react';
|
|
|
+import { useDebounce } from 'use-debounce';
|
|
|
+
|
|
|
+// 定义 Schema 属性接口,方便类型提示
|
|
|
+interface SchemaProperty {
|
|
|
+ type: string;
|
|
|
+ title?: string;
|
|
|
+ description?: string;
|
|
|
+ default?: any;
|
|
|
+ enum?: string[];
|
|
|
+ 'x-order'?: number; // 关键排序字段
|
|
|
+ order?: number; // 兼容标准排序字段
|
|
|
+ [key: string]: any;
|
|
|
+}
|
|
|
|
|
|
export default function AdminCreateOrderPage() {
|
|
|
const router = useRouter();
|
|
|
-
|
|
|
- // 步骤控制
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
- const [products, setProducts] = useState<any[]>([]);
|
|
|
|
|
|
- // 表单状态
|
|
|
- const [targetEmail, setTargetEmail] = useState('');
|
|
|
- const [selectedProductId, setSelectedProductId] = useState<number | null>(null);
|
|
|
+ // 1. 商品搜索状态
|
|
|
+ const [keyword, setKeyword] = useState('');
|
|
|
+ const [debouncedKeyword] = useDebounce(keyword, 500);
|
|
|
+ const [productList, setProductList] = useState<any[]>([]);
|
|
|
+ const [isSearchingProduct, setIsSearchingProduct] = useState(false);
|
|
|
+ const [selectedProduct, setSelectedProduct] = useState<any>(null);
|
|
|
+
|
|
|
+ // 2. 表单状态
|
|
|
const [formSchema, setFormSchema] = useState<any>(null);
|
|
|
const [formValues, setFormValues] = useState<Record<string, any>>({});
|
|
|
- const [skipPayment, setSkipPayment] = useState(true); // 默认跳过支付
|
|
|
+ const [sortedFields, setSortedFields] = useState<string[]>([]); // 存储排序后的字段名
|
|
|
|
|
|
- // 1. 加载商品列表
|
|
|
+ // --- 商品搜索逻辑 ---
|
|
|
useEffect(() => {
|
|
|
- async function loadProducts() {
|
|
|
+ if (!debouncedKeyword) {
|
|
|
+ setProductList([]);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ async function searchProducts() {
|
|
|
+ setIsSearchingProduct(true);
|
|
|
try {
|
|
|
- const res = await api.get('/api/vas/product/list');
|
|
|
- setProducts(Array.isArray(res.data.data) ? res.data.data : []);
|
|
|
+ const res = await api.get('/api/vas/product/list', {
|
|
|
+ params: { keyword: debouncedKeyword, page: 1, size: 20 }
|
|
|
+ });
|
|
|
+ const data = res.data.data;
|
|
|
+ if (Array.isArray(data)) setProductList(data);
|
|
|
+ else if (data.items) setProductList(data.items);
|
|
|
} catch (e) {
|
|
|
- console.error(e);
|
|
|
+ console.error("Search failed", e);
|
|
|
+ } finally {
|
|
|
+ setIsSearchingProduct(false);
|
|
|
}
|
|
|
}
|
|
|
- loadProducts();
|
|
|
- }, []);
|
|
|
+ searchProducts();
|
|
|
+ }, [debouncedKeyword]);
|
|
|
|
|
|
- // 2. 当选择了商品,加载 Schema
|
|
|
+ // --- 加载 Schema 并排序 ---
|
|
|
useEffect(() => {
|
|
|
async function loadSchema() {
|
|
|
- if (!selectedProductId) {
|
|
|
+ if (!selectedProduct?.schema_id) {
|
|
|
setFormSchema(null);
|
|
|
+ setSortedFields([]);
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
- const product = products.find(p => p.id === Number(selectedProductId));
|
|
|
- if (!product?.schema_id) return;
|
|
|
-
|
|
|
try {
|
|
|
- // API: 获取 Schema 定义
|
|
|
- const res = await api.get(`/api/vas/schema/${product.schema_id}`);
|
|
|
+ const res = await api.get('/api/vas/schema/detail', {
|
|
|
+ params: { schema_id: selectedProduct.schema_id }
|
|
|
+ });
|
|
|
const data = res.data.data || res.data;
|
|
|
- // 解析 JSON
|
|
|
const schemaJson = typeof data.schema_json === 'string'
|
|
|
? JSON.parse(data.schema_json)
|
|
|
: data.schema_json;
|
|
|
|
|
|
setFormSchema(schemaJson);
|
|
|
- setFormValues({}); // 重置表单
|
|
|
+ setFormValues({});
|
|
|
+
|
|
|
+ // === 核心修改:字段排序逻辑 ===
|
|
|
+ if (schemaJson.properties) {
|
|
|
+ const keys = Object.keys(schemaJson.properties);
|
|
|
+
|
|
|
+ // 优先检查 ui:order (如果 Schema 顶层有定义)
|
|
|
+ if (Array.isArray(schemaJson['ui:order'])) {
|
|
|
+ setSortedFields(schemaJson['ui:order']);
|
|
|
+ } else {
|
|
|
+ // 否则根据 x-order 或 order 属性排序
|
|
|
+ const sorted = keys.sort((a, b) => {
|
|
|
+ const propA = schemaJson.properties[a] as SchemaProperty;
|
|
|
+ const propB = schemaJson.properties[b] as SchemaProperty;
|
|
|
+
|
|
|
+ // 获取权重,默认 999 放到最后
|
|
|
+ const orderA = propA['x-order'] ?? propA.order ?? 999;
|
|
|
+ const orderB = propB['x-order'] ?? propB.order ?? 999;
|
|
|
+
|
|
|
+ return orderA - orderB;
|
|
|
+ });
|
|
|
+ setSortedFields(sorted);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
} catch (e) {
|
|
|
- alert("加载表单定义失败");
|
|
|
+ alert("表单定义加载失败");
|
|
|
}
|
|
|
}
|
|
|
loadSchema();
|
|
|
- }, [selectedProductId, products]);
|
|
|
+ }, [selectedProduct]);
|
|
|
|
|
|
- // 处理动态表单输入
|
|
|
- const handleInputChange = (key: string, value: any) => {
|
|
|
- setFormValues(prev => ({ ...prev, [key]: value }));
|
|
|
- };
|
|
|
-
|
|
|
- // 3. 提交订单
|
|
|
+ // --- 提交逻辑 ---
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
|
e.preventDefault();
|
|
|
- if (!targetEmail) return alert("请输入目标用户邮箱");
|
|
|
- if (!selectedProductId) return alert("请选择商品");
|
|
|
+ if (!selectedProduct) return alert("请先搜索并选择商品");
|
|
|
|
|
|
setLoading(true);
|
|
|
try {
|
|
|
- // Step A: 创建订单
|
|
|
- // 注意:这里假设后端 create 接口支持通过 extra 参数指定 user_email (管理员权限)
|
|
|
- // 如果后端不支持直接指定 user,可能需要先根据 email 查 user_id,然后传 user_id
|
|
|
+ // 1. 构造 Payload
|
|
|
const payload = {
|
|
|
- product_id: Number(selectedProductId),
|
|
|
+ product_id: selectedProduct.id,
|
|
|
user_inputs: formValues,
|
|
|
- // 传递目标用户标识 (需要后端支持处理,或者后端从 user_inputs 里提取 email)
|
|
|
- target_user_email: targetEmail,
|
|
|
- // 标记为后台创建
|
|
|
- is_admin_created: true
|
|
|
};
|
|
|
|
|
|
- const res = await api.post('/api/vas/order/create', payload);
|
|
|
- const orderData = res.data.data || res.data;
|
|
|
- const orderId = orderData.id;
|
|
|
-
|
|
|
- // Step B: 如果勾选了“跳过支付”,直接将订单更新为已支付
|
|
|
- if (skipPayment && orderId) {
|
|
|
- // 调用更新接口将状态改为 paid
|
|
|
- // API: PUT /api/vas/order/{id}
|
|
|
- await api.put(`/api/vas/order/${orderId}`, {
|
|
|
- status: 'paid', // 强制标记为已支付
|
|
|
- admin_note: '管理员后台代下单,免支付'
|
|
|
- });
|
|
|
- }
|
|
|
+ // 2. === 修改点:调用 create_by_admin 接口 ===
|
|
|
+ // 该接口应当包含:创建订单 + 自动标记支付成功 + 触发后续逻辑
|
|
|
+ const res = await api.post('/api/vas/order/create_by_admin', payload);
|
|
|
+
|
|
|
+ // 兼容不同后端返回结构
|
|
|
+ const orderId = res.data.data?.id || res.data?.id || res.data?.order_id;
|
|
|
|
|
|
- alert("订单创建成功!");
|
|
|
+ alert(`代下单成功!订单号: ${orderId}`);
|
|
|
router.push('/admin/orders');
|
|
|
|
|
|
} catch (error: any) {
|
|
|
console.error(error);
|
|
|
- alert("创建失败: " + (error.response?.data?.message || error.message));
|
|
|
+ alert("下单失败: " + (error.response?.data?.message || error.message));
|
|
|
} finally {
|
|
|
setLoading(false);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- // 动态字段渲染器 (复用之前逻辑)
|
|
|
- const renderField = (key: string, fieldSchema: any) => {
|
|
|
- const commonClass = "w-full border rounded-lg p-2.5 text-sm focus:ring-2 focus:ring-blue-500 outline-none";
|
|
|
-
|
|
|
- if (fieldSchema.enum) {
|
|
|
+ const handleInputChange = (key: string, value: any) => {
|
|
|
+ setFormValues(prev => ({ ...prev, [key]: value }));
|
|
|
+ };
|
|
|
+
|
|
|
+ const renderField = (key: string, fieldSchema: SchemaProperty) => {
|
|
|
+ const commonClass = "w-full border border-slate-300 rounded-lg p-2.5 text-sm focus:ring-2 focus:ring-blue-500 outline-none transition";
|
|
|
+ const label = fieldSchema.title || key;
|
|
|
+ const placeholder = fieldSchema.description || `请输入 ${label}`;
|
|
|
+
|
|
|
+ // 枚举类型 (Select)
|
|
|
+ if (fieldSchema.enum && fieldSchema.enum.length > 0) {
|
|
|
return (
|
|
|
- <select className={commonClass} onChange={e => handleInputChange(key, e.target.value)}>
|
|
|
- <option value="">请选择</option>
|
|
|
+ <select
|
|
|
+ className={commonClass}
|
|
|
+ onChange={e => handleInputChange(key, e.target.value)}
|
|
|
+ value={formValues[key] || ''}
|
|
|
+ >
|
|
|
+ <option value="">请选择 {label}</option>
|
|
|
{fieldSchema.enum.map((v: string) => <option key={v} value={v}>{v}</option>)}
|
|
|
</select>
|
|
|
);
|
|
|
}
|
|
|
+
|
|
|
+ // 日期类型 (原生 Date Picker)
|
|
|
+ if (fieldSchema.format === 'date') {
|
|
|
+ return (
|
|
|
+ <input
|
|
|
+ type="date"
|
|
|
+ className={commonClass}
|
|
|
+ onChange={e => handleInputChange(key, e.target.value)}
|
|
|
+ value={formValues[key] || ''}
|
|
|
+ />
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // 普通输入框
|
|
|
return (
|
|
|
<input
|
|
|
- type={fieldSchema.type === 'integer' ? 'number' : 'text'}
|
|
|
+ type={fieldSchema.type === 'integer' || fieldSchema.type === 'number' ? 'number' : 'text'}
|
|
|
className={commonClass}
|
|
|
+ placeholder={placeholder}
|
|
|
onChange={e => handleInputChange(key, e.target.value)}
|
|
|
+ value={formValues[key] || ''}
|
|
|
/>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
return (
|
|
|
- <div className="max-w-3xl mx-auto">
|
|
|
- <button onClick={() => router.back()} className="flex items-center text-slate-500 hover:text-slate-800 mb-6 text-sm">
|
|
|
- <ArrowLeft size={16} className="mr-1" /> 返回订单列表
|
|
|
- </button>
|
|
|
-
|
|
|
- <div className="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
|
|
- <div className="px-6 py-4 border-b bg-slate-50">
|
|
|
- <h1 className="text-lg font-bold text-slate-800">管理员代下单 (后台创建)</h1>
|
|
|
- <p className="text-xs text-slate-500 mt-1">为用户创建订单并可选择直接跳过支付流程</p>
|
|
|
- </div>
|
|
|
+ <div className="max-w-4xl mx-auto p-4 md:p-8">
|
|
|
+
|
|
|
+ {/* 头部导航 */}
|
|
|
+ <div className="flex items-center justify-between mb-6">
|
|
|
+ <button onClick={() => router.back()} className="flex items-center text-slate-500 hover:text-slate-900 transition text-sm font-medium">
|
|
|
+ <ArrowLeft size={18} className="mr-1" /> 返回列表
|
|
|
+ </button>
|
|
|
+ <h1 className="text-xl font-bold text-slate-800">管理员代客下单</h1>
|
|
|
+ </div>
|
|
|
|
|
|
- <form onSubmit={handleSubmit} className="p-6 space-y-8">
|
|
|
-
|
|
|
- {/* 1. 用户与商品选择 */}
|
|
|
- <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
|
- <div>
|
|
|
- <label className="block text-sm font-bold text-slate-700 mb-2">
|
|
|
- 目标用户邮箱 <span className="text-red-500">*</span>
|
|
|
- </label>
|
|
|
- <div className="relative">
|
|
|
- <input
|
|
|
- type="email" required
|
|
|
- className="w-full border border-slate-300 rounded-lg pl-9 p-2.5 text-sm focus:ring-2 focus:ring-blue-500 outline-none"
|
|
|
- placeholder="user@example.com"
|
|
|
- value={targetEmail}
|
|
|
- onChange={e => setTargetEmail(e.target.value)}
|
|
|
- />
|
|
|
- <User size={16} className="absolute left-3 top-3 text-slate-400" />
|
|
|
- </div>
|
|
|
- <p className="text-xs text-slate-400 mt-1">如果用户不存在,系统将尝试自动创建或报错。</p>
|
|
|
+ <div className="bg-white rounded-xl shadow-lg border border-slate-200 overflow-hidden">
|
|
|
+
|
|
|
+ {/* Step 1: 搜索选择商品 */}
|
|
|
+ <div className="p-6 border-b border-slate-100 bg-slate-50">
|
|
|
+ <label className="block text-sm font-bold text-slate-700 mb-2">
|
|
|
+ 1. 搜索服务商品 <span className="text-red-500">*</span>
|
|
|
+ </label>
|
|
|
+ <div className="relative">
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ className="w-full pl-10 pr-4 py-3 border border-slate-300 rounded-xl shadow-sm focus:ring-2 focus:ring-blue-500 outline-none transition"
|
|
|
+ placeholder="输入关键词搜索 (例如: 日本, France...)"
|
|
|
+ value={keyword}
|
|
|
+ onChange={e => {
|
|
|
+ setKeyword(e.target.value);
|
|
|
+ if(!e.target.value) {
|
|
|
+ setProductList([]);
|
|
|
+ setSelectedProduct(null);
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ <Search className="absolute left-3 top-3.5 text-slate-400" size={20} />
|
|
|
+ {isSearchingProduct && (
|
|
|
+ <Loader2 className="absolute right-3 top-3.5 text-blue-500 animate-spin" size={20} />
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 搜索结果下拉列表 */}
|
|
|
+ {productList.length > 0 && !selectedProduct && (
|
|
|
+ <div className="mt-2 bg-white border border-slate-200 rounded-xl shadow-lg max-h-60 overflow-y-auto divide-y divide-slate-100 animate-in fade-in zoom-in-95 duration-200">
|
|
|
+ {productList.map(p => (
|
|
|
+ <div
|
|
|
+ key={p.id}
|
|
|
+ onClick={() => {
|
|
|
+ setSelectedProduct(p);
|
|
|
+ setKeyword(p.title); // 回填输入框
|
|
|
+ setProductList([]); // 收起列表
|
|
|
+ }}
|
|
|
+ className="p-3 hover:bg-blue-50 cursor-pointer transition flex justify-between items-center group"
|
|
|
+ >
|
|
|
+ <div className="flex items-center gap-3">
|
|
|
+ <div className="p-2 bg-slate-100 rounded-lg group-hover:bg-blue-100 text-slate-500 group-hover:text-blue-600 transition">
|
|
|
+ <Package size={16} />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div className="font-bold text-sm text-slate-800">{p.title}</div>
|
|
|
+ <div className="text-xs text-slate-500">{p.country} • {p.visa_type}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="text-sm font-mono font-bold text-slate-600">
|
|
|
+ {p.price_amount / 100} {p.price_currency}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
</div>
|
|
|
+ )}
|
|
|
|
|
|
- <div>
|
|
|
- <label className="block text-sm font-bold text-slate-700 mb-2">
|
|
|
- 选择商品 <span className="text-red-500">*</span>
|
|
|
- </label>
|
|
|
- <select
|
|
|
- required
|
|
|
- className="w-full border border-slate-300 rounded-lg p-2.5 text-sm focus:ring-2 focus:ring-blue-500 outline-none bg-white"
|
|
|
- value={selectedProductId || ''}
|
|
|
- onChange={e => setSelectedProductId(Number(e.target.value))}
|
|
|
+ {/* 已选中状态展示 */}
|
|
|
+ {selectedProduct && (
|
|
|
+ <div className="mt-4 p-4 bg-blue-50 border border-blue-100 rounded-xl flex justify-between items-center animate-in slide-in-from-top-2">
|
|
|
+ <div className="flex items-center gap-3">
|
|
|
+ <div className="p-2 bg-blue-600 text-white rounded-lg shadow-sm">
|
|
|
+ <CheckCircle size={20} />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div className="font-bold text-slate-800 text-sm">已选择: {selectedProduct.title}</div>
|
|
|
+ <div className="text-xs text-slate-500 mt-0.5">ID: {selectedProduct.id}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ onClick={() => { setSelectedProduct(null); setKeyword(''); setFormSchema(null); setSortedFields([]); }}
|
|
|
+ className="text-xs text-blue-600 hover:text-blue-800 font-medium hover:underline"
|
|
|
>
|
|
|
- <option value="">-- 请选择服务 --</option>
|
|
|
- {products.map(p => (
|
|
|
- <option key={p.id} value={p.id}>{p.title} ({p.price_amount/100} {p.price_currency})</option>
|
|
|
- ))}
|
|
|
- </select>
|
|
|
+ 重新选择
|
|
|
+ </button>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
|
|
|
- <div className="border-t border-slate-100"></div>
|
|
|
-
|
|
|
- {/* 2. 动态表单区域 */}
|
|
|
- <div>
|
|
|
- <h3 className="text-sm font-bold text-slate-800 mb-4 flex items-center gap-2">
|
|
|
- 填写申请资料
|
|
|
- {formSchema && <span className="text-xs font-normal text-slate-500 bg-slate-100 px-2 py-0.5 rounded">Schema Loaded</span>}
|
|
|
- </h3>
|
|
|
-
|
|
|
- {!selectedProductId ? (
|
|
|
- <div className="text-center py-8 bg-slate-50 rounded-lg text-slate-400 text-sm">
|
|
|
- 请先选择左侧的商品,以加载对应的表单字段
|
|
|
- </div>
|
|
|
- ) : !formSchema ? (
|
|
|
- <div className="text-center py-8 text-slate-400 text-sm">正在加载表单定义...</div>
|
|
|
- ) : (
|
|
|
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
- {formSchema.properties && Object.keys(formSchema.properties).map(key => (
|
|
|
- <div key={key}>
|
|
|
- <label className="block text-sm font-medium text-slate-700 mb-1">
|
|
|
- {formSchema.properties[key].title || key}
|
|
|
- </label>
|
|
|
- {renderField(key, formSchema.properties[key])}
|
|
|
- </div>
|
|
|
- ))}
|
|
|
- </div>
|
|
|
- )}
|
|
|
- </div>
|
|
|
+ <form onSubmit={handleSubmit}>
|
|
|
+ {/* Step 2: 填写表单 (仅当选中商品后显示) */}
|
|
|
+ {selectedProduct && (
|
|
|
+ <div className="p-6">
|
|
|
+ <h3 className="text-sm font-bold text-slate-800 mb-6 flex items-center gap-2 pb-2 border-b border-slate-100">
|
|
|
+ <span className="w-6 h-6 rounded-full bg-slate-800 text-white flex items-center justify-center text-xs">2</span>
|
|
|
+ 填写申请资料
|
|
|
+ </h3>
|
|
|
|
|
|
- <div className="border-t border-slate-100"></div>
|
|
|
-
|
|
|
- {/* 3. 支付设置 */}
|
|
|
- <div className="bg-green-50 border border-green-100 rounded-lg p-4 flex items-start gap-3">
|
|
|
- <div className="mt-0.5">
|
|
|
- <input
|
|
|
- type="checkbox"
|
|
|
- id="skipPayment"
|
|
|
- className="w-4 h-4 text-blue-600 rounded border-gray-300 focus:ring-blue-500"
|
|
|
- checked={skipPayment}
|
|
|
- onChange={e => setSkipPayment(e.target.checked)}
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <label htmlFor="skipPayment" className="block text-sm font-bold text-green-800 cursor-pointer">
|
|
|
- 免支付 (直接标记为已支付)
|
|
|
- </label>
|
|
|
- <p className="text-xs text-green-700 mt-1">
|
|
|
- 选中后,订单创建后将自动把状态更为 <strong>PAID</strong>,并触发后续业务流程(如机器人任务)。
|
|
|
- <br/>如果不选中,订单将保持 Pending 状态,需要用户自行登录付款。
|
|
|
- </p>
|
|
|
+ {!formSchema ? (
|
|
|
+ <div className="text-center py-8 text-slate-400 text-sm flex flex-col items-center">
|
|
|
+ <Loader2 className="animate-spin mb-2" /> 正在加载表单定义...
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
|
+ {/* === 使用 sortedFields 进行渲染 === */}
|
|
|
+ {sortedFields.map(key => {
|
|
|
+ const fieldSchema = formSchema.properties[key] as SchemaProperty;
|
|
|
+ return (
|
|
|
+ <div key={key}>
|
|
|
+ <label className="block text-xs font-bold text-slate-500 uppercase mb-1.5 ml-1">
|
|
|
+ {fieldSchema.title || key}
|
|
|
+ </label>
|
|
|
+ {renderField(key, fieldSchema)}
|
|
|
+ {fieldSchema.description && (
|
|
|
+ <p className="text-xs text-gray-400 mt-1">{fieldSchema.description}</p>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+
|
|
|
+ {sortedFields.length === 0 && (
|
|
|
+ <div className="col-span-2 text-center text-slate-400 text-sm py-4 bg-slate-50 rounded-lg border border-dashed border-slate-200">
|
|
|
+ 此商品无需填写额外信息
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ )}
|
|
|
|
|
|
- {/* 提交按钮 */}
|
|
|
- <div className="flex justify-end pt-4">
|
|
|
- <button
|
|
|
- type="submit"
|
|
|
- disabled={loading}
|
|
|
- className="px-8 py-3 bg-slate-900 text-white rounded-lg font-bold hover:bg-slate-800 transition flex items-center gap-2 disabled:opacity-50 shadow-lg"
|
|
|
- >
|
|
|
- {loading ? <Loader2 className="animate-spin" /> : <CheckCircle size={18} />}
|
|
|
- {skipPayment ? '创建并标记成功' : '创建待支付订单'}
|
|
|
- </button>
|
|
|
+ {/* Footer Action */}
|
|
|
+ <div className="p-6 bg-slate-50 border-t border-slate-100 flex items-center justify-between">
|
|
|
+ <div className="text-xs text-slate-500 max-w-md flex items-start gap-2">
|
|
|
+ <Info size={16} className="shrink-0 mt-0.5" />
|
|
|
+ <div>
|
|
|
+ <span className="font-bold text-slate-700">说明:</span>
|
|
|
+ 订单将直接创建在您的账号下,状态为 <span className="font-bold text-green-600">已支付 (Paid)</span>,并自动进入待处理队列。
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <button
|
|
|
+ type="submit"
|
|
|
+ disabled={loading || !selectedProduct}
|
|
|
+ className="px-8 py-3 bg-slate-900 text-white rounded-xl font-bold hover:bg-slate-800 transition flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed shadow-lg shadow-slate-300 transform active:scale-95"
|
|
|
+ >
|
|
|
+ {loading ? <Loader2 className="animate-spin" /> : <CheckCircle size={18} />}
|
|
|
+ 确认创建
|
|
|
+ </button>
|
|
|
</div>
|
|
|
-
|
|
|
</form>
|
|
|
+
|
|
|
</div>
|
|
|
</div>
|
|
|
);
|