Răsfoiți Sursa

feat: update

jerry 4 luni în urmă
părinte
comite
a4710fb1b7

+ 198 - 121
src/app/admin/orders/new/page.tsx

@@ -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>

+ 53 - 41
src/components/CreateOrderForm.tsx

@@ -6,7 +6,6 @@ import { useRouter } from 'next/navigation';
 import { Loader2, Info } from 'lucide-react';
 import { Loader2, Info } from 'lucide-react';
 import BindEmailModal from '@/components/BindEmailModal';
 import BindEmailModal from '@/components/BindEmailModal';
 import { useLanguage } from '@/lib/i18n/LanguageContext';
 import { useLanguage } from '@/lib/i18n/LanguageContext';
-// 引入通用弹窗组件
 import ConfirmModal from '@/components/common/ConfirmModal';
 import ConfirmModal from '@/components/common/ConfirmModal';
 import MessageModal from '@/components/common/MessageModal';
 import MessageModal from '@/components/common/MessageModal';
 
 
@@ -16,7 +15,7 @@ import MessageModal from '@/components/common/MessageModal';
 
 
 interface CreateOrderFormProps {
 interface CreateOrderFormProps {
   productId: string;
   productId: string;
-  productName?: string; // 可选,父组件传入的备用名称
+  productName?: string;
 }
 }
 
 
 interface ProductDetail {
 interface ProductDetail {
@@ -36,9 +35,9 @@ interface SchemaProperty {
   default?: any;
   default?: any;
   enum?: string[] | number[];
   enum?: string[] | number[];
   format?: string;
   format?: string;
-  order?: number;      // 标准排序字段
-  'x-order'?: number;  // 备用排序字段
-  [key: string]: any;  // 允许其他任意字段
+  order?: number;
+  'x-order'?: number;
+  [key: string]: any;
 }
 }
 
 
 interface JsonSchema {
 interface JsonSchema {
@@ -47,7 +46,7 @@ interface JsonSchema {
   type?: string;
   type?: string;
   properties?: Record<string, SchemaProperty>;
   properties?: Record<string, SchemaProperty>;
   required?: string[];
   required?: string[];
-  'ui:order'?: string[]; // 支持 UI Schema 的排序数组
+  'ui:order'?: string[];
 }
 }
 
 
 // ==========================================
 // ==========================================
@@ -109,7 +108,6 @@ export default function CreateOrderForm({ productId, productName }: CreateOrderF
             });
             });
             const schemaData = schemaRes.data.data || schemaRes.data;
             const schemaData = schemaRes.data.data || schemaRes.data;
             
             
-            // 兼容处理 JSON 字符串
             const schemaJson = typeof schemaData.schema_json === 'string' 
             const schemaJson = typeof schemaData.schema_json === 'string' 
               ? JSON.parse(schemaData.schema_json) 
               ? JSON.parse(schemaData.schema_json) 
               : schemaData.schema_json;
               : schemaData.schema_json;
@@ -121,7 +119,16 @@ export default function CreateOrderForm({ productId, productName }: CreateOrderF
             if (schemaJson.properties) {
             if (schemaJson.properties) {
               Object.keys(schemaJson.properties).forEach(key => {
               Object.keys(schemaJson.properties).forEach(key => {
                 const prop = schemaJson.properties[key];
                 const prop = schemaJson.properties[key];
-                initialValues[key] = prop.default !== undefined ? prop.default : '';
+                
+                // 优先使用 schema 定义的 default
+                if (prop.default !== undefined) {
+                  initialValues[key] = prop.default;
+                } else if (prop.type === 'boolean') {
+                   // boolean 类型如果没有默认值,初始化为 false
+                   initialValues[key] = false;
+                } else {
+                  initialValues[key] = ''; 
+                }
               });
               });
             }
             }
             setFormValues(initialValues);
             setFormValues(initialValues);
@@ -146,7 +153,6 @@ export default function CreateOrderForm({ productId, productName }: CreateOrderF
 
 
   // --- 辅助函数:用户状态检查 ---
   // --- 辅助函数:用户状态检查 ---
 
 
-  // 确保用户已登录(如果未登录,尝试自动注册)
   const ensureUserLoggedIn = async (forceNew = false): Promise<boolean> => {
   const ensureUserLoggedIn = async (forceNew = false): Promise<boolean> => {
     if (!forceNew) {
     if (!forceNew) {
       const token = localStorage.getItem('rsid');
       const token = localStorage.getItem('rsid');
@@ -179,17 +185,13 @@ export default function CreateOrderForm({ productId, productName }: CreateOrderF
     }
     }
   };
   };
 
 
-  // 检查用户是否已绑定邮箱
   const checkUserEmail = (): boolean => {
   const checkUserEmail = (): boolean => {
     if (typeof window === 'undefined') return false;
     if (typeof window === 'undefined') return false;
     const userStr = localStorage.getItem('user_info');
     const userStr = localStorage.getItem('user_info');
     if (!userStr) return false; 
     if (!userStr) return false; 
-    
     try {
     try {
       const user = JSON.parse(userStr);
       const user = JSON.parse(userStr);
-      if (user.email && user.email.includes('@')) {
-        return true; 
-      }
+      if (user.email && user.email.includes('@')) return true; 
       return false; 
       return false; 
     } catch (e) {
     } catch (e) {
       return false;
       return false;
@@ -202,7 +204,6 @@ export default function CreateOrderForm({ productId, productName }: CreateOrderF
 
 
   // --- 提交逻辑 ---
   // --- 提交逻辑 ---
 
 
-  // 封装 API 请求
   const performOrderCreation = async () => {
   const performOrderCreation = async () => {
     const payload = {
     const payload = {
       product_id: parseInt(productId),
       product_id: parseInt(productId),
@@ -216,29 +217,24 @@ export default function CreateOrderForm({ productId, productName }: CreateOrderF
     return orderId;
     return orderId;
   };
   };
 
 
-  // 表单提交入口
   const handleSubmit = async (e: React.FormEvent) => {
   const handleSubmit = async (e: React.FormEvent) => {
     e.preventDefault();
     e.preventDefault();
     setSubmitting(true);
     setSubmitting(true);
 
 
     try {
     try {
-      // 1. 初步确保已登录
       const isLoggedIn = await ensureUserLoggedIn();
       const isLoggedIn = await ensureUserLoggedIn();
       if (!isLoggedIn) {
       if (!isLoggedIn) {
         setSubmitting(false);
         setSubmitting(false);
         return;
         return;
       }
       }
 
 
-      // 2. 检查邮箱
       if (!checkUserEmail()) {
       if (!checkUserEmail()) {
         setIsBindEmailOpen(true);
         setIsBindEmailOpen(true);
         setSubmitting(false);
         setSubmitting(false);
         return;
         return;
       }
       }
 
 
-      // 3. 弹出确认框 (不直接提交)
       setShowConfirmModal(true);
       setShowConfirmModal(true);
-      // setSubmitting 状态会在确认框关闭或确认后处理
 
 
     } catch (error: any) {
     } catch (error: any) {
       console.error(error);
       console.error(error);
@@ -246,10 +242,9 @@ export default function CreateOrderForm({ productId, productName }: CreateOrderF
     }
     }
   };
   };
 
 
-  // 确认后的实际提交 (含 Token 过期重试)
   const handleConfirmSubmit = async () => {
   const handleConfirmSubmit = async () => {
     setShowConfirmModal(false);
     setShowConfirmModal(false);
-    setSubmitting(true); // 重新设置 loading,因为关闭弹窗可能导致状态丢失
+    setSubmitting(true); 
 
 
     try {
     try {
       const orderId = await performOrderCreation();
       const orderId = await performOrderCreation();
@@ -258,22 +253,17 @@ export default function CreateOrderForm({ productId, productName }: CreateOrderF
       const errorMsg = firstError.response?.data?.message || "";
       const errorMsg = firstError.response?.data?.message || "";
       const status = firstError.response?.status;
       const status = firstError.response?.status;
 
 
-      // 如果是 Token 失效
       if (status === 401 || errorMsg.toLowerCase().includes('token') || errorMsg.toLowerCase().includes('expired')) {
       if (status === 401 || errorMsg.toLowerCase().includes('token') || errorMsg.toLowerCase().includes('expired')) {
         console.log("Token expired, retrying...");
         console.log("Token expired, retrying...");
-        
-        // A. 强制获取新 Token
         const newSessionSuccess = await ensureUserLoggedIn(true); 
         const newSessionSuccess = await ensureUserLoggedIn(true); 
         
         
         if (newSessionSuccess) {
         if (newSessionSuccess) {
-           // B. 新账号需绑定邮箱
            if (!checkUserEmail()) {
            if (!checkUserEmail()) {
               setIsBindEmailOpen(true);
               setIsBindEmailOpen(true);
               setSubmitting(false);
               setSubmitting(false);
               return;
               return;
            }
            }
 
 
-           // C. 重试提交
            try {
            try {
              const retryOrderId = await performOrderCreation();
              const retryOrderId = await performOrderCreation();
              router.push(`/payment/${retryOrderId}`);
              router.push(`/payment/${retryOrderId}`);
@@ -287,7 +277,6 @@ export default function CreateOrderForm({ productId, productName }: CreateOrderF
            setSubmitting(false);
            setSubmitting(false);
         }
         }
       } else {
       } else {
-        // 普通错误
         const msg = firstError.response?.data?.message || t('order.create_failed');
         const msg = firstError.response?.data?.message || t('order.create_failed');
         showMessage(`${t('common.error')}: ${msg}`);
         showMessage(`${t('common.error')}: ${msg}`);
         setSubmitting(false);
         setSubmitting(false);
@@ -296,25 +285,50 @@ export default function CreateOrderForm({ productId, productName }: CreateOrderF
   };
   };
 
 
   const handleBindSuccess = () => {
   const handleBindSuccess = () => {
-    // 绑定成功后,仅关闭弹窗,用户需再次点击提交
     setIsBindEmailOpen(false);
     setIsBindEmailOpen(false);
   };
   };
 
 
   const renderField = (key: string, fieldSchema: SchemaProperty, required: boolean = false) => {
   const renderField = (key: string, fieldSchema: SchemaProperty, required: boolean = false) => {
-    // 公共样式
     const commonClasses = "w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition text-base md:text-sm bg-white min-h-[46px]";
     const commonClasses = "w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition text-base md:text-sm bg-white min-h-[46px]";
     const label = fieldSchema.title || key;
     const label = fieldSchema.title || key;
     const placeholderText = fieldSchema.description || `${t('common.enter')} ${label}`;
     const placeholderText = fieldSchema.description || `${t('common.enter')} ${label}`;
-    const currentValue = formValues[key] || '';
+    const currentValue = formValues[key];
+
+    // ----------------------------------------------------------------
+    // 1. Boolean 类型 (标准的 Checkbox)
+    // ----------------------------------------------------------------
+    if (fieldSchema.type === 'boolean') {
+      const isChecked = currentValue === true;
+      return (
+        <div className="flex items-center h-[46px] px-1" key={key}>
+            <input
+                id={`field-${key}`}
+                type="checkbox"
+                // Boolean 不应该有 HTML required 属性,因为 unchecked (false) 也是有效值
+                // 除非你特意需要 "Must agree" 的逻辑
+                checked={isChecked}
+                onChange={(e) => handleInputChange(key, e.target.checked)}
+                className="w-5 h-5 text-blue-600 rounded border-gray-300 focus:ring-blue-500 cursor-pointer accent-blue-600"
+            />
+            <label 
+                htmlFor={`field-${key}`} 
+                className="ml-3 text-gray-700 text-sm cursor-pointer select-none font-medium"
+            >
+                {/* 如果选中显示 Yes,否则显示 No,或者你可以只显示固定的 "Enabled/Yes" */}
+                {isChecked ? 'Yes' : 'No'}
+            </label>
+        </div>
+      );
+    }
 
 
-    // 1. 枚举类型 (Select)
+    // 2. 枚举类型 (Select)
     if (fieldSchema.enum && fieldSchema.enum.length > 0) {
     if (fieldSchema.enum && fieldSchema.enum.length > 0) {
       return (
       return (
         <select
         <select
           key={key}
           key={key}
           required={required}
           required={required}
           className={`${commonClasses} appearance-none`}
           className={`${commonClasses} appearance-none`}
-          value={currentValue}
+          value={currentValue || ''}
           onChange={(e) => handleInputChange(key, e.target.value)}
           onChange={(e) => handleInputChange(key, e.target.value)}
         >
         >
           <option value="" disabled>{t('common.select')} {label}</option>
           <option value="" disabled>{t('common.select')} {label}</option>
@@ -325,13 +339,12 @@ export default function CreateOrderForm({ productId, productName }: CreateOrderF
       );
       );
     }
     }
 
 
-    // 2. 日期/时间类型 (修复:聚焦时隐藏 Placeholder)
+    // 3. 日期/时间类型
     if (fieldSchema.format === 'date' || fieldSchema.format === 'date-time') {
     if (fieldSchema.format === 'date' || fieldSchema.format === 'date-time') {
       const realType = fieldSchema.format === 'date' ? 'date' : 'datetime-local';
       const realType = fieldSchema.format === 'date' ? 'date' : 'datetime-local';
       return (
       return (
-        <div className="relative w-full">
+        <div className="relative w-full" key={key}>
           <input
           <input
-            key={key}
             type={realType}
             type={realType}
             required={required}
             required={required}
             className={`
             className={`
@@ -340,10 +353,9 @@ export default function CreateOrderForm({ productId, productName }: CreateOrderF
               ${!currentValue ? 'text-transparent focus:text-gray-900' : 'text-gray-900'} 
               ${!currentValue ? 'text-transparent focus:text-gray-900' : 'text-gray-900'} 
             `}
             `}
             style={{ lineHeight: 'normal' }}
             style={{ lineHeight: 'normal' }}
-            value={currentValue}
+            value={currentValue || ''}
             onChange={(e) => handleInputChange(key, e.target.value)}
             onChange={(e) => handleInputChange(key, e.target.value)}
           />
           />
-          {/* 仅当无值时渲染,且聚焦时通过 CSS 隐藏 */}
           {!currentValue && (
           {!currentValue && (
             <span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 text-base md:text-sm pointer-events-none truncate pr-8 w-full h-fit transition-opacity peer-focus:opacity-0">
             <span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 text-base md:text-sm pointer-events-none truncate pr-8 w-full h-fit transition-opacity peer-focus:opacity-0">
               {placeholderText}
               {placeholderText}
@@ -353,7 +365,7 @@ export default function CreateOrderForm({ productId, productName }: CreateOrderF
       );
       );
     }
     }
 
 
-    // 3. 其他 Input 类型
+    // 4. 其他 Input 类型
     let inputType = 'text';
     let inputType = 'text';
     if (fieldSchema.type === 'integer' || fieldSchema.type === 'number') inputType = 'number';
     if (fieldSchema.type === 'integer' || fieldSchema.type === 'number') inputType = 'number';
     if (fieldSchema.format === 'email') inputType = 'email';
     if (fieldSchema.format === 'email') inputType = 'email';
@@ -365,7 +377,7 @@ export default function CreateOrderForm({ productId, productName }: CreateOrderF
         required={required}
         required={required}
         className={commonClasses}
         className={commonClasses}
         placeholder={placeholderText}
         placeholder={placeholderText}
-        value={currentValue}
+        value={currentValue || ''}
         onChange={(e) => handleInputChange(key, e.target.value)}
         onChange={(e) => handleInputChange(key, e.target.value)}
       />
       />
     );
     );
@@ -480,7 +492,7 @@ export default function CreateOrderForm({ productId, productName }: CreateOrderF
         onConfirm={handleConfirmSubmit}
         onConfirm={handleConfirmSubmit}
         onClose={() => {
         onClose={() => {
             setShowConfirmModal(false);
             setShowConfirmModal(false);
-            setSubmitting(false); // 取消后恢复按钮
+            setSubmitting(false); 
         }}
         }}
       />
       />
 
 

+ 2 - 1
src/components/dashboard/UserOrderDetailModal.tsx

@@ -14,6 +14,7 @@ export interface UserOrder {
   base_amount: number;
   base_amount: number;
   base_currency: string;
   base_currency: string;
   product_title?: string;
   product_title?: string;
+  product_name?: string;
   user_inputs?: Record<string, any>;
   user_inputs?: Record<string, any>;
 }
 }
 
 
@@ -95,7 +96,7 @@ export default function UserOrderDetailModal({ isOpen, onClose, order }: UserOrd
                 <Package size={24} />
                 <Package size={24} />
               </div>
               </div>
               <div>
               <div>
-                <h4 className="font-bold text-gray-900">{order.product_title || t('order.unknown_service')}</h4>
+                <h4 className="font-bold text-gray-900">{order.product_title || order.product_name || t('order.unknown_service')}</h4>
                 <div className="text-sm text-gray-500 mt-1 flex items-center gap-1">
                 <div className="text-sm text-gray-500 mt-1 flex items-center gap-1">
                   <Clock size={12} />
                   <Clock size={12} />
                   {/* 2. 使用 LocalTime 组件 */}
                   {/* 2. 使用 LocalTime 组件 */}

+ 2 - 0
src/lib/i18n/locales/en.ts

@@ -1,6 +1,8 @@
 export const en = {
 export const en = {
   common: {
   common: {
     back: 'Back',
     back: 'Back',
+    yes: "Yes",
+    no: "No",
     loading: 'Loading...',
     loading: 'Loading...',
     save: 'Save',
     save: 'Save',
     cancel: 'Cancel',
     cancel: 'Cancel',

+ 3 - 1
src/lib/i18n/locales/zh.ts

@@ -1,6 +1,8 @@
 export const zh = {
 export const zh = {
   common: {
   common: {
-    back: '返回', 
+    back: '返回',
+    yes: "是",
+    no: "否", 
     loading: '加载中...',
     loading: '加载中...',
     save: '保存',
     save: '保存',
     cancel: '取消',
     cancel: '取消',