소스 검색

智价云2.0页面修改

hechuanqi 2 달 전
부모
커밋
4f008d1c47

+ 1 - 1
.env.development

@@ -1 +1 @@
-API_HOST="http://schedule.dfwy.tech"
+API_HOST="http://localhost:8080"

+ 9 - 0
.idea/dotouch_equipment.iml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/dotouch_equipment.iml" filepath="$PROJECT_DIR$/.idea/dotouch_equipment.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>

+ 7 - 35
.umirc.ts

@@ -17,16 +17,15 @@ export default defineConfig({
   base: '/',
   publicPath: '/',
   outputPath: 'collect_schedule_web',
-  // 如需设置 favicon,建议将文件放在 `public/` 并用绝对路径,例如:favicons: ['/logo.ico']
   // 开发代理:将 /api 请求代理到后端服务
   proxy: {
     '/manager': {
-      target: 'http://schedule.dfwy.tech',
+      target: 'http://localhost:8080',
+      changeOrigin: true,
+    },
+    '/api': {
+      target: 'http://localhost:8080',
       changeOrigin: true,
-      // 如需代理 HTTPS 且后端证书非受信,可打开:
-      // secure: false,
-      // 如需代理 WebSocket,打开:
-      // ws: true,
     },
   },
   favicons: ['/assets/image/logo.ico'],
@@ -42,43 +41,20 @@ export default defineConfig({
       icon: 'home',
       component: './Home',
     },
-    //采集任务
+    // 采集任务(仅查看,任务由调度引擎自动生成)
     {
       name: '采集任务',
       path: '/collect_task',
       icon: 'BookOutlined',
       component: './CollectionTask',
     },
-    {
-      name: '设备管理',
-      path: '/equipment_manager',
-      icon: 'DatabaseOutlined',
-      component: './EquipmentManager',
-    },
-    {
-      name: '设备账号管理',
-      path: '/equipment_account_manager',
-      icon: 'ProfileOutlined',
-      component: './EquipmentAcountManager',
-    },
+    // 平台配置
     {
       name: '平台配置',
       path: '/platform_config',
       icon: 'FileTextOutlined',
       component: './PlatformConfig',
     },
-    {
-      name: '设备任务分配',
-      path: '/collect_task_allocate',
-      icon: 'ProjectOutlined',
-      component: './EquipmentTaskAllocate',
-    },
-    {
-      name: '设备任务记录',
-      path: '/collect_task_record',
-      icon: 'CalendarOutlined',
-      component: './EquipmentTaskRecord',
-    },
     // 全局未匹配路由兜底到 404
     {
       path: '*',
@@ -95,16 +71,12 @@ export default defineConfig({
   npmClient: 'yarn',
   // 网页更新提醒插件配置
   webUpdateNotification: {
-    // 版本来源:git 提交哈希
     versionType: 'build_timestamp',
-    // 控制台打印当前版本号,便于排查
     logVersion: true,
-    // 轮询检测间隔(毫秒)。生产可调大一些,例如 5-10 分钟
     checkInterval: 0.5 * 60 * 1000,
     notificationConfig: {
       placement: 'topRight',
     },
-    // 通知栏文案(中文)
     notificationProps: {
       title: '系统已更新',
       description: '检测到新版本,请刷新页面以获取最新功能',

+ 82 - 1
package-lock.json

@@ -1,5 +1,5 @@
 {
-  "name": "console",
+  "name": "dotouch_equipment",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
@@ -10,6 +10,8 @@
         "@ant-design/pro-components": "^2.4.4",
         "@umijs/max": "^4.6.2",
         "antd": "^5.4.0",
+        "echarts": "^6.0.0",
+        "echarts-for-react": "^3.0.5",
         "extend": "^3.0.2",
         "flux": "^4.0.4",
         "keymirror": "^0.1.1",
@@ -23,6 +25,7 @@
         "@types/qs": "^6.14.0",
         "@types/react": "^18.0.33",
         "@types/react-dom": "^18.0.11",
+        "env-cmd": "^10.1.0",
         "husky": "^9",
         "lint-staged": "^13.2.0",
         "prettier": "^2.8.7",
@@ -9329,6 +9332,36 @@
       "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
       "license": "MIT"
     },
+    "node_modules/echarts": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz",
+      "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "tslib": "2.3.0",
+        "zrender": "6.0.0"
+      }
+    },
+    "node_modules/echarts-for-react": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/echarts-for-react/-/echarts-for-react-3.0.6.tgz",
+      "integrity": "sha512-4zqLgTGWS3JvkQDXjzkR1k1CHRdpd6by0988TWMJgnvDytegWLbeP/VNZmMa+0VJx2eD7Y632bi2JquXDgiGJg==",
+      "license": "MIT",
+      "dependencies": {
+        "fast-deep-equal": "^3.1.3",
+        "size-sensor": "^1.0.1"
+      },
+      "peerDependencies": {
+        "echarts": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0",
+        "react": "^15.0.0 || >=16.0.0"
+      }
+    },
+    "node_modules/echarts/node_modules/tslib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
+      "license": "0BSD"
+    },
     "node_modules/ee-first": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -9411,6 +9444,33 @@
         "url": "https://github.com/fb55/entities?sponsor=1"
       }
     },
+    "node_modules/env-cmd": {
+      "version": "10.1.0",
+      "resolved": "https://registry.npmjs.org/env-cmd/-/env-cmd-10.1.0.tgz",
+      "integrity": "sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "commander": "^4.0.0",
+        "cross-spawn": "^7.0.0"
+      },
+      "bin": {
+        "env-cmd": "bin/env-cmd.js"
+      },
+      "engines": {
+        "node": ">=8.0.0"
+      }
+    },
+    "node_modules/env-cmd/node_modules/commander": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+      "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/env-paths": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
@@ -17935,6 +17995,12 @@
       "integrity": "sha512-9SQdmsyz4HSP+3gs6PJzhkaMEg+6zTlu9oxIghnwUX3eq+ajq4ft5egl0iyR55LAmO/UwvU8NgIWs/ZyQMa6dw==",
       "license": "MIT"
     },
+    "node_modules/size-sensor": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/size-sensor/-/size-sensor-1.0.3.tgz",
+      "integrity": "sha512-+k9mJ2/rQMiRmQUcjn+qznch260leIXY8r4FyYKKyRBO/s5UoeMAHGkCJyE1R/4wrIhTJONfyloY55SkE7ve3A==",
+      "license": "ISC"
+    },
     "node_modules/slash": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -20089,6 +20155,21 @@
       "peerDependencies": {
         "zod": "^3.18.0"
       }
+    },
+    "node_modules/zrender": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz",
+      "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "tslib": "2.3.0"
+      }
+    },
+    "node_modules/zrender/node_modules/tslib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
+      "license": "0BSD"
     }
   }
 }

+ 1 - 1
src/config/api.ts

@@ -1,6 +1,6 @@
 export const getApiBase = (): string => {
   const isDev = process.env.NODE_ENV === 'development';
-  const origin = isDev ? 'http://schedule.dfwy.tech' : process.env.API_HOST;
+  const origin = isDev ? '' : process.env.API_HOST;
   return String(origin).trim().replace(/\/+$/, '');
 };
 

+ 21 - 2
src/constants/StatusConstants.ts

@@ -1,5 +1,24 @@
-export const StatusConstants = {
+// 任务状态枚举(与后端 TaskStatusEnum 对齐)
+export const TaskStatusConstants = {
   0: '未开始',
   1: '进行中',
   2: '已完成',
-};
+};
+
+// 兼容历史引用
+export const StatusConstants = TaskStatusConstants;
+
+// 设备分配状态枚举(与后端 AllocateStatusEnum 对齐:仅 2 种状态)
+// 后端通过 equipment_number 与 max_equipment_number 计算:
+//   equipment_number = 0  -> 0 未分配
+//   equipment_number > 0  -> 1 已分配(含部分分配)
+export const AllocateStatusConstants = {
+  0: '未分配',
+  1: '已分配',
+};
+
+// 平台配置状态枚举(与后端 ConfigStatusEnum 对齐)
+export const ConfigStatusConstants = {
+  0: '启用',
+  1: '禁用',
+};

+ 45 - 18
src/pages/CollectionTask/index.tsx

@@ -1,11 +1,18 @@
 import ProTable from '@/components/ProTable';
 import { PlatformConstants } from '@/constants/PlatformConstants';
-import { StatusConstants } from '@/constants/StatusConstants';
+import {
+  AllocateStatusConstants,
+  TaskStatusConstants,
+} from '@/constants/StatusConstants';
 import API from '@/services';
 import type { ActionType, ProColumns } from '@ant-design/pro-table';
 import React, { useRef } from 'react';
 
-// 禁止挂网商品
+/**
+ * 采集任务列表
+ * 后端接口:GET /collect_task/index?name=&page=&limit=
+ * 任务由定时任务 CollectTaskGenerateTask 自动生成,前端仅展示。
+ */
 const CollectionTask: React.FC = () => {
   const actionRef = useRef<ActionType>();
   const proTableFormRef = useRef<any>();
@@ -19,7 +26,7 @@ const CollectionTask: React.FC = () => {
       hideInSearch: true,
     },
     {
-      title: '任务中心ID',
+      title: '商品配置ID',
       dataIndex: 'collect_product_id',
       key: 'collect_product_id',
       hideInSearch: true,
@@ -35,8 +42,7 @@ const CollectionTask: React.FC = () => {
     {
       title: '产品名称',
       dataIndex: 'product_name',
-      key: 'product_name',
-      hideInSearch: true,
+      key: 'name',
     },
     {
       title: '产品规格',
@@ -44,6 +50,12 @@ const CollectionTask: React.FC = () => {
       key: 'product_specs',
       hideInSearch: true,
     },
+    {
+      title: '产品品牌',
+      dataIndex: 'product_brand',
+      key: 'product_brand',
+      hideInSearch: true,
+    },
     {
       title: '最小起购量',
       dataIndex: 'minimum_order_quantity',
@@ -51,7 +63,7 @@ const CollectionTask: React.FC = () => {
       hideInSearch: true,
     },
     {
-      title: '开始时间',
+      title: '采集开始时间',
       dataIndex: 'sampling_start_time',
       key: 'sampling_start_time',
       valueType: 'dateTime',
@@ -60,7 +72,7 @@ const CollectionTask: React.FC = () => {
         typeof val === 'number' && val < 1e12 ? val * 1000 : val,
     },
     {
-      title: '结束时间',
+      title: '采集结束时间',
       dataIndex: 'sampling_end_time',
       key: 'sampling_end_time',
       hideInSearch: true,
@@ -69,42 +81,57 @@ const CollectionTask: React.FC = () => {
         typeof val === 'number' && val < 1e12 ? val * 1000 : val,
     },
     {
-      title: '最多分配设备数',
+      title: '最多分配设备数',
       dataIndex: 'max_equipment_number',
       hideInSearch: true,
       key: 'max_equipment_number',
     },
     {
-      title: '已分配设备数',
+      title: '已分配设备数',
       dataIndex: 'equipment_number',
       hideInSearch: true,
       key: 'equipment_number',
     },
+    {
+      title: '当前页/总页',
+      dataIndex: 'current_page',
+      hideInSearch: true,
+      key: 'current_page',
+      renderText: (val, record) =>
+        val == null ? '-' : `${val}/${record.total_pages ?? '-'}`,
+    },
+    {
+      title: '已爬取数量',
+      dataIndex: 'crawled_count',
+      hideInSearch: true,
+      key: 'crawled_count',
+    },
+    {
+      title: '执行账号',
+      dataIndex: 'account_username',
+      hideInSearch: true,
+      key: 'account_username',
+    },
     {
       title: '任务状态',
       dataIndex: 'status',
       key: 'status',
       valueType: 'select',
-      valueEnum: StatusConstants,
+      valueEnum: TaskStatusConstants,
       hideInSearch: true,
     },
     {
-      title: '设备分配状态',
+      title: '分配状态',
       dataIndex: 'allocate_status',
       key: 'allocate_status',
       valueType: 'select',
-      valueEnum: {
-        0: '未分配',
-        1: '未分配完成',
-        2: '已分配完成',
-      },
+      valueEnum: AllocateStatusConstants,
       hideInSearch: true,
     },
   ];
 
   return (
     <ProTable
-      noSearch
       columns={columns}
       actionRef={actionRef}
       formRef={proTableFormRef}
@@ -120,7 +147,7 @@ const CollectionTask: React.FC = () => {
       editable={{
         type: 'multiple',
       }}
-      scroll={{ x: '150%' }}
+      scroll={{ x: '200%' }}
     />
   );
 };

+ 0 - 211
src/pages/EquipmentAcountManager/index.tsx

@@ -1,211 +0,0 @@
-import Modal from '@/components/Modal';
-import ModiyFormModal from '@/components/ModiyFormModal';
-import ProTable from '@/components/ProTable';
-import { ModalTitleMap } from '@/constants/BasicConstants';
-import { PlatformConstants } from '@/constants/PlatformConstants';
-import API from '@/services';
-import { guid } from '@/utils/utils';
-import { PlusOutlined } from '@ant-design/icons';
-import type { ActionType, ProColumns } from '@ant-design/pro-table';
-import { Button, message, Space, Typography } from 'antd';
-import React, { useRef } from 'react';
-
-// 禁止挂网商品
-const EquipmentAcountManager: React.FC = () => {
-  const actionRef = useRef<ActionType>();
-  const proTableFormRef = useRef<any>();
-  const downloadRef = useRef<any>({});
-  const modiyFormModalRef = useRef<any>();
-
-  const modiyManage = (type: 'create' | 'update', data: any = {}) => {
-    const { hide } = Modal.show({
-      title: `${ModalTitleMap[type]}设备`,
-      content: (
-        <ModiyFormModal
-          type={type}
-          formOptions={{ labelCol: 5, labelAlign: 'right' }}
-          formList={[
-            {
-              label: '账号',
-              isRequired: true,
-              value: 'username',
-              type: 'input',
-            },
-            {
-              label: '密码',
-              isRequired: true,
-              value: 'password',
-              type: 'password',
-            },
-            {
-              label: '设备ID',
-              isRequired: true,
-              value: 'collect_equipment_id',
-              type: 'input',
-            },
-            {
-              label: '平台',
-              isRequired: true,
-              value: 'platform',
-              type: 'select',
-              options: Object?.entries(PlatformConstants)?.map(
-                ([key, value]) => ({
-                  label: value,
-                  value: Number(key),
-                }),
-              ),
-            },
-          ]}
-          ref={modiyFormModalRef}
-          getDetail={() => data}
-        />
-      ),
-      onOk: async () => {
-        const formInstance = modiyFormModalRef.current;
-        if (!formInstance) {
-          throw new Error('表单未初始化');
-        }
-        const isFormValid = await formInstance.validateForm();
-        if (!isFormValid) {
-          throw new Error('表单校验未通过');
-        }
-        const values = formInstance.getData();
-        const response = ['create'].includes(type)
-          ? await API.CollectTask.addCollectEquipmentAccount(values)
-          : await API.CollectTask.editCollectEquipmentAccount({
-              id: data.id,
-              ...values,
-            });
-        if (response.code === 'success') {
-          message.success('操作成功');
-          actionRef.current?.reload();
-          return;
-        }
-        throw new Error(response?.message || '操作失败');
-      },
-      onCancel: () => {
-        hide();
-      },
-    });
-  };
-
-  const columns: ProColumns[] = [
-    {
-      title: 'ID',
-      dataIndex: 'id',
-      key: 'id',
-      hideInSearch: true,
-    },
-    {
-      title: '设备ID',
-      dataIndex: 'collect_equipment_id',
-      key: 'collect_equipment_id',
-      hideInSearch: true,
-    },
-    {
-      title: '平台',
-      dataIndex: 'platform',
-      key: 'platform',
-      hideInSearch: true,
-      valueType: 'select',
-      valueEnum: PlatformConstants,
-    },
-    {
-      title: '账号',
-      dataIndex: 'username',
-      key: 'username',
-      hideInSearch: true,
-    },
-    {
-      title: '密码',
-      dataIndex: 'password',
-      key: 'password',
-      hideInSearch: true,
-    },
-    {
-      title: '状态',
-      dataIndex: 'status',
-      key: 'status',
-      hideInSearch: true,
-      valueType: 'select',
-      valueEnum: {
-        0: '启用',
-        1: '禁用',
-      },
-    },
-    {
-      title: '操作',
-      dataIndex: 'option',
-      key: 'option',
-      valueType: 'option',
-      fixed: 'right',
-      render: (text, record) => {
-        void text;
-        void record;
-        return (
-          <Space key={guid()}>
-            <Typography.Link
-              style={{ color: record.status == 0 ? 'red' : 'green' }}
-              onClick={async () => {
-                const response =
-                  await API.CollectTask.setCollectEquipmentAccountStatus({
-                    id: record.id,
-                    status: record.status == 0 ? 1 : 0,
-                  });
-                if (response.code === 'success') {
-                  message.success('操作成功');
-                  actionRef.current?.reload();
-                  return;
-                }
-                throw new Error(response?.message || '操作失败');
-              }}
-            >
-              {record.status == 0 ? '禁用' : '启用'}
-            </Typography.Link>
-            <Typography.Link onClick={() => modiyManage('update', record)}>
-              编辑
-            </Typography.Link>
-          </Space>
-        );
-      },
-    },
-  ];
-
-  return (
-    <ProTable
-      columns={columns}
-      actionRef={actionRef}
-      formRef={proTableFormRef}
-      request={async (params) => {
-        downloadRef.current = params;
-        const res = await API.CollectTask.collectEquipmentAccount(params);
-        return {
-          data: res.data?.data || [],
-          total: res.data?.total || 0,
-          success: true,
-        };
-      }}
-      editable={{
-        type: 'multiple',
-      }}
-      scroll={{ x: '150%' }}
-      noSearch
-      toolbar={{
-        actions: [
-          <Button
-            key="export_excel"
-            type="primary"
-            icon={<PlusOutlined />}
-            onClick={() => {
-              modiyManage('create');
-            }}
-          >
-            新增设备账号
-          </Button>,
-        ],
-      }}
-    />
-  );
-};
-
-export default EquipmentAcountManager;

+ 0 - 189
src/pages/EquipmentManager/index.tsx

@@ -1,189 +0,0 @@
-import Modal from '@/components/Modal';
-import ModiyFormModal from '@/components/ModiyFormModal';
-import ProTable from '@/components/ProTable';
-import { ModalTitleMap } from '@/constants/BasicConstants';
-import API from '@/services';
-import { guid } from '@/utils/utils';
-import { PlusOutlined } from '@ant-design/icons';
-import type { ActionType, ProColumns } from '@ant-design/pro-table';
-import { Button, message, Space, Typography } from 'antd';
-import React, { useRef } from 'react';
-
-// 禁止挂网商品
-const EquipmentManager: React.FC = () => {
-  const actionRef = useRef<ActionType>();
-  const proTableFormRef = useRef<any>();
-  const downloadRef = useRef<any>({});
-  const modiyFormModalRef = useRef<any>();
-
-  const modiyManage = (type: 'create' | 'update', data: any = {}) => {
-    const { hide } = Modal.show({
-      title: `${ModalTitleMap[type]}设备`,
-      content: (
-        <ModiyFormModal
-          type={type}
-          formOptions={{ labelCol: 5, labelAlign: 'right' }}
-          formList={[
-            {
-              label: '设备名称',
-              isRequired: true,
-              value: 'name',
-              type: 'input',
-            },
-            {
-              label: '设备终端ID',
-              isRequired: true,
-              value: 'device_id',
-              type: 'input',
-            },
-          ]}
-          ref={modiyFormModalRef}
-          getDetail={() => data}
-        />
-      ),
-      onOk: async () => {
-        const formInstance = modiyFormModalRef.current;
-        if (!formInstance) {
-          throw new Error('表单未初始化');
-        }
-        const isFormValid = await formInstance.validateForm();
-        if (!isFormValid) {
-          throw new Error('表单校验未通过');
-        }
-        const values = formInstance.getData();
-        const response = ['create'].includes(type)
-          ? await API.CollectTask.addCollectEquipment(values)
-          : await API.CollectTask.editCollectEquipment({
-              id: data.id,
-              ...values,
-            });
-        if (response.code === 'success') {
-          message.success('操作成功');
-          actionRef.current?.reload();
-          return;
-        }
-        throw new Error(response?.message || '操作失败');
-      },
-      onCancel: () => {
-        hide();
-      },
-    });
-  };
-
-  const columns: ProColumns[] = [
-    {
-      title: 'ID',
-      dataIndex: 'id',
-      key: 'id',
-      hideInSearch: true,
-    },
-    {
-      title: '设备名称',
-      dataIndex: 'name',
-      key: 'name',
-      hideInSearch: true,
-    },
-    {
-      title: '设备ID',
-      dataIndex: 'device_id',
-      key: 'device_id',
-      hideInSearch: true,
-    },
-    {
-      title: '设备运行状态',
-      dataIndex: 'task_status',
-      key: 'task_status',
-      hideInSearch: true,
-      valueType: 'select',
-      valueEnum: {
-        0: '未占用',
-        1: '已占用',
-      },
-    },
-    {
-      title: '设备状态',
-      dataIndex: 'status',
-      key: 'status',
-      hideInSearch: true,
-      valueType: 'select',
-      valueEnum: {
-        0: '启用',
-        1: '禁用',
-      },
-    },
-    {
-      title: '操作',
-      dataIndex: 'option',
-      key: 'option',
-      valueType: 'option',
-      fixed: 'right',
-      render: (text, record) => {
-        void text;
-        void record;
-        return (
-          <Space key={guid()}>
-            <Typography.Link
-              style={{ color: record.status == 0 ? 'red' : 'green' }}
-              onClick={async () => {
-                const response =
-                  await API.CollectTask.setCollectEquipmentStatus({
-                    id: record.id,
-                    status: record.status == 0 ? 1 : 0,
-                  });
-                if (response.code === 'success') {
-                  message.success('操作成功');
-                  actionRef.current?.reload();
-                  return;
-                }
-                throw new Error(response?.message || '操作失败');
-              }}
-            >
-              {record.status == 0 ? '禁用' : '启用'}
-            </Typography.Link>
-            <Typography.Link onClick={() => modiyManage('update', record)}>
-              编辑
-            </Typography.Link>
-          </Space>
-        );
-      },
-    },
-  ];
-
-  return (
-    <ProTable
-      columns={columns}
-      actionRef={actionRef}
-      formRef={proTableFormRef}
-      request={async (params) => {
-        downloadRef.current = params;
-        const res = await API.CollectTask.collectEquipment(params);
-        return {
-          data: res.data?.data || [],
-          total: res.data?.total || 0,
-          success: true,
-        };
-      }}
-      editable={{
-        type: 'multiple',
-      }}
-      scroll={{ x: '150%' }}
-      noSearch
-      toolbar={{
-        actions: [
-          <Button
-            key="export_excel"
-            type="primary"
-            icon={<PlusOutlined />}
-            onClick={() => {
-              modiyManage('create');
-            }}
-          >
-            新增设备
-          </Button>,
-        ],
-      }}
-    />
-  );
-};
-
-export default EquipmentManager;

+ 0 - 122
src/pages/EquipmentTaskAllocate/index.tsx

@@ -1,122 +0,0 @@
-import ProTable from '@/components/ProTable';
-import { PlatformConstants } from '@/constants/PlatformConstants';
-import API from '@/services';
-import type { ActionType, ProColumns } from '@ant-design/pro-table';
-import React, { useRef } from 'react';
-
-// 禁止挂网商品
-const EquipmentTaskAllocate: React.FC = () => {
-  const actionRef = useRef<ActionType>();
-  const proTableFormRef = useRef<any>();
-  const downloadRef = useRef<any>({});
-
-  const columns: ProColumns[] = [
-    {
-      title: 'ID',
-      dataIndex: 'id',
-      key: 'id',
-      hideInSearch: true,
-    },
-    {
-      title: '采集任务ID',
-      dataIndex: 'collect_task_id',
-      key: 'collect_task_id',
-      hideInSearch: true,
-    },
-    {
-      title: '设备ID',
-      dataIndex: 'collect_equipment_id',
-      key: 'collect_equipment_id',
-      hideInSearch: true,
-    },
-    {
-      title: '平台',
-      dataIndex: 'platform',
-      key: 'platform',
-      hideInSearch: true,
-      valueType: 'select',
-      valueEnum: PlatformConstants,
-    },
-    {
-      title: '产品名称',
-      dataIndex: 'product_name',
-      key: 'duration',
-      hideInSearch: true,
-    },
-    {
-      title: '产品规格',
-      dataIndex: 'product_specs',
-      key: 'product_specs',
-      hideInSearch: true,
-    },
-    {
-      title: '最小起购量',
-      dataIndex: 'minimum_order_quantity',
-      key: 'minimum_order_quantity',
-      hideInSearch: true,
-    },
-    {
-      title: '开始页',
-      dataIndex: 'start_page',
-      key: 'start_page',
-      hideInSearch: true,
-    },
-    {
-      title: '结束页',
-      dataIndex: 'end_page',
-      key: 'end_page',
-      hideInSearch: true,
-    },
-    {
-      title: '应采集数量',
-      dataIndex: 'count',
-      key: 'count',
-      hideInSearch: true,
-    },
-    {
-      title: '实际采集数量',
-      dataIndex: 'real_count',
-      key: 'real_count',
-      hideInSearch: true,
-    },
-    {
-      title: '状态',
-      dataIndex: 'status',
-      key: 'status',
-      hideInSearch: true,
-      valueType: 'select',
-      valueEnum: {
-        1: '未开始',
-        2: '进行中',
-        3: '已完成',
-        4: '失败',
-        5: '失败后已重新分配设备',
-        6: '弃用',
-      },
-    },
-  ];
-
-  return (
-    <ProTable
-      columns={columns}
-      actionRef={actionRef}
-      formRef={proTableFormRef}
-      request={async (params) => {
-        downloadRef.current = params;
-        const res = await API.CollectTask.collectTaskAllocate(params);
-        return {
-          data: res.data?.data || [],
-          total: res.data?.total || 0,
-          success: true,
-        };
-      }}
-      editable={{
-        type: 'multiple',
-      }}
-      scroll={{ x: '150%' }}
-      noSearch
-    />
-  );
-};
-
-export default EquipmentTaskAllocate;

+ 0 - 137
src/pages/EquipmentTaskRecord/index.tsx

@@ -1,137 +0,0 @@
-import ProTable from '@/components/ProTable';
-import { PlatformConstants } from '@/constants/PlatformConstants';
-import API from '@/services';
-import type { ActionType, ProColumns } from '@ant-design/pro-table';
-import React, { useRef } from 'react';
-
-// 禁止挂网商品
-const EquipmentTaskRecord: React.FC = () => {
-  const actionRef = useRef<ActionType>();
-  const proTableFormRef = useRef<any>();
-  const downloadRef = useRef<any>({});
-
-  const columns: ProColumns[] = [
-    {
-      title: 'ID',
-      dataIndex: 'id',
-      key: 'id',
-      hideInSearch: true,
-    },
-    {
-      title: '任务ID',
-      dataIndex: 'collect_task_id',
-      key: 'collect_task_id',
-      hideInSearch: true,
-    },
-    {
-      title: '设备ID',
-      dataIndex: 'collect_equipment_id',
-      key: 'collect_equipment_id',
-      hideInSearch: true,
-    },
-    {
-      title: '平台',
-      dataIndex: 'platform',
-      key: 'platform',
-      hideInSearch: true,
-      valueType: 'select',
-      valueEnum: PlatformConstants,
-    },
-    {
-      title: '产品名称',
-      dataIndex: 'product_name',
-      key: 'duration',
-      hideInSearch: true,
-    },
-    {
-      title: '产品规格',
-      dataIndex: 'product_specs',
-      key: 'product_specs',
-      hideInSearch: true,
-    },
-    {
-      title: '最小起购量',
-      dataIndex: 'minimum_order_quantity',
-      key: 'minimum_order_quantity',
-      hideInSearch: true,
-    },
-    {
-      title: '执行开始时间',
-      dataIndex: 'start_time',
-      key: 'start_time',
-      valueType: 'dateTime',
-      hideInSearch: true,
-      renderText: (val) =>
-        typeof val === 'number' && val < 1e12 ? val * 1000 : val,
-    },
-    {
-      title: '执行结束时间',
-      dataIndex: 'end_time',
-      key: 'end_time',
-      valueType: 'dateTime',
-      hideInSearch: true,
-      renderText: (val) =>
-        typeof val === 'number' && val < 1e12 ? val * 1000 : val,
-    },
-    {
-      title: '开始页',
-      dataIndex: 'start_page',
-      key: 'start_page',
-      hideInSearch: true,
-    },
-    {
-      title: '结束页',
-      dataIndex: 'end_page',
-      key: 'end_page',
-      hideInSearch: true,
-    },
-    {
-      title: '应采集数量',
-      dataIndex: 'count',
-      key: 'count',
-      hideInSearch: true,
-    },
-    {
-      title: '实际采集数量',
-      dataIndex: 'real_count',
-      key: 'real_count',
-      hideInSearch: true,
-    },
-    {
-      title: '状态',
-      dataIndex: 'status',
-      key: 'status',
-      hideInSearch: true,
-      valueType: 'select',
-      valueEnum: {
-        0: '执行中',
-        1: '失败',
-        2: '已完成',
-      },
-    },
-  ];
-
-  return (
-    <ProTable
-      columns={columns}
-      actionRef={actionRef}
-      formRef={proTableFormRef}
-      request={async (params) => {
-        downloadRef.current = params;
-        const res = await API.CollectTask.collectTaskRecord(params);
-        return {
-          data: res.data?.data || [],
-          total: res.data?.total || 0,
-          success: true,
-        };
-      }}
-      editable={{
-        type: 'multiple',
-      }}
-      scroll={{ x: '150%' }}
-      noSearch
-    />
-  );
-};
-
-export default EquipmentTaskRecord;

+ 93 - 4
src/pages/Home/index.less

@@ -1,7 +1,96 @@
 .container {
-  padding-top: 80px;
-  margin-top: 64px;
-  h1 {
-    text-align: center;
+  padding: 16px;
+}
+
+.filterBar {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 16px;
+  flex-wrap: wrap;
+}
+
+.summaryRow {
+  margin-bottom: 16px;
+}
+
+.summaryCard {
+  background: #fff;
+  border-radius: 8px;
+  padding: 20px 24px;
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
+  height: 100%;
+
+  .summaryTitle {
+    color: #888;
+    font-size: 14px;
+  }
+
+  .summaryValue {
+    color: #333;
+    font-size: 28px;
+    font-weight: 600;
+    line-height: 1.2;
+  }
+
+  .summaryHint {
+    color: #aaa;
+    font-size: 12px;
+  }
+}
+
+.platformPanel {
+  background: #fff;
+  border-radius: 8px;
+  padding: 16px 20px;
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
+  margin-bottom: 16px;
+
+  .panelTitle {
+    font-size: 16px;
+    font-weight: 600;
+    color: #333;
+    margin-bottom: 12px;
   }
 }
+
+.metricCell {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 8px 0;
+
+  .metricLabel {
+    color: #888;
+    font-size: 12px;
+    margin-bottom: 4px;
+  }
+
+  .metricValue {
+    font-size: 18px;
+    font-weight: 600;
+    color: #333;
+  }
+
+  .metricSub {
+    color: #aaa;
+    font-size: 12px;
+  }
+}
+
+.rateUp {
+  color: #52c41a;
+}
+
+.rateDown {
+  color: #ff4d4f;
+}
+
+.platformName {
+  font-weight: 600;
+  color: #333;
+}

+ 359 - 1
src/pages/Home/index.tsx

@@ -1,9 +1,367 @@
+import { PlatformConstants } from '@/constants/PlatformConstants';
+import API from '@/services';
+import { Card, Col, DatePicker, Empty, Row, Space, Spin, Table, Tag } from 'antd';
+import type { ColumnsType } from 'antd/es/table';
+import dayjs, { Dayjs } from 'dayjs';
+import React, { useEffect, useMemo, useState } from 'react';
 import styles from './index.less';
 
+const { RangePicker } = DatePicker;
+
+interface DashboardRow {
+  id?: number;
+  statDate?: string;
+  platform: string;
+  totalPages?: number;
+  totalCount?: number;
+  totalTasks?: number;
+  completedTasks?: number;
+  completionRate?: number;
+  activeAccounts?: number;
+  reportCount?: number;
+  exceptionTotal?: number;
+  exceptionRate?: number;
+  insertTime?: number;
+  updateTime?: number;
+}
+
 const HomePage: React.FC = () => {
+  const [date, setDate] = useState<Dayjs>(dayjs());
+  const [range, setRange] = useState<[Dayjs, Dayjs]>([
+    dayjs().subtract(6, 'day'),
+    dayjs(),
+  ]);
+  const [loading, setLoading] = useState(false);
+  const [list, setList] = useState<DashboardRow[]>([]);
+
+  const fetchDashboard = async (queryDate: Dayjs) => {
+    setLoading(true);
+    try {
+      const res: any = await API.Dashboard.getByDate({
+        date: queryDate.format('YYYY-MM-DD'),
+      });
+      if (res?.code === 'success') {
+        setList((res.data || []) as DashboardRow[]);
+      }
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  const fetchTrend = async (startDate: Dayjs, endDate: Dayjs) => {
+    const res: any = await API.Dashboard.getTrend({
+      start_date: startDate.format('YYYY-MM-DD'),
+      end_date: endDate.format('YYYY-MM-DD'),
+    });
+    if (res?.code === 'success') {
+      return (res.data || []) as DashboardRow[];
+    }
+    return [];
+  };
+
+  useEffect(() => {
+    fetchDashboard(date);
+  }, [date]);
+
+  const summary = useMemo(() => {
+    return list.reduce(
+      (acc, cur) => {
+        acc.totalTasks += cur.totalTasks || 0;
+        acc.completedTasks += cur.completedTasks || 0;
+        acc.totalPages += cur.totalPages || 0;
+        acc.totalCount += cur.totalCount || 0;
+        acc.activeAccounts += cur.activeAccounts || 0;
+        acc.exceptionTotal += cur.exceptionTotal || 0;
+        acc.reportCount += cur.reportCount || 0;
+        return acc;
+      },
+      {
+        totalTasks: 0,
+        completedTasks: 0,
+        totalPages: 0,
+        totalCount: 0,
+        activeAccounts: 0,
+        exceptionTotal: 0,
+        reportCount: 0,
+      },
+    );
+  }, [list]);
+
+  const summaryCompletionRate = summary.totalTasks
+    ? ((summary.completedTasks / summary.totalTasks) * 100).toFixed(2)
+    : '0.00';
+  const summaryExceptionRate = summary.reportCount
+    ? ((summary.exceptionTotal / summary.reportCount) * 100).toFixed(2)
+    : '0.00';
+
+  const columns: ColumnsType<DashboardRow> = [
+    {
+      title: '平台',
+      dataIndex: 'platform',
+      key: 'platform',
+      width: 140,
+      render: (val) => (
+        <span className={styles.platformName}>
+          {(PlatformConstants as any)[val] ?? `平台${val}`}
+        </span>
+      ),
+    },
+    {
+      title: '总任务数',
+      dataIndex: 'totalTasks',
+      key: 'totalTasks',
+      align: 'right',
+    },
+    {
+      title: '已完成',
+      dataIndex: 'completedTasks',
+      key: 'completedTasks',
+      align: 'right',
+    },
+    {
+      title: '完成率',
+      dataIndex: 'completionRate',
+      key: 'completionRate',
+      align: 'right',
+      render: (val) =>
+        val == null ? '-' : (
+          <span
+            className={
+              Number(val) >= 80
+                ? styles.rateUp
+                : Number(val) >= 50
+                  ? undefined
+                  : styles.rateDown
+            }
+          >
+            {`${Number(val).toFixed(2)}%`}
+          </span>
+        ),
+    },
+    {
+      title: '今日爬取页数',
+      dataIndex: 'totalPages',
+      key: 'totalPages',
+      align: 'right',
+    },
+    {
+      title: '今日爬取条数',
+      dataIndex: 'totalCount',
+      key: 'totalCount',
+      align: 'right',
+    },
+    {
+      title: '活跃账号',
+      dataIndex: 'activeAccounts',
+      key: 'activeAccounts',
+      align: 'right',
+    },
+    {
+      title: '回告次数',
+      dataIndex: 'reportCount',
+      key: 'reportCount',
+      align: 'right',
+    },
+    {
+      title: '异常次数',
+      dataIndex: 'exceptionTotal',
+      key: 'exceptionTotal',
+      align: 'right',
+      render: (val) =>
+        val > 0 ? (
+          <Tag color="red">{val}</Tag>
+        ) : (
+          <Tag color="green">0</Tag>
+        ),
+    },
+    {
+      title: '异常率',
+      dataIndex: 'exceptionRate',
+      key: 'exceptionRate',
+      align: 'right',
+      render: (val) =>
+        val == null ? '-' : (
+          <span
+            className={
+              Number(val) <= 5 ? styles.rateUp : styles.rateDown
+            }
+          >
+            {`${Number(val).toFixed(2)}%`}
+          </span>
+        ),
+    },
+  ];
+
+  // 简易趋势展示:每日总任务数 vs 已完成任务数
+  const [trendData, setTrendData] = useState<DashboardRow[]>([]);
+  const [trendLoading, setTrendLoading] = useState(false);
+
+  const handleRangeChange = async (val: any) => {
+    if (!val || val.length !== 2) return;
+    const [start, end] = val as [Dayjs, Dayjs];
+    setRange([start, end]);
+    setTrendLoading(true);
+    try {
+      const rows = await fetchTrend(start, end);
+      setTrendData(rows);
+    } finally {
+      setTrendLoading(false);
+    }
+  };
+
+  useEffect(() => {
+    handleRangeChange(range);
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, []);
+
+  const trendColumns: ColumnsType<any> = [
+    { title: '统计日期', dataIndex: 'statDate', key: 'statDate', width: 140 },
+    {
+      title: '平台',
+      dataIndex: 'platform',
+      key: 'platform',
+      width: 140,
+      render: (val) => (PlatformConstants as any)[val] ?? `平台${val}`,
+    },
+    {
+      title: '总任务数',
+      dataIndex: 'totalTasks',
+      key: 'totalTasks',
+      align: 'right',
+    },
+    {
+      title: '已完成',
+      dataIndex: 'completedTasks',
+      key: 'completedTasks',
+      align: 'right',
+    },
+    {
+      title: '爬取页数',
+      dataIndex: 'totalPages',
+      key: 'totalPages',
+      align: 'right',
+    },
+    {
+      title: '爬取条数',
+      dataIndex: 'totalCount',
+      key: 'totalCount',
+      align: 'right',
+    },
+    {
+      title: '异常',
+      dataIndex: 'exceptionTotal',
+      key: 'exceptionTotal',
+      align: 'right',
+    },
+  ];
+
   return (
     <div className={styles.container}>
-      <h1></h1>
+      <div className={styles.filterBar}>
+        <Space>
+          <span>统计日期:</span>
+          <DatePicker
+            value={date}
+            allowClear={false}
+            onChange={(d) => d && setDate(d)}
+          />
+        </Space>
+      </div>
+
+      <Row gutter={[16, 16]} className={styles.summaryRow}>
+        <Col xs={24} sm={12} md={6}>
+          <div className={styles.summaryCard}>
+            <span className={styles.summaryTitle}>今日总任务数</span>
+            <span className={styles.summaryValue}>{summary.totalTasks}</span>
+            <span className={styles.summaryHint}>
+              已完成 {summary.completedTasks}({summaryCompletionRate}%)
+            </span>
+          </div>
+        </Col>
+        <Col xs={24} sm={12} md={6}>
+          <div className={styles.summaryCard}>
+            <span className={styles.summaryTitle}>今日爬取条数</span>
+            <span className={styles.summaryValue}>{summary.totalCount}</span>
+            <span className={styles.summaryHint}>
+              爬取页数 {summary.totalPages}
+            </span>
+          </div>
+        </Col>
+        <Col xs={24} sm={12} md={6}>
+          <div className={styles.summaryCard}>
+            <span className={styles.summaryTitle}>今日活跃账号</span>
+            <span className={styles.summaryValue}>{summary.activeAccounts}</span>
+            <span className={styles.summaryHint}>
+              回告次数 {summary.reportCount}
+            </span>
+          </div>
+        </Col>
+        <Col xs={24} sm={12} md={6}>
+          <div className={styles.summaryCard}>
+            <span className={styles.summaryTitle}>今日异常率</span>
+            <span
+              className={styles.summaryValue}
+              style={{
+                color:
+                  Number(summaryExceptionRate) <= 5 ? '#52c41a' : '#ff4d4f',
+              }}
+            >
+              {`${summaryExceptionRate}%`}
+            </span>
+            <span className={styles.summaryHint}>
+              异常 {summary.exceptionTotal} 次
+            </span>
+          </div>
+        </Col>
+      </Row>
+
+      <div className={styles.platformPanel}>
+        <div className={styles.panelTitle}>平台数据明细({date.format('YYYY-MM-DD')})</div>
+        <Spin spinning={loading}>
+          {list.length ? (
+            <Table<DashboardRow>
+              rowKey={(r) => `${r.platform}-${r.id ?? ''}`}
+              dataSource={list}
+              columns={columns}
+              size="middle"
+              pagination={false}
+            />
+          ) : (
+            <Empty description="暂无数据" />
+          )}
+        </Spin>
+      </div>
+
+      <Card
+        size="small"
+        title={
+          <Space>
+            <span>趋势查询</span>
+            <RangePicker
+              value={range}
+              onChange={handleRangeChange}
+              allowClear={false}
+            />
+          </Space>
+        }
+        style={{ marginTop: 16 }}
+      >
+        <Spin spinning={trendLoading}>
+          {trendData.length ? (
+            <Table
+              rowKey={(r: any) =>
+                `${r.statDate || ''}-${r.platform || ''}`
+              }
+              dataSource={trendData}
+              columns={trendColumns}
+              size="small"
+              pagination={{ pageSize: 20 }}
+            />
+          ) : (
+            <Empty description="暂无趋势数据" />
+          )}
+        </Spin>
+      </Card>
     </div>
   );
 };

+ 87 - 72
src/pages/PlatformConfig/index.tsx

@@ -3,6 +3,7 @@ import ModiyFormModal from '@/components/ModiyFormModal';
 import ProTable from '@/components/ProTable';
 import { ModalTitleMap } from '@/constants/BasicConstants';
 import { PlatformConstants } from '@/constants/PlatformConstants';
+import { ConfigStatusConstants } from '@/constants/StatusConstants';
 import API from '@/services';
 import { guid } from '@/utils/utils';
 import { PlusOutlined } from '@ant-design/icons';
@@ -10,16 +11,46 @@ import type { ActionType, ProColumns } from '@ant-design/pro-table';
 import { Button, message, Space, Typography } from 'antd';
 import React, { useRef } from 'react';
 
-// 禁止挂网商品
+/**
+ * 平台配置
+ * 后端接口:
+ * - GET  /collect_platform_config/index?page=&limit=
+ * - POST /collect_platform_config/add
+ * - POST /collect_platform_config/edit
+ * - POST /collect_platform_config/set_status
+ *
+ * 备注:后端 config 为统一 JSON 配置,约定 key:
+ *       rest_duration(休息时长,分钟)、daily_max_pages(每日最大页数/条数,0=不限)
+ */
 const PlatformConfig: React.FC = () => {
   const actionRef = useRef<ActionType>();
   const proTableFormRef = useRef<any>();
   const downloadRef = useRef<any>({});
   const modiyFormModalRef = useRef<any>();
 
+  const flattenConfig = (record: any) => {
+    // 将后端返回的 config Map 拍平到 rowData,方便表单回显
+    const cfg = record?.config || {};
+    return {
+      platform: record?.platform,
+      rest_duration: cfg.rest_duration ?? 0,
+      daily_max_pages: cfg.daily_max_pages ?? 0,
+      status: record?.status,
+    };
+  };
+
+  const buildConfigBody = (values: any) => {
+    // 将表单字段组装为后端要求的 config Map
+    return {
+      rest_duration: Number(values.rest_duration ?? 0),
+      daily_max_pages: Number(values.daily_max_pages ?? 0),
+    };
+  };
+
   const modiyManage = (type: 'create' | 'update', data: any = {}) => {
     const { hide } = Modal.show({
-      title: `${ModalTitleMap[type]}设备`,
+      title: `${ModalTitleMap[type]}平台配置`,
+      width: 560,
       content: (
         <ModiyFormModal
           type={type}
@@ -30,46 +61,29 @@ const PlatformConfig: React.FC = () => {
               isRequired: true,
               value: 'platform',
               type: 'select',
-              options: Object?.entries(PlatformConstants)?.map(
-                ([key, value]) => ({
-                  label: value,
+              options: Object?.entries(PlatformConstants)
+                ?.filter(([key]) => Number(key) !== 0)
+                ?.map(([key, value]) => ({
+                  label: value as string,
                   value: Number(key),
-                }),
-              ),
-            },
-            {
-              label: '设备运行时长(分钟)',
-              isRequired: true,
-              value: 'duration',
-              type: 'number',
+                })),
             },
             {
-              label: '运行后休息时长',
+              label: '休息时长(分钟)',
               isRequired: true,
               value: 'rest_duration',
               type: 'number',
             },
             {
-              label: '每天运行次数',
-              isRequired: true,
-              value: 'day_number',
-              type: 'number',
-            },
-            {
-              label: '每分钟采集数量',
-              isRequired: true,
-              value: 'minute_size',
-              type: 'number',
-            },
-            {
-              label: '每页数量',
+              label: '每日最大页数/条数',
               isRequired: true,
-              value: 'page_size',
+              value: 'daily_max_pages',
               type: 'number',
+              help: '0 表示不限制',
             },
           ]}
           ref={modiyFormModalRef}
-          getDetail={() => data}
+          getDetail={() => flattenConfig(data)}
         />
       ),
       onOk: async () => {
@@ -82,18 +96,19 @@ const PlatformConfig: React.FC = () => {
           throw new Error('表单校验未通过');
         }
         const values = formInstance.getData();
-        const response = ['create'].includes(type)
-          ? await API.CollectTask.addCollectPlatformConfig(values)
-          : await API.CollectTask.editCollectPlatformConfig({
-              id: data.id,
-              ...values,
-            });
+        const config = buildConfigBody(values);
+        const platform = Number(values.platform);
+        const payload = { platform, config };
+        const response =
+          type === 'create'
+            ? await API.PlatformConfig.add(payload)
+            : await API.PlatformConfig.edit({ id: data.id, ...payload });
         if (response.code === 'success') {
-          message.success('操作成功');
+          message.success(type === 'create' ? '创建成功' : '更新成功');
           actionRef.current?.reload();
           return;
         }
-        throw new Error(response?.message || '操作失败');
+        throw new Error(response?.msg || '操作失败');
       },
       onCancel: () => {
         hide();
@@ -117,45 +132,47 @@ const PlatformConfig: React.FC = () => {
       valueEnum: PlatformConstants,
     },
     {
-      title: '每次任务时长(分钟)',
-      dataIndex: 'duration',
-      key: 'duration',
-      hideInSearch: true,
-    },
-    {
-      title: '每次任务休息时长(分钟)',
-      dataIndex: 'rest_duration',
+      title: '休息时长(分钟)',
+      dataIndex: ['config', 'rest_duration'],
       key: 'rest_duration',
       hideInSearch: true,
+      renderText: (_, record: any) => record?.config?.rest_duration ?? '-',
     },
     {
-      title: '每天任务次数',
-      dataIndex: 'day_number',
-      key: 'day_number',
+      title: '每日最大页数/条数',
+      dataIndex: ['config', 'daily_max_pages'],
+      key: 'daily_max_pages',
       hideInSearch: true,
+      renderText: (_, record: any) =>
+        record?.config?.daily_max_pages === 0
+          ? '不限'
+          : record?.config?.daily_max_pages ?? '-',
     },
     {
-      title: '每分钟采集数量/条',
-      dataIndex: 'minute_size',
-      key: 'minute_size',
+      title: '状态',
+      dataIndex: 'status',
+      key: 'status',
       hideInSearch: true,
+      valueType: 'select',
+      valueEnum: ConfigStatusConstants,
     },
     {
-      title: '每页数量',
-      dataIndex: 'page_size',
-      key: 'page_size',
+      title: '创建时间',
+      dataIndex: 'insert_time',
+      key: 'insert_time',
       hideInSearch: true,
+      valueType: 'dateTime',
+      renderText: (val) =>
+        typeof val === 'number' && val < 1e12 ? val * 1000 : val,
     },
     {
-      title: '状态',
-      dataIndex: 'status',
-      key: 'status',
+      title: '更新时间',
+      dataIndex: 'update_time',
+      key: 'update_time',
       hideInSearch: true,
-      valueType: 'select',
-      valueEnum: {
-        0: '启用',
-        1: '禁用',
-      },
+      valueType: 'dateTime',
+      renderText: (val) =>
+        typeof val === 'number' && val < 1e12 ? val * 1000 : val,
     },
     {
       title: '操作',
@@ -165,23 +182,21 @@ const PlatformConfig: React.FC = () => {
       fixed: 'right',
       render: (text, record) => {
         void text;
-        void record;
         return (
           <Space key={guid()}>
             <Typography.Link
               style={{ color: record.status == 0 ? 'red' : 'green' }}
               onClick={async () => {
-                const response =
-                  await API.CollectTask.setCollectPlatformConfigStatus({
-                    id: record.id,
-                    status: record.status == 0 ? 1 : 0,
-                  });
+                const response = await API.PlatformConfig.setStatus({
+                  id: record.id,
+                  status: record.status == 0 ? 1 : 0,
+                });
                 if (response.code === 'success') {
                   message.success('操作成功');
                   actionRef.current?.reload();
                   return;
                 }
-                throw new Error(response?.message || '操作失败');
+                throw new Error(response?.msg || '操作失败');
               }}
             >
               {record.status == 0 ? '禁用' : '启用'}
@@ -202,7 +217,7 @@ const PlatformConfig: React.FC = () => {
       formRef={proTableFormRef}
       request={async (params) => {
         downloadRef.current = params;
-        const res = await API.CollectTask.collectPlatformConfig(params);
+        const res = await API.PlatformConfig.list(params);
         return {
           data: res.data?.data || [],
           total: res.data?.total || 0,
@@ -217,14 +232,14 @@ const PlatformConfig: React.FC = () => {
       toolbar={{
         actions: [
           <Button
-            key="export_excel"
+            key="add"
             type="primary"
             icon={<PlusOutlined />}
             onClick={() => {
               modiyManage('create');
             }}
           >
-            新增设备
+            新增平台配置
           </Button>,
         ],
       }}

+ 25 - 92
src/services/collect_task.ts

@@ -1,113 +1,46 @@
 import BaseAPI from './BaseAPI';
 
+/**
+ * 采集任务 Service
+ * 后端接口:
+ * - GET  /collect_task/index?name=&page=&limit=    任务分页列表
+ * - GET  /collect_task/pull?platform=&username=     拉取任务(爬虫端)
+ * - POST /collect_task/heartbeat                    心跳上报(爬虫端)
+ * - POST /collect_task/report                       任务回告(爬虫端)
+ */
 class CollectTask extends BaseAPI {
   constructor() {
     super(``);
   }
 
-  //采集任务列表
+  // 采集任务分页列表(仅查询,管理由调度引擎自动完成)
   collectTask(data: any) {
     return this._get({ path: `/collect_task/index`, data }).then((data) => {
       return data;
-    }); 
+    });
   }
 
-  //设备列表
-  collectEquipment(data: any) {
-    return this._get({ path: `/collect_equipment/index`, data }).then((data) => {
+  // 拉取任务(爬虫端)
+  pullTask(data: { platform: string; username: string }) {
+    return this._get({ path: `/collect_task/pull`, data }).then((data) => {
       return data;
-    }); 
+    });
   }
 
-  //新增设备
-  addCollectEquipment(data: any) {
-    return this._post({ path: `/collect_equipment/add`, data }).then((data) => {
-      return data;
-    }); 
-  }
-
-  //编辑设备
-  editCollectEquipment(data: any) {
-    return this._post({ path: `/collect_equipment/edit`, data }).then((data) => {
-      return data;
-    }); 
-  }
-
-  //设置设备状态
-  setCollectEquipmentStatus(data: any) {
-    return this._post({ path: `/collect_equipment/set_status`, data }).then((data) => {
-      return data;
-    }); 
-  }
-
-  //采集设备账号列表
-  collectEquipmentAccount(data: any) {
-    return this._get({ path: `/collect_equipment_account/index`, data }).then((data) => {
-      return data;
-    }); 
-  }
-
-  //新增采集设备账号
-  addCollectEquipmentAccount(data: any) {
-    return this._post({ path: `/collect_equipment_account/add`, data }).then((data) => {
-      return data;
-    }); 
-  }
-
-  //编辑采集设备账号
-  editCollectEquipmentAccount(data: any) {
-    return this._post({ path: `/collect_equipment_account/edit`, data }).then((data) => {
-      return data;
-    }); 
-  }
-
-  //设置采集设备账号状态
-  setCollectEquipmentAccountStatus(data: any) {
-    return this._post({ path: `/collect_equipment_account/set_status`, data }).then((data) => {
-      return data;
-    }); 
-  }
-
-  //采集平台配置列表
-  collectPlatformConfig(data: any) {
-    return this._get({ path: `/collect_platform_config/index`, data }).then((data) => {
-      return data;
-    }); 
-  }
-
-  //新增采集平台配置
-  addCollectPlatformConfig(data: any) {
-    return this._post({ path: `/collect_platform_config/add`, data }).then((data) => {
-      return data;
-    }); 
-  }
-
-  //编辑采集平台配置
-  editCollectPlatformConfig(data: any) {
-    return this._post({ path: `/collect_platform_config/edit`, data }).then((data) => {
-      return data;
-    }); 
-  }
-
-  //设置采集平台配置状态
-  setCollectPlatformConfigStatus(data: any) {
-    return this._post({ path: `/collect_platform_config/set_status`, data }).then((data) => {
-      return data;
-    }); 
-  }
-
-  //采集任务分配列表
-  collectTaskAllocate(data: any) {
-    return this._get({ path: `/collect_task_allocate/index`, data }).then((data) => {
-      return data;
-    }); 
+  // 账号心跳上报(爬虫端)
+  heartbeat(data: { platform: string; username: string }) {
+    return this._post({ path: `/collect_task/heartbeat`, data }).then(
+      (data) => {
+        return data;
+      },
+    );
   }
 
-  //采集任务记录列表
-  collectTaskRecord(data: any) {
-    return this._get({ path: `/collect_task_record/index`, data }).then((data) => {
+  // 任务回告(爬虫端)
+  report(data: any) {
+    return this._post({ path: `/collect_task/report`, data }).then((data) => {
       return data;
-    }); 
+    });
   }
 }
 export default new CollectTask();

+ 32 - 0
src/services/dashboard.ts

@@ -0,0 +1,32 @@
+import BaseAPI from './BaseAPI';
+
+/**
+ * 数据看板 Service
+ * 后端接口:
+ * - GET /collect_dashboard/index?date=yyyy-MM-dd       按日看板数据
+ * - GET /collect_dashboard/trend?start_date=&end_date= 日期范围趋势数据
+ */
+class Dashboard extends BaseAPI {
+  constructor() {
+    super(``);
+  }
+
+  // 查询指定日期的看板数据(不传则默认今天)
+  getByDate(data: { date?: string } = {}) {
+    return this._get({ path: `/collect_dashboard/index`, data }).then(
+      (data) => {
+        return data;
+      },
+    );
+  }
+
+  // 查询日期范围内的看板数据(用于趋势图)
+  getTrend(data: { start_date?: string; end_date?: string }) {
+    return this._get({ path: `/collect_dashboard/trend`, data }).then(
+      (data) => {
+        return data;
+      },
+    );
+  }
+}
+export default new Dashboard();

+ 4 - 0
src/services/index.ts

@@ -1,7 +1,11 @@
 import Login from './login';
 import CollectTask from './collect_task';
+import Dashboard from './dashboard';
+import PlatformConfig from './platform_config';
 
 export default {
   Login,
   CollectTask,
+  Dashboard,
+  PlatformConfig,
 };

+ 1 - 0
src/services/login.ts

@@ -5,6 +5,7 @@ class Login extends BaseAPI {
     super(`/login`);
   }
 
+  // 后端支持 GET/POST,与 PHP Route::any('login/index') 一致
   login(data: { username: string; password: string }) {
     return this._get({ path: `/index`, data }).then((data) => {
       return data;

+ 56 - 0
src/services/platform_config.ts

@@ -0,0 +1,56 @@
+import BaseAPI from './BaseAPI';
+
+/**
+ * 平台配置 Service
+ * 后端接口:
+ * - GET  /collect_platform_config/index?page=&limit=  平台配置列表
+ * - POST /collect_platform_config/add                 新增平台配置
+ * - POST /collect_platform_config/edit                编辑平台配置
+ * - POST /collect_platform_config/set_status          设置平台配置状态
+ *
+ * 备注:config 字段为统一 JSON 配置
+ *       常用 key:rest_duration(休息时长)、daily_max_pages(每日限额)
+ */
+class PlatformConfigAPI extends BaseAPI {
+  constructor() {
+    super(``);
+  }
+
+  // 平台配置分页列表(仅分页,无筛选)
+  list(data: any) {
+    return this._get({ path: `/collect_platform_config/index`, data }).then(
+      (data) => {
+        return data;
+      },
+    );
+  }
+
+  // 新增平台配置
+  add(data: any) {
+    return this._post({ path: `/collect_platform_config/add`, data }).then(
+      (data) => {
+        return data;
+      },
+    );
+  }
+
+  // 编辑平台配置
+  edit(data: any) {
+    return this._post({ path: `/collect_platform_config/edit`, data }).then(
+      (data) => {
+        return data;
+      },
+    );
+  }
+
+  // 设置平台配置状态(0启用,1禁用)
+  setStatus(data: { id: number; status: number }) {
+    return this._post({
+      path: `/collect_platform_config/set_status`,
+      data,
+    }).then((data) => {
+      return data;
+    });
+  }
+}
+export default new PlatformConfigAPI();

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 162 - 526
yarn.lock


이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.