8
n8n 中文网amn8n.com

专注时间

中级

这是一个Personal Productivity领域的自动化工作流,包含 11 个节点。主要使用 If, Code, ItemLists, GoogleCalendar, ScheduleTrigger 等节点。 在繁忙日程中自动在Google日历中屏蔽专注时间

前置要求
  • 无特殊前置要求,导入即可使用
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "H1q8R1MfhEk8F0ni",
  "meta": {
    "instanceId": "bdc54da2c96840612a04bf3fd3a4cd97a7a7bd7c1152bbe41d5615f09311c097"
  },
  "name": "专注时间",
  "tags": [
    {
      "id": "SauVYJKjA9yiw2uI",
      "name": "calendar-automation",
      "createdAt": "2025-07-20T22:06:21.623Z",
      "updatedAt": "2025-07-20T22:06:21.623Z"
    }
  ],
  "nodes": [
    {
      "id": "965166ee-bf7e-48e6-b559-624c4a5bb72f",
      "name": "计划触发器",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -880,
        96
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * *"
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "af8970e0-6d2c-48d4-907b-5e022ab8d5ac",
      "name": "获取整周事件",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        -656,
        96
      ],
      "parameters": {
        "options": {},
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "primary",
          "cachedResultName": "Primary"
        },
        "operation": "getAll"
      },
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "Isqbn5j7Czj35HLS",
          "name": "Google Calendar account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "76c503e8-926f-4254-be1b-f2c5b55938e8",
      "name": "计算整周专注时间段",
      "type": "n8n-nodes-base.code",
      "position": [
        -448,
        96
      ],
      "parameters": {
        "jsCode": "const events = $input.all();\nconst today = new Date();\n\n// Get Sunday of current week (start of week)\nconst sunday = new Date(today);\nsunday.setDate(today.getDate() - today.getDay());\nsunday.setHours(0, 0, 0, 0);\n\n// Create array of all 7 days (Sunday-Saturday)\nconst allDays = [];\nfor (let i = 0; i < 7; i++) {\n  const day = new Date(sunday);\n  day.setDate(sunday.getDate() + i);\n  allDays.push(day);\n}\n\nconst dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];\nconsole.log('Analyzing full week:', allDays.map((d, i) => `${dayNames[i]} ${d.toDateString()}`));\n\nconst allFocusSlots = [];\n\n// Process each day of the week\nfor (let dayIndex = 0; dayIndex < allDays.length; dayIndex++) {\n  const currentDay = allDays[dayIndex];\n  const dayName = dayNames[dayIndex];\n  \n  const workStart = new Date(currentDay);\n  workStart.setHours(9, 0, 0, 0);\n  const workEnd = new Date(currentDay);\n  workEnd.setHours(17, 0, 0, 0);\n  \n  // Filter events for this specific day\n  const dayEvents = events\n    .filter(event => {\n      const eventStart = new Date(event.json.start.dateTime || event.json.start.date);\n      return eventStart.toDateString() === currentDay.toDateString();\n    })\n    .sort((a, b) => {\n      const aStart = new Date(a.json.start.dateTime || a.json.start.date);\n      const bStart = new Date(b.json.start.dateTime || b.json.start.date);\n      return aStart - bStart;\n    });\n\n  // Calculate total booked time for this day\n  let totalBookedMinutes = 0;\n  for (const event of dayEvents) {\n    const startTime = new Date(event.json.start.dateTime || event.json.start.date);\n    const endTime = new Date(event.json.end.dateTime || event.json.end.date);\n    \n    const effectiveStart = new Date(Math.max(startTime.getTime(), workStart.getTime()));\n    const effectiveEnd = new Date(Math.min(endTime.getTime(), workEnd.getTime()));\n    \n    if (effectiveStart < effectiveEnd) {\n      totalBookedMinutes += (effectiveEnd - effectiveStart) / (1000 * 60);\n    }\n  }\n\n  const totalBookedHours = totalBookedMinutes / 60;\n  console.log(`${dayName} ${currentDay.toDateString()}: ${totalBookedHours.toFixed(1)} hours booked`);\n\n  // Only process days with 6+ hours booked\n  if (totalBookedHours >= 6) {\n    console.log(`Creating focus time for ${dayName} (${totalBookedHours.toFixed(1)} hours booked)`);\n    \n    const freeSlots = [];\n    let currentTime = new Date(workStart);\n\n    // Check time before first event\n    if (dayEvents.length > 0) {\n      const firstEventStart = new Date(dayEvents[0].json.start.dateTime || dayEvents[0].json.start.date);\n      if (currentTime < firstEventStart) {\n        const slotEnd = new Date(Math.min(firstEventStart.getTime(), workEnd.getTime()));\n        if (currentTime < slotEnd) {\n          freeSlots.push({\n            start: new Date(currentTime),\n            end: slotEnd\n          });\n        }\n      }\n    }\n\n    // Check time between events\n    for (let i = 0; i < dayEvents.length - 1; i++) {\n      const currentEventEnd = new Date(dayEvents[i].json.end.dateTime || dayEvents[i].json.end.date);\n      const nextEventStart = new Date(dayEvents[i + 1].json.start.dateTime || dayEvents[i + 1].json.start.date);\n      \n      const slotStart = new Date(Math.max(currentEventEnd.getTime(), workStart.getTime()));\n      const slotEnd = new Date(Math.min(nextEventStart.getTime(), workEnd.getTime()));\n      \n      if (slotStart < slotEnd) {\n        freeSlots.push({\n          start: slotStart,\n          end: slotEnd\n        });\n      }\n    }\n\n    // Check time after last event\n    if (dayEvents.length > 0) {\n      const lastEventEnd = new Date(dayEvents[dayEvents.length - 1].json.end.dateTime || dayEvents[dayEvents.length - 1].json.end.date);\n      const slotStart = new Date(Math.max(lastEventEnd.getTime(), workStart.getTime()));\n      \n      if (slotStart < workEnd) {\n        freeSlots.push({\n          start: slotStart,\n          end: new Date(workEnd)\n        });\n      }\n    } else {\n      // No events this day, but 6+ hours requirement met somehow (shouldn't happen, but handle it)\n      console.log(`${dayName}: No events found but met 6+ hour criteria`);\n    }\n\n    // Filter slots that are at least 15 minutes long and add to all slots\n    const validSlots = freeSlots.filter(slot => \n      (slot.end - slot.start) >= 15 * 60 * 1000\n    );\n\n    validSlots.forEach(slot => {\n      allFocusSlots.push({\n        start: slot.start.toISOString(),\n        end: slot.end.toISOString(),\n        duration: Math.round((slot.end - slot.start) / (1000 * 60)),\n        summary: \"Focus Time\",\n        day: currentDay.toDateString(),\n        dayName: dayName,\n        bookedHours: totalBookedHours.toFixed(1)\n      });\n    });\n  }\n}\n\nconsole.log(`Found ${allFocusSlots.length} focus time slots across the full week`);\n\nif (allFocusSlots.length === 0) {\n  return [{\n    json: {\n      shouldCreateFocusTime: false,\n      message: 'No days this week have 6+ hours booked. Focus time not needed.',\n      weekAnalyzed: allDays.map((d, i) => `${dayNames[i]} ${d.toDateString()}`)\n    }\n  }];\n}\n\nreturn [{\n  json: {\n    shouldCreateFocusTime: true,\n    totalSlotsFound: allFocusSlots.length,\n    freeSlots: allFocusSlots,\n    weekAnalyzed: allDays.map((d, i) => `${dayNames[i]} ${d.toDateString()}`)\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "a6d17dd6-3aea-4860-b3c1-cd95d8e6a78c",
      "name": "是否创建专注时间?",
      "type": "n8n-nodes-base.if",
      "position": [
        -224,
        96
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "condition-001",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.shouldCreateFocusTime }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "49a4675b-2a0f-4219-9500-aa1803fd794b",
      "name": "分割空闲时间段",
      "type": "n8n-nodes-base.itemLists",
      "position": [
        0,
        0
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "freeSlots"
      },
      "typeVersion": 3
    },
    {
      "id": "a7191014-8661-41c2-b121-7f7f924c895e",
      "name": "创建专注时间事件",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        224,
        0
      ],
      "parameters": {
        "end": "={{ $json.end }}",
        "start": "={{ $json.start }}",
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "primary",
          "cachedResultName": "Primary"
        },
        "additionalFields": {}
      },
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "Isqbn5j7Czj35HLS",
          "name": "Google Calendar account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "019babfa-6ac8-4028-951c-0cb776ef24cd",
      "name": "记录结果",
      "type": "n8n-nodes-base.code",
      "position": [
        448,
        0
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nconst summary = {\n  message: 'Focus time blocks created successfully for the week',\n  totalSlots: items.length,\n  slots: items.map(item => ({\n    start: item.json.start.dateTime,\n    end: item.json.end.dateTime,\n    summary: item.json.summary\n  }))\n};\n\nconsole.log('Weekly Focus Time Creation Summary:', JSON.stringify(summary, null, 2));\n\nreturn [{\n  json: summary\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "7ecbdb56-9195-4d13-891b-e307d10417f5",
      "name": "记录无需专注时间",
      "type": "n8n-nodes-base.code",
      "position": [
        0,
        208
      ],
      "parameters": {
        "jsCode": "const data = $input.first().json;\nconsole.log('Weekly Focus Time Analysis:', data.message);\n\nreturn [{\n  json: {\n    message: data.message,\n    weekAnalyzed: data.weekAnalyzed,\n    action: 'no_focus_time_needed'\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "5a76f16a-5931-45d8-8319-440b39491a65",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -944,
        -80
      ],
      "parameters": {
        "height": 400,
        "content": "设置工作流运行的计划时间(建议设置在用户工作日开始前)。"
      },
      "typeVersion": 1
    },
    {
      "id": "f9e74c3f-ab4e-4e70-bddc-08f4903dc6f5",
      "name": "便签1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -688,
        -80
      ],
      "parameters": {
        "width": 592,
        "height": 400,
        "content": "工作流检查用户从周日到周六的当前周日历。此部分的目标是确定某天是否已有6小时或更长时间被预订"
      },
      "typeVersion": 1
    },
    {
      "id": "8ac0657c-c682-414e-b56e-884f517bcda2",
      "name": "便签2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -80,
        -176
      ],
      "parameters": {
        "width": 704,
        "height": 544,
        "content": "对于已有6小时或更长时间被预订的天数,工作流会自动将剩余时间块设置为专用专注时间。工作流假设每周工作8小时。例如,如果周一已有6.5小时被预订(用于会议、任务等),工作流将阻塞剩余的1.5小时。记录结果以帮助进行故障排除"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "9c49a780-08ab-4198-9a16-8cdb241b5425",
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Get Full Week Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Free Slots": {
      "main": [
        [
          {
            "node": "Create Focus Time Event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Full Week Events": {
      "main": [
        [
          {
            "node": "Calculate Full Week Focus Time Slots",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Focus Time Event": {
      "main": [
        [
          {
            "node": "Log Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Should Create Focus Time?": {
      "main": [
        [
          {
            "node": "Split Free Slots",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log No Focus Time Needed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Full Week Focus Time Slots": {
      "main": [
        [
          {
            "node": "Should Create Focus Time?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

中级 - 个人效率

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
中级
节点数量11
分类1
节点类型6
难度说明

适合有一定经验的用户,包含 6-15 个节点的中等复杂度工作流

外部链接
在 n8n.io 查看

分享此工作流