|
@@ -3,18 +3,21 @@
|
|
|
import { useState, useEffect } from 'react';
|
|
import { useState, useEffect } from 'react';
|
|
|
import api from '@/lib/api';
|
|
import api from '@/lib/api';
|
|
|
import { useRouter } from 'next/navigation';
|
|
import { useRouter } from 'next/navigation';
|
|
|
-import { Loader2, ArrowLeft, Search, CheckCircle, Package, Info } from 'lucide-react';
|
|
|
|
|
|
|
+// 修复:添加 Info 导入
|
|
|
|
|
+import {
|
|
|
|
|
+ Loader2, ArrowLeft, Search, CheckCircle, Package,
|
|
|
|
|
+ Sparkles, Bot, Wand2, Check, Info
|
|
|
|
|
+} from 'lucide-react';
|
|
|
import { useDebounce } from 'use-debounce';
|
|
import { useDebounce } from 'use-debounce';
|
|
|
|
|
|
|
|
-// 定义 Schema 属性接口,方便类型提示
|
|
|
|
|
interface SchemaProperty {
|
|
interface SchemaProperty {
|
|
|
type: string;
|
|
type: string;
|
|
|
title?: string;
|
|
title?: string;
|
|
|
description?: string;
|
|
description?: string;
|
|
|
default?: any;
|
|
default?: any;
|
|
|
enum?: string[];
|
|
enum?: string[];
|
|
|
- 'x-order'?: number; // 关键排序字段
|
|
|
|
|
- order?: number; // 兼容标准排序字段
|
|
|
|
|
|
|
+ 'x-order'?: number;
|
|
|
|
|
+ order?: number;
|
|
|
[key: string]: any;
|
|
[key: string]: any;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -32,7 +35,13 @@ export default function AdminCreateOrderPage() {
|
|
|
// 2. 表单状态
|
|
// 2. 表单状态
|
|
|
const [formSchema, setFormSchema] = useState<any>(null);
|
|
const [formSchema, setFormSchema] = useState<any>(null);
|
|
|
const [formValues, setFormValues] = useState<Record<string, any>>({});
|
|
const [formValues, setFormValues] = useState<Record<string, any>>({});
|
|
|
- const [sortedFields, setSortedFields] = useState<string[]>([]); // 存储排序后的字段名
|
|
|
|
|
|
|
+ const [sortedFields, setSortedFields] = useState<string[]>([]);
|
|
|
|
|
+
|
|
|
|
|
+ // 3. AI 解析状态
|
|
|
|
|
+ const [rawInput, setRawInput] = useState('');
|
|
|
|
|
+ const [parsing, setParsing] = useState(false);
|
|
|
|
|
+ // 默认展开,确保可见
|
|
|
|
|
+ const [showAiPanel, setShowAiPanel] = useState(true);
|
|
|
|
|
|
|
|
// --- 商品搜索逻辑 ---
|
|
// --- 商品搜索逻辑 ---
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
@@ -40,7 +49,6 @@ export default function AdminCreateOrderPage() {
|
|
|
setProductList([]);
|
|
setProductList([]);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
async function searchProducts() {
|
|
async function searchProducts() {
|
|
|
setIsSearchingProduct(true);
|
|
setIsSearchingProduct(true);
|
|
|
try {
|
|
try {
|
|
@@ -59,7 +67,7 @@ export default function AdminCreateOrderPage() {
|
|
|
searchProducts();
|
|
searchProducts();
|
|
|
}, [debouncedKeyword]);
|
|
}, [debouncedKeyword]);
|
|
|
|
|
|
|
|
- // --- 加载 Schema 并排序 ---
|
|
|
|
|
|
|
+ // --- 加载 Schema ---
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
async function loadSchema() {
|
|
async function loadSchema() {
|
|
|
if (!selectedProduct?.schema_id) {
|
|
if (!selectedProduct?.schema_id) {
|
|
@@ -78,30 +86,23 @@ export default function AdminCreateOrderPage() {
|
|
|
|
|
|
|
|
setFormSchema(schemaJson);
|
|
setFormSchema(schemaJson);
|
|
|
setFormValues({});
|
|
setFormValues({});
|
|
|
|
|
+ setShowAiPanel(true); // 重新选择商品时展开
|
|
|
|
|
|
|
|
- // === 核心修改:字段排序逻辑 ===
|
|
|
|
|
if (schemaJson.properties) {
|
|
if (schemaJson.properties) {
|
|
|
const keys = Object.keys(schemaJson.properties);
|
|
const keys = Object.keys(schemaJson.properties);
|
|
|
-
|
|
|
|
|
- // 优先检查 ui:order (如果 Schema 顶层有定义)
|
|
|
|
|
if (Array.isArray(schemaJson['ui:order'])) {
|
|
if (Array.isArray(schemaJson['ui:order'])) {
|
|
|
setSortedFields(schemaJson['ui:order']);
|
|
setSortedFields(schemaJson['ui:order']);
|
|
|
} else {
|
|
} else {
|
|
|
- // 否则根据 x-order 或 order 属性排序
|
|
|
|
|
const sorted = keys.sort((a, b) => {
|
|
const sorted = keys.sort((a, b) => {
|
|
|
const propA = schemaJson.properties[a] as SchemaProperty;
|
|
const propA = schemaJson.properties[a] as SchemaProperty;
|
|
|
const propB = schemaJson.properties[b] as SchemaProperty;
|
|
const propB = schemaJson.properties[b] as SchemaProperty;
|
|
|
-
|
|
|
|
|
- // 获取权重,默认 999 放到最后
|
|
|
|
|
const orderA = propA['x-order'] ?? propA.order ?? 999;
|
|
const orderA = propA['x-order'] ?? propA.order ?? 999;
|
|
|
const orderB = propB['x-order'] ?? propB.order ?? 999;
|
|
const orderB = propB['x-order'] ?? propB.order ?? 999;
|
|
|
-
|
|
|
|
|
return orderA - orderB;
|
|
return orderA - orderB;
|
|
|
});
|
|
});
|
|
|
setSortedFields(sorted);
|
|
setSortedFields(sorted);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
alert("表单定义加载失败");
|
|
alert("表单定义加载失败");
|
|
|
}
|
|
}
|
|
@@ -109,24 +110,54 @@ export default function AdminCreateOrderPage() {
|
|
|
loadSchema();
|
|
loadSchema();
|
|
|
}, [selectedProduct]);
|
|
}, [selectedProduct]);
|
|
|
|
|
|
|
|
- // --- 提交逻辑 ---
|
|
|
|
|
|
|
+ // --- AI 解析逻辑 ---
|
|
|
|
|
+ const handleAiParse = async () => {
|
|
|
|
|
+ if (!rawInput.trim()) return alert("请先粘贴需要解析的文本内容");
|
|
|
|
|
+ if (!selectedProduct?.schema_id) return alert("AI 解析依赖于 Schema,请先选择商品");
|
|
|
|
|
+
|
|
|
|
|
+ setParsing(true);
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await api.post('/api/vas/llm/data_parsing', {
|
|
|
|
|
+ input_raw_str: rawInput,
|
|
|
|
|
+ schema_id: selectedProduct.schema_id
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const responseData = res.data.data || res.data;
|
|
|
|
|
+ // 读取 parsed_obj
|
|
|
|
|
+ const fieldsToFill = responseData?.parsed_obj || responseData;
|
|
|
|
|
+
|
|
|
|
|
+ if (fieldsToFill && typeof fieldsToFill === 'object') {
|
|
|
|
|
+ setFormValues(prev => ({
|
|
|
|
|
+ ...prev,
|
|
|
|
|
+ ...fieldsToFill
|
|
|
|
|
+ }));
|
|
|
|
|
+
|
|
|
|
|
+ const count = Object.keys(fieldsToFill).length;
|
|
|
|
|
+ alert(`✨ AI 成功识别并填充了 ${count} 个字段!`);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ alert("解析失败:未能识别有效数据");
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e: any) {
|
|
|
|
|
+ console.error(e);
|
|
|
|
|
+ alert("解析失败: " + (e.response?.data?.message || e.message));
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setParsing(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // --- 提交订单 ---
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
|
e.preventDefault();
|
|
e.preventDefault();
|
|
|
if (!selectedProduct) return alert("请先搜索并选择商品");
|
|
if (!selectedProduct) return alert("请先搜索并选择商品");
|
|
|
|
|
|
|
|
setLoading(true);
|
|
setLoading(true);
|
|
|
try {
|
|
try {
|
|
|
- // 1. 构造 Payload
|
|
|
|
|
const payload = {
|
|
const payload = {
|
|
|
product_id: selectedProduct.id,
|
|
product_id: selectedProduct.id,
|
|
|
user_inputs: formValues,
|
|
user_inputs: formValues,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- // 2. === 修改点:调用 create_by_admin 接口 ===
|
|
|
|
|
- // 该接口应当包含:创建订单 + 自动标记支付成功 + 触发后续逻辑
|
|
|
|
|
const res = await api.post('/api/vas/order/create_by_admin', payload);
|
|
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;
|
|
const orderId = res.data.data?.id || res.data?.id || res.data?.order_id;
|
|
|
|
|
|
|
|
alert(`代下单成功!订单号: ${orderId}`);
|
|
alert(`代下单成功!订单号: ${orderId}`);
|
|
@@ -145,17 +176,17 @@ export default function AdminCreateOrderPage() {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const renderField = (key: string, fieldSchema: SchemaProperty) => {
|
|
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 commonClass = "w-full border border-slate-300 rounded-lg p-2.5 text-sm focus:ring-2 focus:ring-blue-500 outline-none transition bg-white";
|
|
|
const label = fieldSchema.title || key;
|
|
const label = fieldSchema.title || key;
|
|
|
const placeholder = fieldSchema.description || `请输入 ${label}`;
|
|
const placeholder = fieldSchema.description || `请输入 ${label}`;
|
|
|
|
|
+ const currentValue = formValues[key] || '';
|
|
|
|
|
|
|
|
- // 枚举类型 (Select)
|
|
|
|
|
if (fieldSchema.enum && fieldSchema.enum.length > 0) {
|
|
if (fieldSchema.enum && fieldSchema.enum.length > 0) {
|
|
|
return (
|
|
return (
|
|
|
<select
|
|
<select
|
|
|
className={commonClass}
|
|
className={commonClass}
|
|
|
onChange={e => handleInputChange(key, e.target.value)}
|
|
onChange={e => handleInputChange(key, e.target.value)}
|
|
|
- value={formValues[key] || ''}
|
|
|
|
|
|
|
+ value={currentValue}
|
|
|
>
|
|
>
|
|
|
<option value="">请选择 {label}</option>
|
|
<option value="">请选择 {label}</option>
|
|
|
{fieldSchema.enum.map((v: string) => <option key={v} value={v}>{v}</option>)}
|
|
{fieldSchema.enum.map((v: string) => <option key={v} value={v}>{v}</option>)}
|
|
@@ -163,52 +194,54 @@ export default function AdminCreateOrderPage() {
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 日期类型 (原生 Date Picker)
|
|
|
|
|
if (fieldSchema.format === 'date') {
|
|
if (fieldSchema.format === 'date') {
|
|
|
return (
|
|
return (
|
|
|
<input
|
|
<input
|
|
|
type="date"
|
|
type="date"
|
|
|
className={commonClass}
|
|
className={commonClass}
|
|
|
onChange={e => handleInputChange(key, e.target.value)}
|
|
onChange={e => handleInputChange(key, e.target.value)}
|
|
|
- value={formValues[key] || ''}
|
|
|
|
|
|
|
+ value={currentValue}
|
|
|
/>
|
|
/>
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 普通输入框
|
|
|
|
|
return (
|
|
return (
|
|
|
<input
|
|
<input
|
|
|
type={fieldSchema.type === 'integer' || fieldSchema.type === 'number' ? 'number' : 'text'}
|
|
type={fieldSchema.type === 'integer' || fieldSchema.type === 'number' ? 'number' : 'text'}
|
|
|
className={commonClass}
|
|
className={commonClass}
|
|
|
placeholder={placeholder}
|
|
placeholder={placeholder}
|
|
|
onChange={e => handleInputChange(key, e.target.value)}
|
|
onChange={e => handleInputChange(key, e.target.value)}
|
|
|
- value={formValues[key] || ''}
|
|
|
|
|
|
|
+ value={currentValue}
|
|
|
/>
|
|
/>
|
|
|
);
|
|
);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
- <div className="max-w-4xl mx-auto p-4 md:p-8">
|
|
|
|
|
|
|
+ <div className="max-w-5xl mx-auto p-4 md:p-8">
|
|
|
|
|
|
|
|
{/* 头部导航 */}
|
|
{/* 头部导航 */}
|
|
|
- <div className="flex items-center justify-between mb-6">
|
|
|
|
|
|
|
+ <div className="flex items-center justify-between mb-8">
|
|
|
<button onClick={() => router.back()} className="flex items-center text-slate-500 hover:text-slate-900 transition text-sm font-medium">
|
|
<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" /> 返回列表
|
|
<ArrowLeft size={18} className="mr-1" /> 返回列表
|
|
|
</button>
|
|
</button>
|
|
|
<h1 className="text-xl font-bold text-slate-800">管理员代客下单</h1>
|
|
<h1 className="text-xl font-bold text-slate-800">管理员代客下单</h1>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div className="bg-white rounded-xl shadow-lg border border-slate-200 overflow-hidden">
|
|
|
|
|
|
|
+ <div className="space-y-6">
|
|
|
|
|
|
|
|
{/* Step 1: 搜索选择商品 */}
|
|
{/* 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>
|
|
|
|
|
|
|
+ <div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
|
|
|
|
+ <label className="block text-base font-bold text-slate-800 mb-3 flex items-center gap-2">
|
|
|
|
|
+ <span className="w-6 h-6 rounded-full bg-slate-900 text-white flex items-center justify-center text-xs font-mono">1</span>
|
|
|
|
|
+ 选择服务商品
|
|
|
</label>
|
|
</label>
|
|
|
|
|
+
|
|
|
<div className="relative">
|
|
<div className="relative">
|
|
|
<input
|
|
<input
|
|
|
type="text"
|
|
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"
|
|
|
|
|
|
|
+ className={`w-full pl-11 pr-4 py-3 border rounded-xl shadow-sm outline-none transition text-base
|
|
|
|
|
+ ${selectedProduct ? 'border-green-500 ring-1 ring-green-500 bg-green-50/20' : 'border-slate-300 focus:ring-2 focus:ring-blue-500'}
|
|
|
|
|
+ `}
|
|
|
placeholder="输入关键词搜索 (例如: 日本, France...)"
|
|
placeholder="输入关键词搜索 (例如: 日本, France...)"
|
|
|
value={keyword}
|
|
value={keyword}
|
|
|
onChange={e => {
|
|
onChange={e => {
|
|
@@ -216,35 +249,51 @@ export default function AdminCreateOrderPage() {
|
|
|
if(!e.target.value) {
|
|
if(!e.target.value) {
|
|
|
setProductList([]);
|
|
setProductList([]);
|
|
|
setSelectedProduct(null);
|
|
setSelectedProduct(null);
|
|
|
|
|
+ setShowAiPanel(false);
|
|
|
}
|
|
}
|
|
|
}}
|
|
}}
|
|
|
/>
|
|
/>
|
|
|
- <Search className="absolute left-3 top-3.5 text-slate-400" size={20} />
|
|
|
|
|
|
|
+ <Search className="absolute left-3.5 top-3.5 text-slate-400" size={20} />
|
|
|
{isSearchingProduct && (
|
|
{isSearchingProduct && (
|
|
|
<Loader2 className="absolute right-3 top-3.5 text-blue-500 animate-spin" size={20} />
|
|
<Loader2 className="absolute right-3 top-3.5 text-blue-500 animate-spin" size={20} />
|
|
|
)}
|
|
)}
|
|
|
|
|
+
|
|
|
|
|
+ {selectedProduct && (
|
|
|
|
|
+ <div className="absolute right-3 top-2.5 flex items-center gap-2">
|
|
|
|
|
+ <span className="text-xs font-bold text-green-600 bg-green-100 px-2 py-1 rounded-full flex items-center gap-1">
|
|
|
|
|
+ <CheckCircle size={12} /> 已选: {selectedProduct.title}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={() => { setSelectedProduct(null); setKeyword(''); setFormSchema(null); setSortedFields([]); setRawInput(''); }}
|
|
|
|
|
+ className="text-xs text-slate-400 hover:text-red-500 underline ml-2"
|
|
|
|
|
+ >
|
|
|
|
|
+ 清除
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- {/* 搜索结果下拉列表 */}
|
|
|
|
|
|
|
+ {/* 搜索结果下拉 */}
|
|
|
{productList.length > 0 && !selectedProduct && (
|
|
{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">
|
|
|
|
|
|
|
+ <div className="mt-2 bg-white border border-slate-200 rounded-xl shadow-xl max-h-60 overflow-y-auto divide-y divide-slate-100 animate-in fade-in zoom-in-95 duration-200">
|
|
|
{productList.map(p => (
|
|
{productList.map(p => (
|
|
|
<div
|
|
<div
|
|
|
key={p.id}
|
|
key={p.id}
|
|
|
onClick={() => {
|
|
onClick={() => {
|
|
|
setSelectedProduct(p);
|
|
setSelectedProduct(p);
|
|
|
- setKeyword(p.title); // 回填输入框
|
|
|
|
|
- setProductList([]); // 收起列表
|
|
|
|
|
|
|
+ setKeyword(p.title);
|
|
|
|
|
+ setProductList([]);
|
|
|
|
|
+ setShowAiPanel(true); // 选中后展开 AI
|
|
|
}}
|
|
}}
|
|
|
- className="p-3 hover:bg-blue-50 cursor-pointer transition flex justify-between items-center group"
|
|
|
|
|
|
|
+ className="p-4 hover:bg-blue-50 cursor-pointer transition flex justify-between items-center group"
|
|
|
>
|
|
>
|
|
|
<div className="flex items-center gap-3">
|
|
<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">
|
|
<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} />
|
|
|
|
|
|
|
+ <Package size={20} />
|
|
|
</div>
|
|
</div>
|
|
|
<div>
|
|
<div>
|
|
|
<div className="font-bold text-sm text-slate-800">{p.title}</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 className="text-xs text-slate-500 mt-0.5">{p.country} • {p.visa_type}</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div className="text-sm font-mono font-bold text-slate-600">
|
|
<div className="text-sm font-mono font-bold text-slate-600">
|
|
@@ -254,91 +303,119 @@ export default function AdminCreateOrderPage() {
|
|
|
))}
|
|
))}
|
|
|
</div>
|
|
</div>
|
|
|
)}
|
|
)}
|
|
|
-
|
|
|
|
|
- {/* 已选中状态展示 */}
|
|
|
|
|
- {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"
|
|
|
|
|
- >
|
|
|
|
|
- 重新选择
|
|
|
|
|
- </button>
|
|
|
|
|
- </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>
|
|
|
|
|
|
|
+ {/* Step 2: 填写表单 (含 AI) */}
|
|
|
|
|
+ {selectedProduct && (
|
|
|
|
|
+ <form onSubmit={handleSubmit} className="animate-in fade-in slide-in-from-bottom-4 duration-500">
|
|
|
|
|
+ <div className="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
|
|
|
|
+ <div className="p-6 border-b border-slate-100 bg-slate-50/50">
|
|
|
|
|
+ <label className="block text-base font-bold text-slate-800 flex items-center gap-2">
|
|
|
|
|
+ <span className="w-6 h-6 rounded-full bg-slate-900 text-white flex items-center justify-center text-xs font-mono">2</span>
|
|
|
|
|
+ 填写申请资料
|
|
|
|
|
+ </label>
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
- {!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>
|
|
|
|
|
- )}
|
|
|
|
|
|
|
+ {/* === AI 智能填充区域 === */}
|
|
|
|
|
+ {formSchema && (
|
|
|
|
|
+ <div className="bg-gradient-to-r from-violet-50 to-fuchsia-50 p-6 border-b border-violet-100">
|
|
|
|
|
+ <div className="flex items-start gap-3">
|
|
|
|
|
+ <div className="p-2 bg-white rounded-lg shadow-sm text-violet-600 mt-1">
|
|
|
|
|
+ <Bot size={24} />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex-1">
|
|
|
|
|
+ <h3 className="font-bold text-violet-900 text-base mb-1 flex items-center gap-2">
|
|
|
|
|
+ AI 智能填表助手 <Sparkles size={16} className="text-amber-500 animate-pulse" />
|
|
|
|
|
+ </h3>
|
|
|
|
|
+ <p className="text-xs text-violet-600 mb-3">
|
|
|
|
|
+ 直接粘贴客户发来的聊天记录、邮件或证件文本,AI 将自动识别并填充到下方表单。
|
|
|
|
|
+ </p>
|
|
|
|
|
+
|
|
|
|
|
+ <div className="relative">
|
|
|
|
|
+ <textarea
|
|
|
|
|
+ className="w-full border border-violet-200 rounded-xl p-4 text-sm focus:ring-2 focus:ring-violet-500 outline-none resize-none shadow-sm min-h-[100px] bg-white/80 focus:bg-white transition"
|
|
|
|
|
+ placeholder="例如:
|
|
|
|
|
+姓名:张三
|
|
|
|
|
+护照号:E12345678
|
|
|
|
|
+出生日期:1990-01-01
|
|
|
|
|
+..."
|
|
|
|
|
+ value={rawInput}
|
|
|
|
|
+ onChange={e => setRawInput(e.target.value)}
|
|
|
|
|
+ />
|
|
|
|
|
+ <div className="absolute bottom-3 right-3">
|
|
|
|
|
+ <button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ disabled={parsing || !rawInput.trim()}
|
|
|
|
|
+ onClick={handleAiParse}
|
|
|
|
|
+ className="bg-violet-600 text-white px-4 py-2 rounded-lg text-xs font-bold hover:bg-violet-700 transition flex items-center gap-2 shadow-md disabled:opacity-50 disabled:cursor-not-allowed transform active:scale-95"
|
|
|
|
|
+ >
|
|
|
|
|
+ {parsing ? <Loader2 size={14} className="animate-spin" /> : <Wand2 size={14} />}
|
|
|
|
|
+ {parsing ? 'AI 正在分析...' : '一键识别填充'}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</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>
|
|
|
)}
|
|
)}
|
|
|
- </div>
|
|
|
|
|
- )}
|
|
|
|
|
|
|
|
|
|
- {/* 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 className="p-6">
|
|
|
|
|
+ {!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-x-8 gap-y-6">
|
|
|
|
|
+ {sortedFields.map(key => {
|
|
|
|
|
+ const fieldSchema = formSchema.properties[key] as SchemaProperty;
|
|
|
|
|
+ const hasValue = formValues[key] !== undefined && formValues[key] !== '';
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div key={key}>
|
|
|
|
|
+ <label className="block text-sm font-bold text-slate-700 mb-2 ml-1 flex items-center gap-2">
|
|
|
|
|
+ {fieldSchema.title || key}
|
|
|
|
|
+ {/* 如果有 AI 填充的提示图标 */}
|
|
|
|
|
+ {hasValue && <Check size={14} className="text-green-500" />}
|
|
|
|
|
+ </label>
|
|
|
|
|
+ {renderField(key, fieldSchema)}
|
|
|
|
|
+ {fieldSchema.description && (
|
|
|
|
|
+ <p className="text-xs text-gray-400 mt-1.5 ml-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>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Footer Action */}
|
|
|
|
|
+ <div className="p-6 bg-slate-50 border-t border-slate-100 flex items-center justify-between">
|
|
|
|
|
+ <div className="hidden md:flex text-xs text-slate-500 max-w-md items-start gap-2">
|
|
|
|
|
+ <Info size={16} className="shrink-0 mt-0.5 text-blue-500" />
|
|
|
|
|
+ <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="w-full md:w-auto px-8 py-3.5 bg-slate-900 text-white rounded-xl font-bold hover:bg-slate-800 transition flex items-center justify-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>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </form>
|
|
|
|
|
+ )}
|
|
|
|
|
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|