ソースを参照

feat(accounts): 添加账号管理功能

实现账号管理的CRUD功能,包括账号列表、详情查看和编辑
在多个页面添加网站名称和网站地址字段
更新API基础URL配置以支持环境变量
max_liu 2 ヶ月 前
コミット
c0e261cd0a

+ 109 - 0
server/routes/accounts.js

@@ -0,0 +1,109 @@
+const express = require("express");
+const router = express.Router();
+const { executeQuery } = require("../config/database");
+
+// 列表:支持按 account 和 status 查询,分页
+router.get("/", async (req, res) => {
+  try {
+    const { page = 1, pageSize = 10, account, status } = req.query;
+    const offset = (page - 1) * pageSize;
+
+    let whereConditions = [];
+    let sqlParams = [];
+    let countParams = [];
+
+    if (account) {
+      whereConditions.push("account LIKE ?");
+      sqlParams.push(`%${account}%`);
+      countParams.push(`%${account}%`);
+    }
+
+    if (status !== undefined && status !== "") {
+      whereConditions.push("status = ?");
+      const statusVal = parseInt(status);
+      sqlParams.push(statusVal);
+      countParams.push(statusVal);
+    }
+
+    const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(" AND ")}` : "";
+
+    const sql = `
+      SELECT id, hostname, account, info, status, create_time, update_time
+      FROM chrome_login_data
+      ${whereClause}
+      ORDER BY create_time DESC
+      LIMIT ? OFFSET ?
+    `;
+
+    const countSql = `SELECT COUNT(*) as total FROM chrome_login_data ${whereClause}`;
+
+    sqlParams.push(parseInt(pageSize), offset);
+
+    const [data, countResult] = await Promise.all([
+      executeQuery(sql, sqlParams),
+      executeQuery(countSql, countParams),
+    ]);
+
+    res.json({
+      data,
+      total: countResult[0]?.total || 0,
+      page: parseInt(page),
+      pageSize: parseInt(pageSize),
+    });
+  } catch (error) {
+    console.error("Error fetching accounts:", error);
+    res.status(500).json({ error: "Internal server error" });
+  }
+});
+
+// 详情
+router.get("/:id", async (req, res) => {
+  try {
+    const { id } = req.params;
+    const sql = `
+      SELECT id, hostname, account, info, status, create_time, update_time
+      FROM chrome_login_data
+      WHERE id = ?
+    `;
+
+    const rows = await executeQuery(sql, [id]);
+    if (!rows || rows.length === 0) {
+      return res.status(404).json({ error: "Account not found" });
+    }
+
+    res.json(rows[0]);
+  } catch (error) {
+    console.error("Error fetching account detail:", error);
+    res.status(500).json({ error: "Internal server error" });
+  }
+});
+
+// 编辑更新
+router.put("/:id", async (req, res) => {
+  try {
+    const { id } = req.params;
+    const { hostname, account, info, status } = req.body;
+
+    const sql = `
+      UPDATE chrome_login_data
+      SET hostname = ?, account = ?, info = ?, status = ?, update_time = NOW()
+      WHERE id = ?
+    `;
+
+    const params = [
+      hostname ?? null,
+      account ?? null,
+      info ?? null,
+      status ?? null,
+      id,
+    ];
+
+    await executeQuery(sql, params);
+    res.json({ message: "Account updated successfully" });
+  } catch (error) {
+    console.error("Error updating account:", error);
+    res.status(500).json({ error: "Internal server error" });
+  }
+});
+
+module.exports = router;

+ 13 - 5
server/routes/autoAccessTasks.js

@@ -29,7 +29,7 @@ router.get("/", async (req, res) => {
     const sql = `
       SELECT access_task_id, 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, website_location, status, fail_reason,
+             operate_path_data, origin_content_link, website_location, website_name, website_address, status, fail_reason,
              create_time, update_time
       FROM tools_auto_access_task
       ${whereClause}
@@ -64,7 +64,7 @@ router.get("/:id", async (req, res) => {
     const sql = `
       SELECT access_task_id, 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, website_location, status, fail_reason,
+             operate_path_data, origin_content_link, website_location, website_name, website_address, status, fail_reason,
              create_time, update_time
       FROM tools_auto_access_task
       WHERE access_task_id = ?
@@ -98,6 +98,8 @@ router.put("/:id", async (req, res) => {
       operate_path_data,
       origin_content_link,
       website_location,
+      website_name,
+      website_address,
       status,
       fail_reason,
     } = req.body;
@@ -107,7 +109,7 @@ router.put("/:id", async (req, res) => {
       SET 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 = ?,
-          website_location = ?, status = ?, fail_reason = ?, update_time = NOW()
+          website_location = ?, website_name = ?, website_address = ?, status = ?, fail_reason = ?, update_time = NOW()
       WHERE access_task_id = ?
     `;
 
@@ -123,6 +125,8 @@ router.put("/:id", async (req, res) => {
       operate_path_data ?? null,
       origin_content_link ?? null,
       website_location ?? null,
+      website_name ?? null,
+      website_address ?? null,
       status ?? null,
       fail_reason ?? null,
       id,
@@ -147,6 +151,8 @@ router.post("/", async (req, res) => {
       api_doc,
       operate_path_data,
       website_location,
+      website_name,
+      website_address,
     } = req.body;
 
     // 验证必填字段
@@ -170,9 +176,9 @@ router.post("/", async (req, res) => {
     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, website_location,
+        access_type, api_provider, api_doc, operate_path_data, website_location, website_name, website_address,
         create_time, update_time
-      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
+      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
     `;
 
     // 保证 operate_path_data 为字符串(可能传入对象)
@@ -189,6 +195,8 @@ router.post("/", async (req, res) => {
       api_doc || null,
       operatePathValue || null,
       website_location || null,
+      website_name || null,
+      website_address || null,
     ]);
 
     res.status(201).json({

+ 7 - 3
server/routes/toolsLibrary.js

@@ -35,7 +35,7 @@ router.get("/", async (req, res) => {
     const sql = `
       SELECT tools_id, tools_name, tools_function_name, mcp_tools_name, tools_full_name,
              tools_desc, tools_version, access_task_id, status, call_type,
-             api_provider, api_url_path, website_location, create_time, update_time
+             api_provider, api_url_path, website_location, website_name, website_address, create_time, update_time
       FROM tools_library
       ${whereClause}
       ORDER BY create_time DESC
@@ -71,7 +71,7 @@ router.get("/:id", async (req, res) => {
     const sql = `
       SELECT tools_id, tools_name, tools_function_name, mcp_tools_name, tools_full_name,
              tools_desc, tools_version, access_task_id, status, call_type,
-             api_provider, api_url_path, website_location, operate_path_data, params_definition,
+             api_provider, api_url_path, website_location, website_name, website_address, operate_path_data, params_definition,
              response_desc, create_time, update_time
       FROM tools_library
       WHERE tools_id = ?
@@ -106,6 +106,8 @@ router.put("/:id", async (req, res) => {
       api_provider,
       api_url_path,
       website_location,
+      website_name,
+      website_address,
       operate_path_data,
       params_definition,
       response_desc,
@@ -116,7 +118,7 @@ router.put("/:id", async (req, res) => {
       SET tools_name = ?, tools_function_name = ?, mcp_tools_name = ?, tools_full_name = ?,
           tools_desc = ?, tools_version = ?, access_task_id = ?,
           status = ?, call_type = ?, api_provider = ?, api_url_path = ?,
-          website_location = ?,
+          website_location = ?, website_name = ?, website_address = ?,
           operate_path_data = ?, params_definition = ?, response_desc = ?,
           update_time = NOW()
       WHERE tools_id = ?
@@ -136,6 +138,8 @@ router.put("/:id", async (req, res) => {
       api_provider ?? null,
       api_url_path ?? null,
       website_location ?? null,
+      website_name ?? null,
+      website_address ?? null,
       operate_path_data ?? null,
       params_definition ?? null,
       response_desc ?? null,

+ 2 - 0
server/server.js

@@ -6,6 +6,7 @@ const { createPool } = require("./config/database");
 const pendingToolsRoutes = require("./routes/pendingTools");
 const autoAccessTasksRoutes = require("./routes/autoAccessTasks");
 const toolsLibraryRoutes = require("./routes/toolsLibrary");
+const accountsRoutes = require("./routes/accounts");
 
 const app = express();
 const PORT = process.env.PORT || 3001;
@@ -32,6 +33,7 @@ createPool();
 app.use("/api/pending-tools", pendingToolsRoutes);
 app.use("/api/auto-access-tasks", autoAccessTasksRoutes);
 app.use("/api/tools-library", toolsLibraryRoutes);
+app.use("/api/accounts", accountsRoutes);
 
 app.get("/api/health", (req, res) => {
   res.json({ status: "OK", message: "Server is running" });

+ 68 - 16
src/App.js

@@ -1,7 +1,7 @@
 import React from "react";
 import { BrowserRouter as Router, Routes, Route, Navigate, useLocation, useNavigate } from "react-router-dom";
 import { Layout, Menu } from "antd";
-import { ToolOutlined, SearchOutlined, AppstoreOutlined } from "@ant-design/icons";
+import { ToolOutlined, SearchOutlined, AppstoreOutlined, UserOutlined } from "@ant-design/icons";
 import Dashboard from "./components/Dashboard";
 import PendingToolsList from "./pages/PendingToolsList";
 import PendingToolsAdd from "./pages/PendingToolsAdd";
@@ -11,6 +11,8 @@ import AutoAccessTaskAdd from "./pages/AutoAccessTaskAdd";
 import AutoAccessTaskDetail from "./pages/AutoAccessTaskDetail";
 import ToolsLibraryList from "./pages/ToolsLibraryList";
 import ToolsLibraryDetail from "./pages/ToolsLibraryDetail";
+import AccountList from "./pages/AccountList";
+import AccountDetail from "./pages/AccountDetail";
 import "./App.css";
 
 const { Header, Content, Sider } = Layout;
@@ -22,10 +24,11 @@ function AppContent() {
   // 根据当前路径确定选中的菜单项
   const getSelectedKey = () => {
     const path = location.pathname;
-    if (path.startsWith('/pending-tools')) return ['1'];
-    if (path.startsWith('/auto-access-tasks')) return ['2'];
-    if (path.startsWith('/tools-library')) return ['3'];
-    return ['1']; // 默认选中第一项
+    if (path.startsWith("/pending-tools")) return ["1"];
+    if (path.startsWith("/auto-access-tasks")) return ["2"];
+    if (path.startsWith("/tools-library")) return ["3"];
+    if (path.startsWith("/accounts")) return ["4"];
+    return ["1"]; // 默认选中第一项
   };
 
   return (
@@ -61,22 +64,71 @@ function AppContent() {
                 label: "工具列表",
                 onClick: () => navigate("/tools-library"),
               },
+              {
+                key: "4",
+                icon: <UserOutlined />,
+                label: "平台账号列表",
+                onClick: () => navigate("/accounts"),
+              },
             ]}
           />
         </Sider>
-          <Layout className="ml-[200px] p-6 bg-gray-50">
+        <Layout className="ml-[200px] p-6 bg-gray-50">
           <Content className="bg-white p-6 rounded-lg shadow-sm min-h-screen overflow-y-auto">
             <Routes>
-              <Route path="/" element={<Navigate to="/pending-tools" replace />} />
-              <Route path="/dashboard" element={<Dashboard />} />
-              <Route path="/pending-tools" element={<PendingToolsList />} />
-              <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 />} />
+              <Route
+                path="/"
+                element={
+                  <Navigate
+                    to="/pending-tools"
+                    replace
+                  />
+                }
+              />
+              <Route
+                path="/dashboard"
+                element={<Dashboard />}
+              />
+              <Route
+                path="/pending-tools"
+                element={<PendingToolsList />}
+              />
+              <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 />}
+              />
+              <Route
+                path="/accounts"
+                element={<AccountList />}
+              />
+              <Route
+                path="/accounts/:id"
+                element={<AccountDetail />}
+              />
             </Routes>
           </Content>
         </Layout>

+ 163 - 0
src/pages/AccountDetail.js

@@ -0,0 +1,163 @@
+import React, { useState, useEffect } from "react";
+import { Form, Input, Select, Button, Card, Descriptions, Tag, message, Spin } from "antd";
+import { useParams, useNavigate, useLocation } from "react-router-dom";
+import { ArrowLeftOutlined } from "@ant-design/icons";
+import { accountsApi } from "../services/api";
+import moment from "moment";
+import { ACCOUNT_STATUS_MAP, ACCOUNT_STATUS_TAG_COLOR } from "./AccountList";
+
+const { TextArea } = Input;
+const { Option } = Select;
+
+const AccountDetail = () => {
+  const [form] = Form.useForm();
+  const [data, setData] = useState(null);
+  const [loading, setLoading] = useState(true);
+  const [saving, setSaving] = useState(false);
+  const { id } = useParams();
+  const navigate = useNavigate();
+  const location = useLocation();
+  const isEditMode = location.search.includes("mode=edit");
+
+  const getStatusColor = (status) => ACCOUNT_STATUS_TAG_COLOR[status] || "default";
+  const getStatusText = (status) => ACCOUNT_STATUS_MAP[status] || "未知";
+
+  const fetchData = async () => {
+    try {
+      const response = await accountsApi.getDetail(id);
+      setData(response.data);
+      form.setFieldsValue(response.data);
+    } catch (error) {
+      message.error("获取详情失败");
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  const handleSave = async (values) => {
+    setSaving(true);
+    try {
+      await accountsApi.update(id, values);
+      message.success("更新成功");
+      navigate("/accounts");
+    } catch (error) {
+      const errorMessage = error.response?.data?.error || error.message || "更新失败";
+      message.error(`更新失败: ${errorMessage}`);
+    } finally {
+      setSaving(false);
+    }
+  };
+
+  useEffect(() => {
+    fetchData();
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [id]);
+
+  if (loading) {
+    return (
+      <div style={{ textAlign: "center", padding: 50 }}>
+        <Spin size="large" />
+      </div>
+    );
+  }
+
+  if (!data) {
+    return <div>数据不存在</div>;
+  }
+
+  return (
+    <div className="detail-container">
+      <Button
+        icon={<ArrowLeftOutlined />}
+        onClick={() => navigate("/accounts")}
+        style={{ marginBottom: 16 }}
+      >
+        返回列表
+      </Button>
+
+      {isEditMode ? (
+        <Card
+          title="编辑账号"
+          style={{ marginBottom: 24 }}
+        >
+          <Form
+            form={form}
+            layout="vertical"
+            onFinish={handleSave}
+            className="w-full"
+          >
+            <Form.Item
+              label="站点"
+              name="hostname"
+              rules={[{ required: true, message: "请输入站点Host" }]}
+            >
+              <Input />
+            </Form.Item>
+            <Form.Item
+              label="平台"
+              name="account"
+              rules={[{ required: true, message: "请输入账号名称" }]}
+            >
+              <Input />
+            </Form.Item>
+            <Form.Item
+              label="状态"
+              name="status"
+              rules={[{ required: true, message: "请选择状态" }]}
+            >
+              <Select>
+                <Option value={1}>正常</Option>
+                <Option value={2}>封禁</Option>
+                <Option value={3}>授权信息不可用</Option>
+              </Select>
+            </Form.Item>
+            <Form.Item
+              label="Info"
+              name="info"
+            >
+              <TextArea rows={6} />
+            </Form.Item>
+
+            <Form.Item>
+              <Button
+                type="primary"
+                htmlType="submit"
+                loading={saving}
+              >
+                保存
+              </Button>
+            </Form.Item>
+          </Form>
+        </Card>
+      ) : (
+        <Card
+          title="账号详情"
+          style={{ marginBottom: 24 }}
+        >
+          <Descriptions
+            bordered
+            column={2}
+            size="middle"
+            labelStyle={{ width: "150px" }}
+          >
+            <Descriptions.Item label="站点">{data.hostname}</Descriptions.Item>
+            <Descriptions.Item label="平台">{data.account}</Descriptions.Item>
+            <Descriptions.Item label="状态">
+              <Tag color={getStatusColor(data.status)}>{getStatusText(data.status)}</Tag>
+            </Descriptions.Item>
+            <Descriptions.Item label="创建时间">
+              {moment(data.create_time).format("YYYY-MM-DD HH:mm:ss")}
+            </Descriptions.Item>
+            <Descriptions.Item label="Info">
+              <div style={{ maxHeight: "200px", overflow: "auto" }}>
+                <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word", margin: 0 }}>{data.info || "无"}</pre>
+              </div>
+            </Descriptions.Item>
+          </Descriptions>
+        </Card>
+      )}
+    </div>
+  );
+};
+
+export default AccountDetail;

+ 202 - 0
src/pages/AccountList.js

@@ -0,0 +1,202 @@
+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 { useNavigate } from "react-router-dom";
+import { accountsApi } from "../services/api";
+import moment from "moment";
+
+export const ACCOUNT_STATUS_MAP = {
+  1: "正常",
+  2: "封禁",
+  3: "授权信息不可用",
+};
+
+export const ACCOUNT_STATUS_TAG_COLOR = {
+  1: "success",
+  2: "error",
+  3: "warning",
+};
+
+const { Option } = Select;
+
+const AccountList = () => {
+  const [data, setData] = useState([]);
+  const [loading, setLoading] = useState(false);
+  const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 });
+  const [accountFilter, setAccountFilter] = useState("");
+  const [statusFilter, setStatusFilter] = useState(undefined);
+  const navigate = useNavigate();
+
+  const getStatusColor = (status) => ACCOUNT_STATUS_TAG_COLOR[status] || "default";
+  const getStatusText = (status) => ACCOUNT_STATUS_MAP[status] || "未知";
+
+  const columns = [
+    {
+      title: "ID",
+      dataIndex: "id",
+      key: "id",
+      width: 100,
+    },
+    {
+      title: "站点Host",
+      dataIndex: "hostname",
+      key: "hostname",
+      width: 180,
+      ellipsis: true,
+      render: (text) => (
+        <Tooltip title={text}>
+          <span className="cursor-pointer">{text}</span>
+        </Tooltip>
+      ),
+    },
+    {
+      title: "平台",
+      dataIndex: "account",
+      key: "account",
+      width: 180,
+    },
+    {
+      title: "状态",
+      dataIndex: "status",
+      key: "status",
+      width: 120,
+      render: (status) => <Tag color={getStatusColor(status)}>{getStatusText(status)}</Tag>,
+    },
+    {
+      title: "创建时间",
+      dataIndex: "create_time",
+      key: "create_time",
+      width: 180,
+      render: (time) => moment(time).format("YYYY-MM-DD HH:mm:ss"),
+    },
+    {
+      title: "更新时间",
+      dataIndex: "update_time",
+      key: "update_time",
+      width: 180,
+      render: (time) => moment(time).format("YYYY-MM-DD HH:mm:ss"),
+    },
+    {
+      title: "操作",
+      key: "action",
+      width: 120,
+      fixed: "right",
+      render: (_, record) => (
+        <Space size="small">
+          <Button
+            type="primary"
+            size="small"
+            icon={<EyeOutlined />}
+            onClick={() => navigate(`/accounts/${record.id}`)}
+          >
+            详情
+          </Button>
+          {/* <Button
+            size="small"
+            icon={<EditOutlined />}
+            onClick={() => navigate(`/accounts/${record.id}?mode=edit`)}
+          >
+            编辑
+          </Button> */}
+        </Space>
+      ),
+    },
+  ];
+
+  const fetchData = async (page = 1, pageSize = 10, account = "", status = undefined) => {
+    setLoading(true);
+    try {
+      const params = { page, pageSize };
+      if (account) params.account = account;
+      if (status !== undefined && status !== "") params.status = status;
+      const response = await accountsApi.getList(params);
+      setData(response.data.data);
+      setPagination({ current: response.data.page, pageSize: response.data.pageSize, total: response.data.total });
+    } catch (error) {
+      message.error("获取数据失败");
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  const handleTableChange = (paginationConfig) => {
+    fetchData(paginationConfig.current, paginationConfig.pageSize, accountFilter, statusFilter);
+  };
+
+  const handleSearch = () => {
+    fetchData(1, pagination.pageSize, accountFilter, statusFilter);
+  };
+
+  const handleReset = () => {
+    setAccountFilter("");
+    setStatusFilter(undefined);
+    fetchData(1, pagination.pageSize, "", undefined);
+  };
+
+  useEffect(() => {
+    fetchData();
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, []);
+
+  return (
+    <div>
+      <div className="mb-4">
+        <Row
+          gutter={16}
+          align="middle"
+        >
+          <Col span={8}>
+            <Input
+              placeholder="按账号名称查询"
+              value={accountFilter}
+              onChange={(e) => setAccountFilter(e.target.value)}
+              allowClear
+            />
+          </Col>
+          <Col span={6}>
+            <Select
+              placeholder="选择状态"
+              value={statusFilter}
+              onChange={setStatusFilter}
+              allowClear
+              style={{ width: "100%" }}
+            >
+              <Option value={1}>正常</Option>
+              <Option value={2}>封禁</Option>
+              <Option value={3}>授权信息不可用</Option>
+            </Select>
+          </Col>
+          <Col span={10}>
+            <Space>
+              <Button
+                type="primary"
+                icon={<SearchOutlined />}
+                onClick={handleSearch}
+              >
+                搜索
+              </Button>
+              <Button
+                icon={<ReloadOutlined />}
+                onClick={handleReset}
+              >
+                重置
+              </Button>
+            </Space>
+          </Col>
+        </Row>
+      </div>
+
+      <Table
+        rowKey="id"
+        columns={columns}
+        dataSource={data}
+        loading={loading}
+        pagination={{ current: pagination.current, pageSize: pagination.pageSize, total: pagination.total }}
+        onChange={handleTableChange}
+        scroll={{ x: 1000 }}
+      />
+    </div>
+  );
+};
+
+export default AccountList;

+ 18 - 0
src/pages/AutoAccessTaskAdd.js

@@ -39,6 +39,8 @@ const AutoAccessTaskAdd = () => {
         api_doc: values.apiDoc || null,
         operate_path_data: values.operatePathData || null,
         website_location: values.websiteLocation || null,
+        website_name: values.websiteName || null,
+        website_address: values.websiteAddress || null,
       };
 
       await autoAccessTasksApi.create(submitData);
@@ -194,6 +196,22 @@ const AutoAccessTaskAdd = () => {
             </Select>
           </Form.Item>
 
+          <Form.Item
+            label="网站名称"
+            name="websiteName"
+            rules={[{ max: 200, message: "网站名称不能超过200个字符" }]}
+          >
+            <Input placeholder="请输入网站名称" />
+          </Form.Item>
+
+          <Form.Item
+            label="网站地址"
+            name="websiteAddress"
+            rules={[{ type: "url", message: "请输入合法的网址(含协议)" }]}
+          >
+            <Input placeholder="例如:https://example.com" />
+          </Form.Item>
+
           <Form.Item
             label="工具功能名称"
             name="toolsFunctionName"

+ 20 - 0
src/pages/AutoAccessTaskDetail.js

@@ -244,6 +244,24 @@ const AutoAccessTaskDetail = () => {
                   </Select>
                 </Form.Item>
               </Col>
+              <Col span={12}>
+                <Form.Item
+                  label="网站名称"
+                  name="website_name"
+                >
+                  <Input />
+                </Form.Item>
+              </Col>
+            </Row>
+            <Row gutter={24}>
+              <Col span={24}>
+                <Form.Item
+                  label="网站地址"
+                  name="website_address"
+                >
+                  <Input />
+                </Form.Item>
+              </Col>
             </Row>
 
             <Row gutter={24}>
@@ -363,6 +381,8 @@ const AutoAccessTaskDetail = () => {
             <Descriptions.Item label="API类名">{data.api_class_name}</Descriptions.Item>
             <Descriptions.Item label="API提供方">{data.api_provider}</Descriptions.Item>
             <Descriptions.Item label="网站位置">{data.website_location || "无"}</Descriptions.Item>
+            <Descriptions.Item label="网站名称">{data.website_name || "无"}</Descriptions.Item>
+            <Descriptions.Item label="网站地址">{data.website_address || "无"}</Descriptions.Item>
             <Descriptions.Item
               label="补充ApiKey"
               span={2}

+ 24 - 0
src/pages/AutoAccessTaskList.js

@@ -120,6 +120,30 @@ const AutoAccessTaskList = () => {
         </Tooltip>
       ),
     },
+    {
+      title: "网站名称",
+      dataIndex: "website_name",
+      key: "website_name",
+      width: 200,
+      ellipsis: true,
+      render: (text) => (
+        <Tooltip title={text}>
+          <span className="cursor-pointer">{text || "无"}</span>
+        </Tooltip>
+      ),
+    },
+    {
+      title: "网站地址",
+      dataIndex: "website_address",
+      key: "website_address",
+      width: 240,
+      ellipsis: true,
+      render: (text) => (
+        <Tooltip title={text}>
+          <span className="cursor-pointer">{text || "无"}</span>
+        </Tooltip>
+      ),
+    },
     {
       title: "工具功能描述",
       dataIndex: "tools_function_desc",

+ 21 - 1
src/pages/ToolsLibraryDetail.js

@@ -148,7 +148,7 @@ const ToolsLibraryDetail = () => {
                 <Form.Item
                   label="工具功能名称"
                   name="tools_function_name"
-                  rules={[{ required: true, message: "请输入工具功能名称" }]}
+                  rules={[{ required: false, message: "请输入工具功能名称" }]}
                 >
                   <Input />
                 </Form.Item>
@@ -247,6 +247,24 @@ const ToolsLibraryDetail = () => {
                   </Select>
                 </Form.Item>
               </Col>
+              <Col span={12}>
+                <Form.Item
+                  label="网站名称"
+                  name="website_name"
+                >
+                  <Input />
+                </Form.Item>
+              </Col>
+            </Row>
+            <Row gutter={24}>
+              <Col span={24}>
+                <Form.Item
+                  label="网站地址"
+                  name="website_address"
+                >
+                  <Input />
+                </Form.Item>
+              </Col>
             </Row>
             <Form.Item
               label="MCP工具名称"
@@ -333,6 +351,8 @@ const ToolsLibraryDetail = () => {
             </Descriptions.Item>
             <Descriptions.Item label="API路径">{data.api_url_path}</Descriptions.Item>
             <Descriptions.Item label="网站位置">{data.website_location || "无"}</Descriptions.Item>
+            <Descriptions.Item label="网站名称">{data.website_name || "无"}</Descriptions.Item>
+            <Descriptions.Item label="网站地址">{data.website_address || "无"}</Descriptions.Item>
 
             <Descriptions.Item
               label="MCP工具名称"

+ 24 - 0
src/pages/ToolsLibraryList.js

@@ -181,6 +181,30 @@ const ToolsLibraryList = () => {
         </Tooltip>
       ),
     },
+    {
+      title: "网站名称",
+      dataIndex: "website_name",
+      key: "website_name",
+      width: 200,
+      ellipsis: true,
+      render: (text) => (
+        <Tooltip title={text}>
+          <span className="cursor-pointer">{text || "无"}</span>
+        </Tooltip>
+      ),
+    },
+    {
+      title: "网站地址",
+      dataIndex: "website_address",
+      key: "website_address",
+      width: 240,
+      ellipsis: true,
+      render: (text) => (
+        <Tooltip title={text}>
+          <span className="cursor-pointer">{text || "无"}</span>
+        </Tooltip>
+      ),
+    },
     {
       title: "创建时间",
       dataIndex: "create_time",

+ 7 - 1
src/services/api.js

@@ -4,7 +4,7 @@ import axios from "axios";
 // 非 localhost 环境下,优先使用同源地址,避免跨域
 const isLocalhost = typeof window !== "undefined" && /localhost/.test(window.location.host);
 const API_BASE_URL = isLocalhost
-  ? "http://localhost:3001/api"
+  ? (process.env.REACT_APP_API_BASE_URL || "http://localhost:3001/api")
   : (typeof window !== "undefined" ? `${window.location.origin}/api` : "http://tools.aiddit.com/api");
 
 const api = axios.create({
@@ -44,4 +44,10 @@ export const toolsLibraryApi = {
   publish: (id) => api.post(`/tools-library/${id}/publish`),
 };
 
+export const accountsApi = {
+  getList: (params) => api.get("/accounts", { params }),
+  getDetail: (id) => api.get(`/accounts/${id}`),
+  update: (id, data) => api.put(`/accounts/${id}`, data),
+};
+
 export default api;