Explorar o código

feat: 更新生产环境配置并添加待审核状态

- 修改生产环境API地址为直接访问后端服务
- 配置CORS允许生产环境前端域名访问
- 在工具库和待处理工具页面添加待审核/待发布状态
- 添加部署脚本用于自动化部署
- 调整操作列宽度并移除删除按钮
max_liu hai 2 semanas
pai
achega
72f5acb809

+ 47 - 0
deploy.sh

@@ -0,0 +1,47 @@
+#!/bin/bash
+
+# 服务器部署脚本
+SERVER_IP="47.93.61.163"
+PROJECT_PATH="/root/tools_auto_web"  # 服务器上的项目路径,请根据实际情况修改
+
+echo "🚀 开始部署到服务器..."
+
+# 1. 构建前端
+echo "📦 构建前端项目..."
+yarn build
+
+# 2. 上传文件到服务器
+echo "📤 上传文件到服务器..."
+scp -r build/ root@$SERVER_IP:$PROJECT_PATH/
+scp -r server/ root@$SERVER_IP:$PROJECT_PATH/
+scp package.json root@$SERVER_IP:$PROJECT_PATH/
+
+# 3. 在服务器上执行部署命令
+echo "🔧 在服务器上安装依赖并启动服务..."
+ssh root@$SERVER_IP << EOF
+cd $PROJECT_PATH
+
+# 安装依赖
+npm install
+
+# 启动后端服务
+pm2 delete api-server 2>/dev/null || true
+cd server
+pm2 start server.js --name "api-server"
+cd ..
+
+# 启动前端服务
+pm2 delete frontend-serve 2>/dev/null || true
+pm2 serve build 3030 --name "frontend-serve"
+
+# 保存PM2配置
+pm2 save
+
+# 显示服务状态
+pm2 list
+EOF
+
+echo "✅ 部署完成!"
+echo "🌐 前端地址: http://$SERVER_IP:3030"
+echo "🔗 后端地址: http://$SERVER_IP:3001"
+echo "📊 健康检查: http://$SERVER_IP:3001/api/health"

+ 11 - 1
server/server.js

@@ -10,7 +10,17 @@ const toolsLibraryRoutes = require('./routes/toolsLibrary');
 const app = express();
 const PORT = process.env.PORT || 3001;
 
-app.use(cors());
+// 配置CORS,允许前端域名访问
+app.use(cors({
+  origin: [
+    'http://47.93.61.163:3030',  // 生产环境前端地址
+    'http://localhost:3000',     // 本地开发环境
+    'http://localhost:3030'      // 本地测试环境
+  ],
+  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
+  allowedHeaders: ['Content-Type', 'Authorization'],
+  credentials: true
+}));
 app.use(bodyParser.json());
 app.use(bodyParser.urlencoded({ extended: true }));
 

+ 3 - 0
src/pages/PendingToolsDetail.js

@@ -24,6 +24,7 @@ const PendingToolsDetail = () => {
       1: "warning",
       2: "success",
       3: "error",
+      4: "pending",
     };
     return statusMap[status] || "default";
   };
@@ -34,6 +35,7 @@ const PendingToolsDetail = () => {
       1: "处理中",
       2: "已完成",
       3: "失败",
+      4: "待审核",
     };
     return statusMap[status] || "未知";
   };
@@ -142,6 +144,7 @@ const PendingToolsDetail = () => {
                 <Option value={1}>处理中</Option>
                 <Option value={2}>已完成</Option>
                 <Option value={3}>失败</Option>
+                <Option value={4}>待审核</Option>
               </Select>
             </Form.Item>
 

+ 5 - 3
src/pages/PendingToolsList.js

@@ -26,6 +26,7 @@ const PendingToolsList = () => {
       1: "warning",
       2: "success",
       3: "error",
+      4: "pending",
     };
     return statusMap[status] || "default";
   };
@@ -36,6 +37,7 @@ const PendingToolsList = () => {
       1: "处理中",
       2: "已完成",
       3: "失败",
+      4: "待审核",
     };
     return statusMap[status] || "未知";
   };
@@ -107,7 +109,7 @@ const PendingToolsList = () => {
     {
       title: "操作",
       key: "action",
-      width: 240,
+      width: 150,
       fixed: "right",
       render: (_, record) => (
         <Space size="small">
@@ -126,14 +128,14 @@ const PendingToolsList = () => {
           >
             编辑
           </Button>
-          <Button
+          {/* <Button
             danger
             size="small"
             icon={<DeleteOutlined />}
             onClick={() => handleDelete(record.search_task_id)}
           >
             删除
-          </Button>
+          </Button> */}
         </Space>
       ),
     },

+ 78 - 72
src/pages/ToolsLibraryDetail.js

@@ -1,9 +1,9 @@
-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 { toolsLibraryApi } from '../services/api';
-import moment from 'moment';
+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 { toolsLibraryApi } from "../services/api";
+import moment from "moment";
 
 const { TextArea } = Input;
 const { Option } = Select;
@@ -16,37 +16,41 @@ const ToolsLibraryDetail = () => {
   const { id } = useParams();
   const navigate = useNavigate();
   const location = useLocation();
-  const isEditMode = location.search.includes('mode=edit');
+  const isEditMode = location.search.includes("mode=edit");
 
   const getStatusColor = (status) => {
     const statusMap = {
-      'normal': 'success',
-      'offline': 'default',
+      normal: "success",
+      offline: "default",
+      unpublished: "default",
+      published: "success",
     };
-    return statusMap[status] || 'warning';
+    return statusMap[status] || "warning";
   };
 
   const getStatusText = (status) => {
     const statusMap = {
-      'normal': '正常',
-      'offline': '已下线',
+      normal: "正常",
+      offline: "已下线",
+      unpublished: "待发布",
+      published: "已发布",
     };
     return statusMap[status] || status;
   };
 
   const getCallTypeText = (type) => {
     const typeMap = {
-      'api': 'API调用',
-      'browser_auto_operate': '浏览器自动操作',
+      api: "API调用",
+      browser_auto_operate: "浏览器自动操作",
     };
     return typeMap[type] || type;
   };
 
   const getApiProviderText = (provider) => {
     const providerMap = {
-      'official': '官方',
-      '302ai': '302AI',
-      'official_api': '官方API',
+      official: "官方",
+      "302ai": "302AI",
+      official_api: "官方API",
     };
     return providerMap[provider] || provider;
   };
@@ -57,7 +61,7 @@ const ToolsLibraryDetail = () => {
       setData(response.data);
       form.setFieldsValue(response.data);
     } catch (error) {
-      message.error('获取详情失败');
+      message.error("获取详情失败");
     } finally {
       setLoading(false);
     }
@@ -67,10 +71,10 @@ const ToolsLibraryDetail = () => {
     setSaving(true);
     try {
       await toolsLibraryApi.update(id, values);
-      message.success('更新成功');
-      navigate('/tools-library');
+      message.success("更新成功");
+      navigate("/tools-library");
     } catch (error) {
-      message.error('更新失败');
+      message.error("更新失败");
     } finally {
       setSaving(false);
     }
@@ -82,7 +86,7 @@ const ToolsLibraryDetail = () => {
 
   if (loading) {
     return (
-      <div style={{ textAlign: 'center', padding: '50px' }}>
+      <div style={{ textAlign: "center", padding: "50px" }}>
         <Spin size="large" />
       </div>
     );
@@ -96,14 +100,17 @@ const ToolsLibraryDetail = () => {
     <div className="detail-container">
       <Button
         icon={<ArrowLeftOutlined />}
-        onClick={() => navigate('/tools-library')}
+        onClick={() => navigate("/tools-library")}
         style={{ marginBottom: 16 }}
       >
         返回列表
       </Button>
 
       {isEditMode ? (
-        <Card title="编辑工具" style={{ marginBottom: 24 }}>
+        <Card
+          title="编辑工具"
+          style={{ marginBottom: 24 }}
+        >
           <Form
             form={form}
             layout="vertical"
@@ -113,7 +120,7 @@ const ToolsLibraryDetail = () => {
             <Form.Item
               label="工具名称"
               name="tools_name"
-              rules={[{ required: true, message: '请输入工具名称' }]}
+              rules={[{ required: true, message: "请输入工具名称" }]}
             >
               <Input />
             </Form.Item>
@@ -121,7 +128,7 @@ const ToolsLibraryDetail = () => {
             <Form.Item
               label="工具功能名称"
               name="tools_function_name"
-              rules={[{ required: true, message: '请输入工具功能名称' }]}
+              rules={[{ required: true, message: "请输入工具功能名称" }]}
             >
               <Input />
             </Form.Item>
@@ -157,11 +164,13 @@ const ToolsLibraryDetail = () => {
             <Form.Item
               label="状态"
               name="status"
-              rules={[{ required: true, message: '请选择状态' }]}
+              rules={[{ required: true, message: "请选择状态" }]}
             >
               <Select>
                 <Option value="normal">正常</Option>
                 <Option value="offline">已下线</Option>
+                <Option value="unpublished">待发布</Option>
+                <Option value="published">已发布</Option>
               </Select>
             </Form.Item>
 
@@ -215,10 +224,12 @@ const ToolsLibraryDetail = () => {
             </Form.Item>
 
             <div className="button-group">
-              <Button onClick={() => navigate('/tools-library')}>
-                取消
-              </Button>
-              <Button type="primary" htmlType="submit" loading={saving}>
+              <Button onClick={() => navigate("/tools-library")}>取消</Button>
+              <Button
+                type="primary"
+                htmlType="submit"
+                loading={saving}
+              >
                 保存
               </Button>
             </div>
@@ -226,29 +237,20 @@ const ToolsLibraryDetail = () => {
         </Card>
       ) : (
         <Card title="工具详情">
-          <Descriptions column={2} bordered>
-            <Descriptions.Item label="工具ID">
-              {data.tools_id}
-            </Descriptions.Item>
-            <Descriptions.Item label="工具名称">
-              {data.tools_name}
-            </Descriptions.Item>
-            <Descriptions.Item label="工具功能名称">
-              {data.tools_function_name}
-            </Descriptions.Item>
-            <Descriptions.Item label="工具全称">
-              {data.tools_full_name}
-            </Descriptions.Item>
+          <Descriptions
+            column={2}
+            bordered
+          >
+            <Descriptions.Item label="工具ID">{data.tools_id}</Descriptions.Item>
+            <Descriptions.Item label="工具名称">{data.tools_name}</Descriptions.Item>
+            <Descriptions.Item label="工具功能名称">{data.tools_function_name}</Descriptions.Item>
+            <Descriptions.Item label="工具全称">{data.tools_full_name}</Descriptions.Item>
             <Descriptions.Item label="工具版本">
               <Tag color="geekblue">{data.tools_version}</Tag>
             </Descriptions.Item>
-            <Descriptions.Item label="自动接入任务ID">
-              {data.access_task_id}
-            </Descriptions.Item>
+            <Descriptions.Item label="自动接入任务ID">{data.access_task_id}</Descriptions.Item>
             <Descriptions.Item label="状态">
-              <Tag color={getStatusColor(data.status)}>
-                {getStatusText(data.status)}
-              </Tag>
+              <Tag color={getStatusColor(data.status)}>{getStatusText(data.status)}</Tag>
             </Descriptions.Item>
             <Descriptions.Item label="调用方式">
               <Tag color="blue">{getCallTypeText(data.call_type)}</Tag>
@@ -256,38 +258,42 @@ const ToolsLibraryDetail = () => {
             <Descriptions.Item label="API提供方">
               <Tag color="purple">{getApiProviderText(data.api_provider)}</Tag>
             </Descriptions.Item>
-            <Descriptions.Item label="API路径">
-              {data.api_url_path}
-            </Descriptions.Item>
-            <Descriptions.Item label="工具描述" span={2}>
+            <Descriptions.Item label="API路径">{data.api_url_path}</Descriptions.Item>
+            <Descriptions.Item
+              label="工具描述"
+              span={2}
+            >
               {data.tools_desc}
             </Descriptions.Item>
-            <Descriptions.Item label="操作路径数据" span={2}>
-              <div style={{ maxHeight: '200px', overflow: 'auto' }}>
-                <pre style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
-                  {data.operate_path_data || '无'}
-                </pre>
+            <Descriptions.Item
+              label="操作路径数据"
+              span={2}
+            >
+              <div style={{ maxHeight: "200px", overflow: "auto" }}>
+                <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>{data.operate_path_data || "无"}</pre>
               </div>
             </Descriptions.Item>
-            <Descriptions.Item label="参数定义" span={2}>
-              <div style={{ maxHeight: '200px', overflow: 'auto' }}>
-                <pre style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
-                  {data.params_definition || '无'}
-                </pre>
+            <Descriptions.Item
+              label="参数定义"
+              span={2}
+            >
+              <div style={{ maxHeight: "200px", overflow: "auto" }}>
+                <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>{data.params_definition || "无"}</pre>
               </div>
             </Descriptions.Item>
-            <Descriptions.Item label="响应数据说明" span={2}>
-              <div style={{ maxHeight: '200px', overflow: 'auto' }}>
-                <pre style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
-                  {data.response_desc || '无'}
-                </pre>
+            <Descriptions.Item
+              label="响应数据说明"
+              span={2}
+            >
+              <div style={{ maxHeight: "200px", overflow: "auto" }}>
+                <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>{data.response_desc || "无"}</pre>
               </div>
             </Descriptions.Item>
             <Descriptions.Item label="创建时间">
-              {moment(data.create_time).format('YYYY-MM-DD HH:mm:ss')}
+              {moment(data.create_time).format("YYYY-MM-DD HH:mm:ss")}
             </Descriptions.Item>
             <Descriptions.Item label="更新时间">
-              {moment(data.update_time).format('YYYY-MM-DD HH:mm:ss')}
+              {moment(data.update_time).format("YYYY-MM-DD HH:mm:ss")}
             </Descriptions.Item>
           </Descriptions>
         </Card>
@@ -296,4 +302,4 @@ const ToolsLibraryDetail = () => {
   );
 };
 
-export default ToolsLibraryDetail;
+export default ToolsLibraryDetail;

+ 5 - 1
src/pages/ToolsLibraryList.js

@@ -21,6 +21,8 @@ const ToolsLibraryList = () => {
     const statusMap = {
       normal: "success",
       offline: "default",
+      unpublished: "default",
+      published: "success",
     };
     return statusMap[status] || "warning";
   };
@@ -29,6 +31,8 @@ const ToolsLibraryList = () => {
     const statusMap = {
       normal: "正常",
       offline: "已下线",
+      unpublished: "待发布",
+      published: "已发布",
     };
     return statusMap[status] || status;
   };
@@ -156,7 +160,7 @@ const ToolsLibraryList = () => {
     {
       title: "操作",
       key: "action",
-      width: 200,
+      width: 240,
       fixed: "right",
       render: (_, record) => (
         <Space size="small">

+ 1 - 1
src/services/api.js

@@ -2,7 +2,7 @@ import axios from 'axios';
 
 // 根据环境自动切换API地址
 const API_BASE_URL = process.env.NODE_ENV === 'production' 
-  ? '/api'  // 生产环境使用相对路径,通过Nginx代理
+  ? 'http://47.93.61.163:3001/api'  // 生产环境直接访问后端服务
   : 'http://localhost:3001/api';  // 开发环境使用本地地址
 
 const api = axios.create({