Преглед изворни кода

feat(自动接入任务): 新增自动接入任务创建功能及相关页面

refactor(表单验证): 将部分表单字段改为非必填
feat(API服务): 添加创建自动接入任务的API端点
style(列表页): 优化搜索区域布局并添加新增按钮
docs(路由): 添加自动接入任务新增页面的路由配置
max_liu пре 1 недеља
родитељ
комит
06b1d74deb

+ 143 - 0
server/routes/PendingToolsAdd.js

@@ -0,0 +1,143 @@
+import React, { useState } from "react";
+import { Form, Input, Button, Card, message, Space } from "antd";
+import { ArrowLeftOutlined, SaveOutlined } from "@ant-design/icons";
+import { useNavigate } from "react-router-dom";
+import { pendingToolsApi } from "../services/api";
+
+const { TextArea } = Input;
+
+const PendingToolsAdd = () => {
+  const [form] = Form.useForm();
+  const [loading, setLoading] = useState(false);
+  const navigate = useNavigate();
+
+  // 生成search_task_id的函数(基于Python代码的JavaScript版本)
+  const generateSearchTaskId = () => {
+    // 获取当前时间,精确到毫秒
+    const now = new Date();
+    const year = now.getFullYear();
+    const month = String(now.getMonth() + 1).padStart(2, "0");
+    const day = String(now.getDate()).padStart(2, "0");
+    const hours = String(now.getHours()).padStart(2, "0");
+    const minutes = String(now.getMinutes()).padStart(2, "0");
+    const seconds = String(now.getSeconds()).padStart(2, "0");
+    const milliseconds = String(now.getMilliseconds()).padStart(3, "0");
+
+    const timestamp = `${year}${month}${day}${hours}${minutes}${seconds}${milliseconds}`;
+
+    // 生成 6 位随机数,范围 100000~999999
+    const randomNumber = Math.floor(Math.random() * (999999 - 100000 + 1)) + 100000;
+
+    return `${timestamp}${randomNumber}`;
+  };
+
+  const handleSubmit = async (values) => {
+    setLoading(true);
+    try {
+      const searchTaskId = generateSearchTaskId();
+      const submitData = {
+        search_task_id: searchTaskId,
+        tools_name: values.toolsName,
+        tools_function_name: values.toolsFunctionName,
+        tools_function_desc: values.toolsFunctionDesc,
+        status: 0, // 默认状态为待处理
+        created_at: new Date().toISOString(),
+        updated_at: new Date().toISOString(),
+      };
+
+      await pendingToolsApi.create(submitData);
+      message.success("新增工具成功!");
+      navigate("/pending-tools");
+    } catch (error) {
+      console.error("新增工具失败:", error);
+      message.error("新增工具失败,请重试");
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  const handleBack = () => {
+    navigate("/pending-tools");
+  };
+
+  return (
+    <div className="p-6">
+      <Card
+        title={
+          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%" }}>
+            <Button
+              type="text"
+              icon={<ArrowLeftOutlined />}
+              onClick={handleBack}
+            >
+              返回列表
+            </Button>
+            <span style={{ fontSize: "18px", fontWeight: "bold", flex: 1, textAlign: "center" }}>新增待接入工具</span>
+            <div style={{ width: "80px" }}></div> {/* 占位元素,保持标题居中 */}
+          </div>
+        }
+      >
+        <Form
+          form={form}
+          layout="vertical"
+          onFinish={handleSubmit}
+          autoComplete="off"
+        >
+          <Form.Item
+            label="工具名称"
+            name="toolsName"
+            rules={[
+              { required: true, message: "请输入工具名称" },
+              { max: 100, message: "工具名称不能超过100个字符" },
+            ]}
+          >
+            <Input placeholder="请输入工具名称" />
+          </Form.Item>
+
+          <Form.Item
+            label="工具功能名称"
+            name="toolsFunctionName"
+            rules={[
+              { required: true, message: "请输入工具功能名称" },
+              { max: 200, message: "工具功能名称不能超过200个字符" },
+            ]}
+          >
+            <Input placeholder="请输入工具功能名称" />
+          </Form.Item>
+
+          <Form.Item
+            label="工具功能描述"
+            name="toolsFunctionDesc"
+            rules={[
+              { required: true, message: "请输入工具功能描述" },
+              { max: 1000, message: "工具功能描述不能超过1000个字符" },
+            ]}
+          >
+            <TextArea
+              placeholder="请输入工具功能描述"
+              rows={6}
+              showCount
+              maxLength={1000}
+            />
+          </Form.Item>
+
+          <Form.Item style={{ textAlign: 'center' }}>
+            <Space>
+              <Button
+                type="primary"
+                htmlType="submit"
+                loading={loading}
+                icon={<SaveOutlined />}
+              >
+                保存
+              </Button>
+              <Button onClick={handleBack}>取消</Button>
+            </Space>
+          </Form.Item>
+        </Form>
+      </Card>
+    </div>
+  );
+};
+
+export default PendingToolsAdd;

+ 110 - 32
server/routes/autoAccessTasks.js

@@ -1,8 +1,8 @@
-const express = require('express');
+const express = require("express");
 const router = express.Router();
-const { executeQuery } = require('../config/database');
+const { executeQuery } = require("../config/database");
 
-router.get('/', async (req, res) => {
+router.get("/", async (req, res) => {
   try {
     const { page = 1, pageSize = 10, search, status } = req.query;
     const offset = (page - 1) * pageSize;
@@ -13,22 +13,22 @@ router.get('/', async (req, res) => {
 
     // 添加搜索条件
     if (search) {
-      whereConditions.push('tools_name LIKE ?');
+      whereConditions.push("tools_name LIKE ?");
       queryParams.push(`%${search}%`);
     }
 
     // 添加状态过滤条件
-    if (status !== undefined && status !== '') {
-      whereConditions.push('status = ?');
+    if (status !== undefined && status !== "") {
+      whereConditions.push("status = ?");
       queryParams.push(parseInt(status));
     }
 
     // 构建WHERE子句
-    const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(' AND ')}` : '';
+    const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(" AND ")}` : "";
 
     const sql = `
       SELECT access_task_id, search_task_id, tools_name, tools_function_name,
-             access_type, tools_function_desc, api_doc, api_class_name,
+             access_type, tools_function_desc, api_doc, api_class_name, api_provider,
              operate_path_data, origin_content_link, status, fail_reason,
              create_time, update_time
       FROM tools_auto_access_task
@@ -43,30 +43,27 @@ router.get('/', async (req, res) => {
     const sqlParams = [...queryParams, parseInt(pageSize), offset];
     const countParams = [...queryParams];
 
-    const [data, countResult] = await Promise.all([
-      executeQuery(sql, sqlParams),
-      executeQuery(countSql, countParams)
-    ]);
+    const [data, countResult] = await Promise.all([executeQuery(sql, sqlParams), executeQuery(countSql, countParams)]);
 
     res.json({
       data,
       total: countResult[0].total,
       page: parseInt(page),
-      pageSize: parseInt(pageSize)
+      pageSize: parseInt(pageSize),
     });
   } catch (error) {
-    console.error('Error fetching auto access tasks:', error);
-    res.status(500).json({ error: 'Internal server error' });
+    console.error("Error fetching auto access tasks:", error);
+    res.status(500).json({ error: "Internal server error" });
   }
 });
 
-router.get('/:id', async (req, res) => {
+router.get("/:id", async (req, res) => {
   try {
     const { id } = req.params;
 
     const sql = `
       SELECT access_task_id, search_task_id, tools_name, tools_function_name,
-             access_type, tools_function_desc, api_doc, api_class_name,
+             access_type, tools_function_desc, api_doc, api_class_name, api_provider,
              operate_path_data, origin_content_link, status, fail_reason,
              create_time, update_time
       FROM tools_auto_access_task
@@ -76,45 +73,126 @@ router.get('/:id', async (req, res) => {
     const data = await executeQuery(sql, [id]);
 
     if (data.length === 0) {
-      return res.status(404).json({ error: 'Task not found' });
+      return res.status(404).json({ error: "Task not found" });
     }
 
     res.json(data[0]);
   } catch (error) {
-    console.error('Error fetching auto access task detail:', error);
-    res.status(500).json({ error: 'Internal server error' });
+    console.error("Error fetching auto access task detail:", error);
+    res.status(500).json({ error: "Internal server error" });
   }
 });
 
-router.put('/:id', async (req, res) => {
+router.put("/:id", async (req, res) => {
   try {
     const { id } = req.params;
     const {
-      search_task_id, tools_name, tools_function_name, access_type,
-      tools_function_desc, api_doc, api_class_name, operate_path_data,
-      origin_content_link, status, fail_reason
+      search_task_id,
+      tools_name,
+      tools_function_name,
+      access_type,
+      tools_function_desc,
+      api_doc,
+      api_class_name,
+      api_provider,
+      operate_path_data,
+      origin_content_link,
+      status,
+      fail_reason,
     } = req.body;
 
     const sql = `
       UPDATE tools_auto_access_task
       SET search_task_id = ?, tools_name = ?, tools_function_name = ?,
           access_type = ?, tools_function_desc = ?, api_doc = ?,
-          api_class_name = ?, operate_path_data = ?, origin_content_link = ?,
+          api_class_name = ?, api_provider = ?, operate_path_data = ?, origin_content_link = ?,
           status = ?, fail_reason = ?, update_time = NOW()
       WHERE access_task_id = ?
     `;
 
     await executeQuery(sql, [
-      search_task_id, tools_name, tools_function_name, access_type,
-      tools_function_desc, api_doc, api_class_name, operate_path_data,
-      origin_content_link, status, fail_reason, id
+      search_task_id ?? null,
+      tools_name ?? null,
+      tools_function_name ?? null,
+      access_type ?? null,
+      tools_function_desc ?? null,
+      api_doc ?? null,
+      api_class_name ?? null,
+      api_provider ?? null,
+      operate_path_data ?? null,
+      origin_content_link ?? null,
+      status ?? null,
+      fail_reason ?? null,
+      id,
+    ]);
+
+    res.json({ message: "Task updated successfully" });
+  } catch (error) {
+    console.error("Error updating auto access task:", error);
+    res.status(500).json({ error: "Internal server error" });
+  }
+});
+
+router.post("/", async (req, res) => {
+  try {
+    const {
+      access_task_id,
+      tools_name,
+      tools_function_name,
+      tools_function_desc,
+      access_type,
+      api_provider,
+      api_doc,
+      operate_path_data,
+      status = 0,
+    } = req.body;
+
+    // 验证必填字段
+    if (!access_task_id || !tools_name || access_type === undefined) {
+      return res.status(400).json({ error: "Missing required fields" });
+    }
+
+    // 根据接入方式验证条件必填字段
+    if (access_type === "api_no_crack" || access_type === "api_crack") {
+      // API接入方式
+      if (!api_provider || !api_doc) {
+        return res.status(400).json({ error: "API provider and API doc are required for API access type" });
+      }
+    } else if (access_type === "browser_auto_operate") {
+      // 浏览器自动操作接入方式
+      if (!operate_path_data) {
+        return res.status(400).json({ error: "Operation path data is required for browser automation access type" });
+      }
+    }
+
+    const sql = `
+      INSERT INTO tools_auto_access_task (
+        access_task_id, tools_name, tools_function_name, tools_function_desc,
+        access_type, api_provider, api_doc, operate_path_data, status,
+        create_time, update_time
+      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
+    `;
+
+    await executeQuery(sql, [
+      access_task_id,
+      tools_name,
+      tools_function_name || null,
+      tools_function_desc || null,
+      access_type,
+      api_provider || null,
+      api_doc || null,
+      operate_path_data || null,
+      status,
     ]);
 
-    res.json({ message: 'Task updated successfully' });
+    res.status(201).json({ 
+      message: "Auto access task created successfully",
+      access_task_id 
+    });
   } catch (error) {
-    console.error('Error updating auto access task:', error);
-    res.status(500).json({ error: 'Internal server error' });
+    console.error("Error creating auto access task:", error);
+    res.status(500).json({ error: "Internal server error" });
   }
 });
 
-module.exports = router;
+module.exports = router;

+ 2 - 0
src/App.js

@@ -7,6 +7,7 @@ import PendingToolsList from "./pages/PendingToolsList";
 import PendingToolsAdd from "./pages/PendingToolsAdd";
 import PendingToolsDetail from "./pages/PendingToolsDetail";
 import AutoAccessTaskList from "./pages/AutoAccessTaskList";
+import AutoAccessTaskAdd from "./pages/AutoAccessTaskAdd";
 import AutoAccessTaskDetail from "./pages/AutoAccessTaskDetail";
 import ToolsLibraryList from "./pages/ToolsLibraryList";
 import ToolsLibraryDetail from "./pages/ToolsLibraryDetail";
@@ -72,6 +73,7 @@ function AppContent() {
               <Route path="/pending-tools/add" element={<PendingToolsAdd />} />
               <Route path="/pending-tools/:id" element={<PendingToolsDetail />} />
               <Route path="/auto-access-tasks" element={<AutoAccessTaskList />} />
+              <Route path="/auto-access-tasks/add" element={<AutoAccessTaskAdd />} />
               <Route path="/auto-access-tasks/:id" element={<AutoAccessTaskDetail />} />
               <Route path="/tools-library" element={<ToolsLibraryList />} />
               <Route path="/tools-library/:id" element={<ToolsLibraryDetail />} />

+ 219 - 0
src/pages/AutoAccessTaskAdd.js

@@ -0,0 +1,219 @@
+import React, { useState } from "react";
+import { Form, Input, Button, Card, message, Space, Select } from "antd";
+import { ArrowLeftOutlined, SaveOutlined } from "@ant-design/icons";
+import { useNavigate } from "react-router-dom";
+import { autoAccessTasksApi } from "../services/api";
+import { generateSearchTaskId } from "./PendingToolsAdd";
+
+const { TextArea } = Input;
+const { Option } = Select;
+export const ACCESS_PROVIDER_OPTIONS = [
+  { value: "official", label: "official" },
+  { value: "302ai", label: "302ai" },
+];
+const AutoAccessTaskAdd = () => {
+  const [form] = Form.useForm();
+  const [loading, setLoading] = useState(false);
+  const [accessType, setAccessType] = useState(undefined);
+  const navigate = useNavigate();
+
+  // 接入方式选项
+  const ACCESS_TYPE_OPTIONS = [
+    { value: "api_no_crack", label: "API无破解" },
+    { value: "api_crack", label: "API破解" },
+    { value: "browser_auto_operate", label: "浏览器自动操作" },
+  ];
+
+  const handleSubmit = async (values) => {
+    try {
+      setLoading(true);
+
+      const submitData = {
+        access_task_id: generateSearchTaskId(),
+        tools_name: values.toolsName,
+        tools_function_name: values.toolsFunctionName,
+        tools_function_desc: values.toolsFunctionDesc,
+        access_type: values.accessType,
+        api_provider: values.apiProvider || null,
+        api_doc: values.apiDoc || null,
+        operate_path_data: values.operatePathData || null,
+        status: 0, // 默认状态为待执行
+      };
+
+      await autoAccessTasksApi.create(submitData);
+      message.success("新增接入任务成功!");
+      navigate("/auto-access-tasks");
+    } catch (error) {
+      console.error("新增接入任务失败:", error);
+      message.error("新增接入任务失败,请重试");
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  const handleBack = () => {
+    navigate("/auto-access-tasks");
+  };
+
+  const handleAccessTypeChange = (value) => {
+    setAccessType(value);
+    // 清空相关字段
+    form.setFieldsValue({
+      apiProvider: undefined,
+      apiDoc: undefined,
+      operatePathData: undefined,
+    });
+  };
+
+  return (
+    <div className="p-6">
+      <Card
+        title={
+          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%" }}>
+            <Button
+              type="text"
+              icon={<ArrowLeftOutlined />}
+              onClick={handleBack}
+            >
+              返回列表
+            </Button>
+            <span style={{ fontSize: "18px", fontWeight: "bold", flex: 1, textAlign: "center" }}>新增接入任务</span>
+            <div style={{ width: "80px" }}></div>
+          </div>
+        }
+      >
+        <Form
+          form={form}
+          layout="vertical"
+          onFinish={handleSubmit}
+          autoComplete="off"
+        >
+          <Form.Item
+            label="工具名称"
+            name="toolsName"
+            rules={[
+              { required: true, message: "请输入工具名称" },
+              { max: 100, message: "工具名称不能超过100个字符" },
+            ]}
+          >
+            <Input placeholder="请输入工具名称" />
+          </Form.Item>
+
+          <Form.Item
+            label="接入方式"
+            name="accessType"
+            rules={[{ required: true, message: "请选择接入方式" }]}
+          >
+            <Select
+              placeholder="请选择接入方式"
+              onChange={handleAccessTypeChange}
+            >
+              {ACCESS_TYPE_OPTIONS.map((option) => (
+                <Option
+                  key={option.value}
+                  value={option.value}
+                >
+                  {option.label}
+                </Option>
+              ))}
+            </Select>
+          </Form.Item>
+
+          {/* API接入方式时显示的字段 */}
+          {(accessType === "api_no_crack" || accessType === "api_crack") && (
+            <>
+              <Form.Item
+                label="API提供方"
+                name="apiProvider"
+                rules={[{ required: true, message: "请选择API提供方" }]}
+              >
+                <Select placeholder="请选择API提供方">
+                  {ACCESS_PROVIDER_OPTIONS.map((option) => (
+                    <Option
+                      key={option.value}
+                      value={option.value}
+                    >
+                      {option.label}
+                    </Option>
+                  ))}
+                </Select>
+              </Form.Item>
+
+              <Form.Item
+                label="API文档"
+                name="apiDoc"
+                rules={[
+                  { required: true, message: "请输入API文档" },
+                  { max: 500, message: "API文档不能超过500个字符" },
+                ]}
+              >
+                <TextArea
+                  rows={4}
+                  placeholder="请输入API文档内容或链接"
+                  showCount
+                  maxLength={500}
+                />
+              </Form.Item>
+            </>
+          )}
+
+          {/* 浏览器自动操作接入方式时显示的字段 */}
+          {accessType === "browser_auto_operate" && (
+            <Form.Item
+              label="操作路径数据"
+              name="operatePathData"
+              rules={[
+                { required: true, message: "请输入操作路径数据" },
+                { max: 1000, message: "操作路径数据不能超过1000个字符" },
+              ]}
+            >
+              <TextArea
+                rows={6}
+                placeholder="请输入浏览器自动操作的路径数据"
+                showCount
+                maxLength={1000}
+              />
+            </Form.Item>
+          )}
+
+          <Form.Item
+            label="工具功能名称"
+            name="toolsFunctionName"
+            rules={[{ max: 100, message: "工具功能名称不能超过100个字符" }]}
+          >
+            <Input placeholder="请输入工具功能名称" />
+          </Form.Item>
+
+          <Form.Item
+            label="工具功能描述"
+            name="toolsFunctionDesc"
+            rules={[{ max: 500, message: "工具功能描述不能超过500个字符" }]}
+          >
+            <TextArea
+              rows={4}
+              placeholder="请输入工具功能描述"
+              showCount
+              maxLength={500}
+            />
+          </Form.Item>
+
+          <Form.Item style={{ textAlign: "center" }}>
+            <Space>
+              <Button
+                type="primary"
+                htmlType="submit"
+                loading={loading}
+                icon={<SaveOutlined />}
+              >
+                保存
+              </Button>
+              <Button onClick={handleBack}>取消</Button>
+            </Space>
+          </Form.Item>
+        </Form>
+      </Card>
+    </div>
+  );
+};
+
+export default AutoAccessTaskAdd;

+ 124 - 84
src/pages/AutoAccessTaskDetail.js

@@ -5,6 +5,7 @@ import { ArrowLeftOutlined, CopyOutlined } from "@ant-design/icons";
 import { autoAccessTasksApi } from "../services/api";
 import moment from "moment";
 import { STATUS_MAP, STATUS_TAG_COLOR } from "./AutoAccessTaskList";
+import { ACCESS_PROVIDER_OPTIONS } from "./AutoAccessTaskAdd";
 
 const { TextArea } = Input;
 const { Option } = Select;
@@ -27,6 +28,7 @@ const AutoAccessTaskDetail = () => {
   const [data, setData] = useState(null);
   const [loading, setLoading] = useState(true);
   const [saving, setSaving] = useState(false);
+  const [accessType, setAccessType] = useState(undefined);
   const { id } = useParams();
   const navigate = useNavigate();
   const location = useLocation();
@@ -54,6 +56,7 @@ const AutoAccessTaskDetail = () => {
       const response = await autoAccessTasksApi.getDetail(id);
       setData(response.data);
       form.setFieldsValue(response.data);
+      setAccessType(response.data.access_type);
     } catch (error) {
       message.error("获取详情失败");
     } finally {
@@ -90,6 +93,21 @@ const AutoAccessTaskDetail = () => {
     }
   };
 
+  const handleAccessTypeChange = (value) => {
+    setAccessType(value);
+    // 当接入方式改变时,清空相关字段的值
+    if (value === "browser_auto_operate") {
+      form.setFieldsValue({
+        api_provider: undefined,
+        api_doc: undefined,
+      });
+    } else if (value === "api_no_crack" || value === "api_crack") {
+      form.setFieldsValue({
+        operate_path_data: undefined,
+      });
+    }
+  };
+
   useEffect(() => {
     fetchData();
   }, [id]);
@@ -128,16 +146,15 @@ const AutoAccessTaskDetail = () => {
             className="w-full"
           >
             <Row gutter={24}>
-              <Col span={12}>
+              {/* <Col span={12}>
                 <Form.Item
                   label="检索任务ID"
                   name="search_task_id"
                 >
                   <Input />
                 </Form.Item>
-              </Col>
+              </Col> */}
               <Col span={12}>
-                {" "}
                 <Form.Item
                   label="工具名称"
                   name="tools_name"
@@ -146,58 +163,48 @@ const AutoAccessTaskDetail = () => {
                   <Input />
                 </Form.Item>
               </Col>
-            </Row>
-            <Row gutter={24}>
-              <Col span={12}>
-                <Form.Item
-                  label="状态"
-                  name="status"
-                  rules={[{ required: true, message: "请选择状态" }]}
-                >
-                  <Select>
-                    {STATUS_OPTIONS.map((option) => (
-                      <Option
-                        key={option.value}
-                        value={option.value}
-                      >
-                        {option.label}
-                      </Option>
-                    ))}
-                  </Select>
-                </Form.Item>
-              </Col>
-              <Col span={12}>
-                <Form.Item
-                  label="补充ApiKey"
-                  name="api_key"
-                >
-                  <Input />
-                </Form.Item>
-              </Col>
-            </Row>
-            <Row gutter={24}>
               <Col span={12}>
                 <Form.Item
                   label="工具功能名称"
                   name="tools_function_name"
-                  rules={[{ required: true, message: "请输入工具功能名称" }]}
+                  rules={[{ required: false, message: "请输入工具功能名称" }]}
                 >
                   <Input />
                 </Form.Item>
               </Col>
+            </Row>
+            <Row gutter={24}>
               <Col span={12}>
                 <Form.Item
                   label="接入方式"
                   name="access_type"
                   rules={[{ required: true, message: "请选择接入方式" }]}
                 >
-                  <Select>
+                  <Select onChange={handleAccessTypeChange}>
                     <Option value="api_no_crack">API无破解</Option>
                     <Option value="api_crack">API破解</Option>
                     <Option value="browser_auto_operate">浏览器自动操作</Option>
                   </Select>
                 </Form.Item>
               </Col>
+              <Col span={12}>
+                <Form.Item
+                  label="状态"
+                  name="status"
+                  rules={[{ required: false, message: "请选择状态" }]}
+                >
+                  <Select>
+                    {STATUS_OPTIONS.map((option) => (
+                      <Option
+                        key={option.value}
+                        value={option.value}
+                      >
+                        {option.label}
+                      </Option>
+                    ))}
+                  </Select>
+                </Form.Item>
+              </Col>
             </Row>
             <Row gutter={24}>
               <Col span={12}>
@@ -210,66 +217,98 @@ const AutoAccessTaskDetail = () => {
               </Col>
               <Col span={12}>
                 <Form.Item
-                  label="源内容链接"
-                  name="origin_content_link"
+                  label="补充ApiKey"
+                  name="api_key"
                 >
                   <Input />
                 </Form.Item>
               </Col>
             </Row>
+
             <Row gutter={24}>
-              <Col span={24}>
-                <Form.Item
-                  label={
-                    <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
-                      API文档
-                      <Tooltip title="点击复制API文档">
-                        <Button
-                          type="text"
-                          style={{ color: "blue" }}
-                          size="small"
-                          icon={<CopyOutlined />}
-                          onClick={handleCopyApiDoc}
-                        />
-                      </Tooltip>
-                    </div>
-                  }
-                  name="api_doc"
-                >
-                  <TextArea rows={6} />
-                </Form.Item>
-              </Col>
-            </Row>
-            <Row gutter={24}>
-              <Col span={24}>
-                <Form.Item
-                  label="操作路径数据"
-                  name="operate_path_data"
-                >
-                  <TextArea rows={4} />
-                </Form.Item>
-              </Col>
-            </Row>
-            <Row gutter={24}>
-              <Col span={24}>
-                <Form.Item
-                  label="工具功能描述"
-                  name="tools_function_desc"
-                >
-                  <TextArea rows={4} />
-                </Form.Item>
-              </Col>
-            </Row>
-            <Row gutter={24}>
-              <Col span={24}>
+              <Col span={12}>
                 <Form.Item
-                  label="失败原因"
-                  name="fail_reason"
+                  label="API提供方"
+                  name="api_provider"
+                  rules={[
+                    {
+                      required: true,
+                      message: "请输入API提供方",
+                    },
+                  ]}
                 >
-                  <TextArea rows={3} />
+                  <Select placeholder="请选择API提供方">
+                    {ACCESS_PROVIDER_OPTIONS.map((option) => (
+                      <Option
+                        key={option.value}
+                        value={option.value}
+                      >
+                        {option.label}
+                      </Option>
+                    ))}
+                  </Select>
                 </Form.Item>
               </Col>
             </Row>
+            <Form.Item
+              label={
+                <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
+                  API文档
+                  <Tooltip title="点击复制API文档">
+                    <Button
+                      type="text"
+                      style={{ color: "blue" }}
+                      size="small"
+                      icon={<CopyOutlined />}
+                      onClick={handleCopyApiDoc}
+                    />
+                  </Tooltip>
+                </div>
+              }
+              name="api_doc"
+              rules={[
+                {
+                  required: accessType === "api_no_crack" || accessType === "api_crack",
+                  message: "请输入API文档",
+                },
+              ]}
+            >
+              <TextArea rows={6} />
+            </Form.Item>
+
+            <Form.Item
+              label="操作路径数据"
+              name="operate_path_data"
+              rules={[
+                {
+                  required: accessType === "browser_auto_operate",
+                  message: "请输入操作路径数据",
+                },
+              ]}
+            >
+              <TextArea rows={4} />
+            </Form.Item>
+
+            <Form.Item
+              label="源内容链接"
+              name="origin_content_link"
+            >
+              <Input />
+            </Form.Item>
+
+            <Form.Item
+              label="工具功能描述"
+              name="tools_function_desc"
+            >
+              <TextArea rows={4} />
+            </Form.Item>
+
+            <Form.Item
+              label="失败原因"
+              name="fail_reason"
+            >
+              <TextArea rows={3} />
+            </Form.Item>
 
             <div className="button-group">
               <Button onClick={() => navigate("/auto-access-tasks")}>取消</Button>
@@ -348,6 +387,7 @@ const AutoAccessTaskDetail = () => {
               </div>
             </Descriptions.Item>
             <Descriptions.Item label="API类名">{data.api_class_name}</Descriptions.Item>
+            <Descriptions.Item label="API提供方">{data.api_provider}</Descriptions.Item>
             <Descriptions.Item label="源内容链接">
               {data.origin_content_link ? (
                 <a

+ 31 - 18
src/pages/AutoAccessTaskList.js

@@ -1,6 +1,6 @@
 import React, { useState, useEffect } from "react";
 import { Table, Button, Space, Tag, message, Tooltip, Input, Select, Row, Col } from "antd";
-import { EditOutlined, EyeOutlined, SearchOutlined, ReloadOutlined } from "@ant-design/icons";
+import { EditOutlined, EyeOutlined, SearchOutlined, ReloadOutlined, PlusOutlined } from "@ant-design/icons";
 import { useNavigate } from "react-router-dom";
 import { autoAccessTasksApi } from "../services/api";
 import moment from "moment";
@@ -97,9 +97,9 @@ const AutoAccessTaskList = () => {
     },
 
     {
-      title: "API类名",
-      dataIndex: "api_class_name",
-      key: "api_class_name",
+      title: "API提供方",
+      dataIndex: "api_provider",
+      key: "api_provider",
       width: 200,
       ellipsis: true,
       render: (text) => (
@@ -230,33 +230,35 @@ const AutoAccessTaskList = () => {
 
   return (
     <div className="table-container">
-      <div style={{ marginBottom: 16, padding: 16, backgroundColor: '#fafafa', borderRadius: 6 }}>
-        <Row gutter={[16, 16]}>
-          <Col span={6}>
+      <div className="mb-6">
+        {/* 新的搜索区域 */}
+        <div className="bg-gray-50 p-4 rounded-lg mb-4 flex justify-between items-center">
+          <div className="grid grid-cols-1 md:grid-cols-5 gap-4 items-end">
             <Input
               placeholder="搜索工具名称"
               value={searchText}
               onChange={(e) => setSearchText(e.target.value)}
               allowClear
             />
-          </Col>
-          <Col span={6}>
+
             <Select
               placeholder="选择状态"
               value={statusFilter}
               onChange={handleStatusChange}
               allowClear
-              style={{ width: '100%' }}
+              style={{ width: "100%" }}
             >
               {Object.entries(STATUS_MAP).map(([key, value]) => (
-                <Option key={key} value={parseInt(key)}>
+                <Option
+                  key={key}
+                  value={parseInt(key)}
+                >
                   {value}
                 </Option>
               ))}
             </Select>
-          </Col>
-          <Col span={6}>
-            <Space>
+
+            <div className="flex space-x-2">
               <Button
                 type="primary"
                 icon={<SearchOutlined />}
@@ -270,11 +272,22 @@ const AutoAccessTaskList = () => {
               >
                 重置
               </Button>
-            </Space>
-          </Col>
-        </Row>
+            </div>
+          </div>
+
+          <div>
+            <Button
+              type="primary"
+              icon={<PlusOutlined />}
+              onClick={() => navigate("/auto-access-tasks/add")}
+              className="w-full"
+            >
+              新增
+            </Button>
+          </div>
+        </div>
       </div>
-      
+
       <Table
         columns={columns}
         dataSource={data}

+ 20 - 21
src/pages/PendingToolsAdd.js

@@ -5,31 +5,30 @@ import { useNavigate } from "react-router-dom";
 import { pendingToolsApi } from "../services/api";
 
 const { TextArea } = Input;
+export const generateSearchTaskId = () => {
+  // 获取当前时间,精确到毫秒
+  const now = new Date();
+  const year = now.getFullYear();
+  const month = String(now.getMonth() + 1).padStart(2, "0");
+  const day = String(now.getDate()).padStart(2, "0");
+  const hours = String(now.getHours()).padStart(2, "0");
+  const minutes = String(now.getMinutes()).padStart(2, "0");
+  const seconds = String(now.getSeconds()).padStart(2, "0");
+  const milliseconds = String(now.getMilliseconds()).padStart(3, "0");
 
+  const timestamp = `${year}${month}${day}${hours}${minutes}${seconds}${milliseconds}`;
+
+  // 生成 6 位随机数,范围 100000~999999
+  const randomNumber = Math.floor(Math.random() * (999999 - 100000 + 1)) + 100000;
+
+  return `${timestamp}${randomNumber}`;
+};
 const PendingToolsAdd = () => {
   const [form] = Form.useForm();
   const [loading, setLoading] = useState(false);
   const navigate = useNavigate();
 
   // 生成search_task_id的函数(基于Python代码的JavaScript版本)
-  const generateSearchTaskId = () => {
-    // 获取当前时间,精确到毫秒
-    const now = new Date();
-    const year = now.getFullYear();
-    const month = String(now.getMonth() + 1).padStart(2, "0");
-    const day = String(now.getDate()).padStart(2, "0");
-    const hours = String(now.getHours()).padStart(2, "0");
-    const minutes = String(now.getMinutes()).padStart(2, "0");
-    const seconds = String(now.getSeconds()).padStart(2, "0");
-    const milliseconds = String(now.getMilliseconds()).padStart(3, "0");
-
-    const timestamp = `${year}${month}${day}${hours}${minutes}${seconds}${milliseconds}`;
-
-    // 生成 6 位随机数,范围 100000~999999
-    const randomNumber = Math.floor(Math.random() * (999999 - 100000 + 1)) + 100000;
-
-    return `${timestamp}${randomNumber}`;
-  };
 
   const handleSubmit = async (values) => {
     setLoading(true);
@@ -98,7 +97,7 @@ const PendingToolsAdd = () => {
             label="工具功能名称"
             name="toolsFunctionName"
             rules={[
-              { required: true, message: "请输入工具功能名称" },
+              { required: false, message: "请输入工具功能名称" },
               { max: 200, message: "工具功能名称不能超过200个字符" },
             ]}
           >
@@ -109,7 +108,7 @@ const PendingToolsAdd = () => {
             label="工具功能描述"
             name="toolsFunctionDesc"
             rules={[
-              { required: true, message: "请输入工具功能描述" },
+              { required: false, message: "请输入工具功能描述" },
               { max: 1000, message: "工具功能描述不能超过1000个字符" },
             ]}
           >
@@ -121,7 +120,7 @@ const PendingToolsAdd = () => {
             />
           </Form.Item>
 
-          <Form.Item style={{ textAlign: 'center' }}>
+          <Form.Item style={{ textAlign: "center" }}>
             <Space>
               <Button
                 type="primary"

+ 2 - 2
src/pages/PendingToolsDetail.js

@@ -109,14 +109,14 @@ const PendingToolsDetail = () => {
             <Form.Item
               label="工具功能名称"
               name="tools_function_name"
-              rules={[{ required: true, message: "请输入工具功能名称" }]}
+              rules={[{ required: false, message: "请输入工具功能名称" }]}
             >
               <Input />
             </Form.Item>
             <Form.Item
               label="状态"
               name="status"
-              rules={[{ required: true, message: "请选择状态" }]}
+              rules={[{ required: false, message: "请选择状态" }]}
             >
               <Select>
                 <Option value={0}>待处理</Option>

+ 39 - 33
src/pages/ToolsLibraryList.js

@@ -1,6 +1,13 @@
 import React, { useState, useEffect } from "react";
 import { Table, Button, Space, Tag, message, Modal, Tooltip, Input, Select, Row, Col } from "antd";
-import { EditOutlined, EyeOutlined, SendOutlined, SearchOutlined, ReloadOutlined } from "@ant-design/icons";
+import {
+  EditOutlined,
+  EyeOutlined,
+  SendOutlined,
+  SearchOutlined,
+  ReloadOutlined,
+  PlusOutlined,
+} from "@ant-design/icons";
 import { useNavigate } from "react-router-dom";
 import { toolsLibraryApi } from "../services/api";
 import moment from "moment";
@@ -54,9 +61,8 @@ const ToolsLibraryList = () => {
 
   const getApiProviderText = (provider) => {
     const providerMap = {
-      official: "官方",
-      "302ai": "302AI",
-      official_api: "官方API",
+      official: "official",
+      "302ai": "302ai",
     };
     return providerMap[provider] || provider;
   };
@@ -221,23 +227,23 @@ const ToolsLibraryList = () => {
         page,
         pageSize,
       };
-      
+
       // 使用传入的搜索参数或当前的搜索表单状态
       const currentSearchParams = searchParams || searchForm;
-      
+
       // 添加搜索参数
       if (currentSearchParams.toolsName) {
         params.toolsName = currentSearchParams.toolsName;
       }
-      
+
       if (currentSearchParams.mcpToolsName) {
         params.mcpToolsName = currentSearchParams.mcpToolsName;
       }
-      
-      if (currentSearchParams.status !== undefined && currentSearchParams.status !== '') {
+
+      if (currentSearchParams.status !== undefined && currentSearchParams.status !== "") {
         params.status = currentSearchParams.status;
       }
-      
+
       const response = await toolsLibraryApi.getList(params);
       setData(response.data.data);
       setPagination({
@@ -294,12 +300,12 @@ const ToolsLibraryList = () => {
   const handleSearchFormChange = (field, value) => {
     const newSearchForm = {
       ...searchForm,
-      [field]: value
+      [field]: value,
     };
     setSearchForm(newSearchForm);
-    
+
     // 只有状态选择时才自动刷新数据,工具名称和MCP工具名称需要点击搜索按钮
-    if (field === 'status') {
+    if (field === "status") {
       fetchData(1, pagination.pageSize, newSearchForm);
     }
   };
@@ -310,42 +316,42 @@ const ToolsLibraryList = () => {
 
   return (
     <div className="table-container">
-      {/* 搜索区域 */}
-      <div style={{ marginBottom: 16, padding: 16, backgroundColor: '#fafafa', borderRadius: 6 }}>
-        <Row gutter={[16, 16]}>
-          <Col span={6}>
+      <div className="mb-6">
+        {/* 新的搜索区域 */}
+        <div className="bg-gray-50 p-4 rounded-lg mb-4 flex justify-between items-center">
+          <div className="grid grid-cols-1 md:grid-cols-5 gap-4 items-end">
             <Input
               placeholder="搜索工具名称"
               value={searchForm.toolsName}
-              onChange={(e) => handleSearchFormChange('toolsName', e.target.value)}
+              onChange={(e) => handleSearchFormChange("toolsName", e.target.value)}
               allowClear
             />
-          </Col>
-          <Col span={6}>
+
             <Input
               placeholder="搜索MCP工具名称"
               value={searchForm.mcpToolsName}
-              onChange={(e) => handleSearchFormChange('mcpToolsName', e.target.value)}
+              onChange={(e) => handleSearchFormChange("mcpToolsName", e.target.value)}
               allowClear
             />
-          </Col>
-          <Col span={6}>
+
             <Select
               placeholder="选择状态"
               value={searchForm.status}
-              onChange={(value) => handleSearchFormChange('status', value)}
+              onChange={(value) => handleSearchFormChange("status", value)}
               allowClear
-              style={{ width: '100%' }}
+              style={{ width: "100%" }}
             >
               {Object.entries(STATUS_MAP).map(([key, value]) => (
-                <Option key={key} value={key}>
+                <Option
+                  key={key}
+                  value={key}
+                >
                   {value}
                 </Option>
               ))}
             </Select>
-          </Col>
-          <Col span={6}>
-            <Space>
+
+            <div className="flex space-x-2">
               <Button
                 type="primary"
                 icon={<SearchOutlined />}
@@ -359,11 +365,11 @@ const ToolsLibraryList = () => {
               >
                 重置
               </Button>
-            </Space>
-          </Col>
-        </Row>
+            </div>
+          </div>
+        </div>
       </div>
-      
+
       <Table
         columns={columns}
         dataSource={data}

+ 1 - 0
src/services/api.js

@@ -30,6 +30,7 @@ export const pendingToolsApi = {
 export const autoAccessTasksApi = {
   getList: (params) => api.get("/auto-access-tasks", { params }),
   getDetail: (id) => api.get(`/auto-access-tasks/${id}`),
+  create: (data) => api.post("/auto-access-tasks", data),
   update: (id, data) => api.put(`/auto-access-tasks/${id}`, data),
 };