通过Gmail将设备请求路由至采购部门并需经理批准
高级
这是一个Content Creation, Multimodal AI领域的自动化工作流,包含 55 个节点。主要使用 If, Set, Form, Gmail, Merge 等节点。 通过Gmail将设备请求路由至采购部门,需经理批准
前置要求
- •Google 账号和 Gmail API 凭证
- •MySQL 数据库连接信息
- •OpenAI API Key
使用的节点 (55)
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"id": "rtK6UJNlclMQboZu",
"meta": {
"instanceId": "46f10f8f90bd424d08d7ec9e92bdd735f336873f2ca9f21d76cc7ac132eeaabd",
"templateCredsSetupCompleted": true
},
"name": "通过 Gmail 将设备请求路由至采购部门并需经理批准",
"tags": [],
"nodes": [
{
"id": "37da6182-7899-43ea-8266-f63209c78a45",
"name": "问题表单",
"type": "n8n-nodes-base.formTrigger",
"position": [
-416,
-144
],
"webhookId": "65a85fa9-64a9-4bfb-b2b0-ee43675117b4",
"parameters": {
"options": {
"customCss": "/* ===== Base layout ===== */\nhtml, body {\n height: 100%;\n font-family: var(--font-family);\n font-weight: var(--font-weight-normal);\n font-size: var(--font-size-body);\n background:\n radial-gradient(1200px 800px at 10% -10%, #f0f2ff 0%, transparent 60%),\n radial-gradient(1000px 700px at 110% 0%, #fef3f0 0%, transparent 55%),\n var(--color-background);\n color: var(--color-html-text);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n.container, .form-wrapper, .n8n-form {\n max-width: var(--container-width);\n margin: 32px auto;\n padding: 0 12px;\n}\n\n/* ===== Test banner (top) ===== */\n.test-notice {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0 auto 12px;\n max-width: var(--container-width);\n background: var(--color-test-notice-bg);\n border: 1px solid var(--color-test-notice-border);\n color: var(--color-test-notice-text);\n padding: var(--padding-test-notice-vertical) var(--padding-test-notice-horizontal);\n border-radius: var(--border-radius-input);\n font-size: var(--font-size-test-notice);\n}\n\n/* ===== Card ===== */\n.form-card {\n background: var(--color-card-bg);\n border: 1px solid var(--color-card-border);\n border-radius: var(--border-radius-card);\n box-shadow: var(--box-shadow-card);\n padding: var(--padding-card);\n margin-bottom: var(--margin-bottom-card);\n}\n\n/* ===== Headings & subtitle ===== */\n.form-card h1,\nh1.form-title {\n font-size: var(--font-size-header);\n color: var(--color-header);\n margin: 4px 0 6px;\n font-weight: var(--font-weight-bold);\n letter-spacing: -0.2px;\n}\n\n.form-subtitle {\n font-size: var(--font-size-paragraph);\n color: var(--color-header-subtext);\n margin: 0 0 16px;\n line-height: 1.5;\n}\n\n/* ===== Form fields ===== */\n.form-field {\n display: grid;\n gap: 6px;\n margin-bottom: 14px;\n}\n\n.form-field label {\n font-size: var(--font-size-label);\n color: var(--color-label);\n font-weight: 600;\n}\n\n.form-field label .required {\n color: var(--color-required);\n margin-left: 2px;\n}\n\n/* Inputs / selects / textareas */\ninput[type=\"text\"],\ninput[type=\"email\"],\ninput[type=\"number\"],\ninput[type=\"search\"],\ntextarea,\nselect {\n width: 100%;\n appearance: none;\n border: 1px solid var(--color-input-border);\n border-radius: var(--border-radius-input);\n padding: 12px 14px;\n font-size: var(--font-size-input);\n color: var(--color-input-text);\n background: #fff;\n transition: border-color .15s ease, box-shadow .15s ease, transform .05s ease;\n}\n\ninput::placeholder,\ntextarea::placeholder {\n opacity: var(--opacity-placeholder);\n}\n\n/* Hover / focus */\ninput:hover,\ntextarea:hover,\nselect:hover {\n border-color: #cfd5e2;\n}\n\ninput:focus,\ntextarea:focus,\nselect:focus {\n outline: none;\n border-color: var(--color-focus-border);\n box-shadow: 0 0 0 4px rgba(90, 76, 194, .12);\n}\n\n/* Invalid */\n.is-invalid,\ninput[aria-invalid=\"true\"],\ntextarea[aria-invalid=\"true\"],\nselect[aria-invalid=\"true\"] {\n border-color: var(--color-error);\n box-shadow: 0 0 0 4px rgba(234, 31, 48, .12);\n}\n\n/* Help / error text */\n.help-text {\n font-size: 12px;\n color: var(--color-link);\n}\n\n.error-text {\n font-size: var(--font-size-error);\n color: var(--color-error);\n margin-top: 4px;\n}\n\n/* ===== File input (custom) ===== */\n.input-file {\n position: relative;\n}\n\n.input-file input[type=\"file\"] {\n position: absolute;\n inset: 0;\n opacity: 0;\n cursor: pointer;\n}\n\n.input-file .file-ui {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n border: 1px dashed var(--color-input-border);\n border-radius: var(--border-radius-input);\n padding: 12px 14px;\n color: var(--color-input-text);\n background: #fff;\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n\n.input-file .file-ui:hover {\n border-color: #cfd5e2;\n}\n\n.input-file .file-ui:has(+ input[type=\"file\"]:focus) {\n border-color: var(--color-focus-border);\n box-shadow: 0 0 0 4px rgba(90, 76, 194, .12);\n}\n\n/* ===== Checkboxes / radios (accessible but nicer) ===== */\ninput[type=\"checkbox\"], input[type=\"radio\"] {\n accent-color: var(--color-focus-border);\n width: var(--checkbox-size);\n height: var(--checkbox-size);\n}\n\n/* ===== Buttons ===== */\n.actions,\n.form-actions {\n margin-top: 8px;\n}\n\nbutton[type=\"submit\"],\n.button-submit {\n width: 100%;\n height: var(--submit-btn-height);\n background: var(--color-submit-btn-bg);\n color: var(--color-submit-btn-text);\n border: 1px solid transparent;\n border-radius: var(--border-radius-input);\n font-size: 15px;\n font-weight: 700;\n letter-spacing: .2px;\n cursor: pointer;\n transition: transform .05s ease, filter .15s ease, box-shadow .15s ease;\n box-shadow: 0 6px 18px rgba(255, 109, 90, .25);\n}\n\nbutton[type=\"submit\"]:hover {\n filter: brightness(0.98);\n transform: translateY(-1px);\n box-shadow: 0 8px 24px rgba(255, 109, 90, .28);\n}\n\nbutton[type=\"submit\"]:active {\n transform: translateY(0);\n box-shadow: 0 6px 18px rgba(255, 109, 90, .22);\n}\n\nbutton[disabled] {\n opacity: .6;\n cursor: not-allowed;\n}\n\n/* Secondary link under button (e.g., “Clear form”) */\n.clear-link {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n margin-top: 10px;\n font-size: var(--font-size-link);\n color: var(--color-link);\n text-decoration: none;\n}\n.clear-link:hover { text-decoration: underline; }\n\n/* ===== Footer / “Automated with n8n” ===== */\n.form-footer {\n margin: 10px auto 24px;\n max-width: var(--container-width);\n text-align: center;\n color: var(--color-link);\n font-size: var(--font-size-link);\n}\n\n/* ===== Responsive ===== */\n@media (max-width: 520px) {\n .container, .form-wrapper, .n8n-form {\n margin: 20px auto;\n padding: 0 10px;\n }\n .form-card { padding: 20px; }\n .form-card h1, h1.form-title { font-size: 18px; }\n .form-subtitle { font-size: 13px; }\n}\n\n/* ===== Optional: Dark mode (auto) ===== */\n@media (prefers-color-scheme: dark) {\n body {\n background:\n radial-gradient(1200px 800px at 10% -10%, #272a33 0%, transparent 60%),\n radial-gradient(1000px 700px at 110% 0%, #2c2433 0%, transparent 55%),\n #16181d;\n color: #d8dbe3;\n }\n\n .test-notice {\n background: #2a2115;\n border-color: #3a2b18;\n color: #ffce74;\n }\n\n .form-card {\n background: #1c1f27;\n border-color: #2a2f3a;\n box-shadow: 0 8px 24px rgba(0,0,0,.35);\n }\n\n .form-card h1, h1.form-title { color: #e9ebf3; }\n .form-subtitle { color: #a6adbb; }\n\n input, textarea, select {\n background: #11151c;\n border-color: #2a2f3a;\n color: #d8dbe3;\n }\n input:hover, textarea:hover, select:hover { border-color: #3a4250; }\n input:focus, textarea:focus, select:focus {\n border-color: #b7a8ff;\n box-shadow: 0 0 0 4px rgba(183,168,255,.18);\n }\n\n .help-text { color: #9aa3b2; }\n\n button[type=\"submit\"],\n .button-submit {\n box-shadow: 0 10px 26px rgba(255,109,90,.34);\n }\n\n .form-footer { color: #9aa3b2; }\n}\n",
"buttonLabel": "Send question"
},
"formTitle": "Equipment Request Form",
"formFields": {
"values": [
{
"fieldLabel": "Your employee enrollment",
"requiredField": true
}
]
},
"responseMode": "lastNode",
"formDescription": "Please fill in your employee ID so we can validate your information and identify your manager"
},
"typeVersion": 2.3
},
{
"id": "4e3062f4-9644-4830-9410-1bdf4802a49d",
"name": "如果",
"type": "n8n-nodes-base.if",
"position": [
400,
-144
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d0fdfd93-0d28-43cf-8db7-1a4b22d57a15",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.isEmpty() }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "b6487e94-9186-4ec6-b0a6-cbe4fb04401c",
"name": "返回未找到员工",
"type": "n8n-nodes-base.form",
"position": [
720,
-352
],
"webhookId": "df14c48b-886d-4b8b-9c25-c0476114b939",
"parameters": {
"options": {},
"operation": "completion",
"completionTitle": "Employee not found",
"completionMessage": "It was not possible to find your registration based on your enrollment number."
},
"typeVersion": 2.3
},
{
"id": "1f8c6236-784a-49e9-9da6-4ad8b53d7de5",
"name": "获取请求信息",
"type": "n8n-nodes-base.form",
"position": [
-416,
464
],
"webhookId": "6abb8175-8e68-480c-a179-7798db9c266d",
"parameters": {
"options": {
"customCss": "/* ===== Base layout ===== */\nhtml, body {\n height: 100%;\n font-family: var(--font-family);\n font-weight: var(--font-weight-normal);\n font-size: var(--font-size-body);\n background:\n radial-gradient(1200px 800px at 10% -10%, #f0f2ff 0%, transparent 60%),\n radial-gradient(1000px 700px at 110% 0%, #fef3f0 0%, transparent 55%),\n var(--color-background);\n color: var(--color-html-text);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n.container, .form-wrapper, .n8n-form {\n max-width: var(--container-width);\n margin: 32px auto;\n padding: 0 12px;\n}\n\n/* ===== Test banner (top) ===== */\n.test-notice {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0 auto 12px;\n max-width: var(--container-width);\n background: var(--color-test-notice-bg);\n border: 1px solid var(--color-test-notice-border);\n color: var(--color-test-notice-text);\n padding: var(--padding-test-notice-vertical) var(--padding-test-notice-horizontal);\n border-radius: var(--border-radius-input);\n font-size: var(--font-size-test-notice);\n}\n\n/* ===== Card ===== */\n.form-card {\n background: var(--color-card-bg);\n border: 1px solid var(--color-card-border);\n border-radius: var(--border-radius-card);\n box-shadow: var(--box-shadow-card);\n padding: var(--padding-card);\n margin-bottom: var(--margin-bottom-card);\n}\n\n/* ===== Headings & subtitle ===== */\n.form-card h1,\nh1.form-title {\n font-size: var(--font-size-header);\n color: var(--color-header);\n margin: 4px 0 6px;\n font-weight: var(--font-weight-bold);\n letter-spacing: -0.2px;\n}\n\n.form-subtitle {\n font-size: var(--font-size-paragraph);\n color: var(--color-header-subtext);\n margin: 0 0 16px;\n line-height: 1.5;\n}\n\n/* ===== Form fields ===== */\n.form-field {\n display: grid;\n gap: 6px;\n margin-bottom: 14px;\n}\n\n.form-field label {\n font-size: var(--font-size-label);\n color: var(--color-label);\n font-weight: 600;\n}\n\n.form-field label .required {\n color: var(--color-required);\n margin-left: 2px;\n}\n\n/* Inputs / selects / textareas */\ninput[type=\"text\"],\ninput[type=\"email\"],\ninput[type=\"number\"],\ninput[type=\"search\"],\ntextarea,\nselect {\n width: 100%;\n appearance: none;\n border: 1px solid var(--color-input-border);\n border-radius: var(--border-radius-input);\n padding: 12px 14px;\n font-size: var(--font-size-input);\n color: var(--color-input-text);\n background: #fff;\n transition: border-color .15s ease, box-shadow .15s ease, transform .05s ease;\n}\n\ninput::placeholder,\ntextarea::placeholder {\n opacity: var(--opacity-placeholder);\n}\n\n/* Hover / focus */\ninput:hover,\ntextarea:hover,\nselect:hover {\n border-color: #cfd5e2;\n}\n\ninput:focus,\ntextarea:focus,\nselect:focus {\n outline: none;\n border-color: var(--color-focus-border);\n box-shadow: 0 0 0 4px rgba(90, 76, 194, .12);\n}\n\n/* Invalid */\n.is-invalid,\ninput[aria-invalid=\"true\"],\ntextarea[aria-invalid=\"true\"],\nselect[aria-invalid=\"true\"] {\n border-color: var(--color-error);\n box-shadow: 0 0 0 4px rgba(234, 31, 48, .12);\n}\n\n/* Help / error text */\n.help-text {\n font-size: 12px;\n color: var(--color-link);\n}\n\n.error-text {\n font-size: var(--font-size-error);\n color: var(--color-error);\n margin-top: 4px;\n}\n\n/* ===== File input (custom) ===== */\n.input-file {\n position: relative;\n}\n\n.input-file input[type=\"file\"] {\n position: absolute;\n inset: 0;\n opacity: 0;\n cursor: pointer;\n}\n\n.input-file .file-ui {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n border: 1px dashed var(--color-input-border);\n border-radius: var(--border-radius-input);\n padding: 12px 14px;\n color: var(--color-input-text);\n background: #fff;\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n\n.input-file .file-ui:hover {\n border-color: #cfd5e2;\n}\n\n.input-file .file-ui:has(+ input[type=\"file\"]:focus) {\n border-color: var(--color-focus-border);\n box-shadow: 0 0 0 4px rgba(90, 76, 194, .12);\n}\n\n/* ===== Checkboxes / radios (accessible but nicer) ===== */\ninput[type=\"checkbox\"], input[type=\"radio\"] {\n accent-color: var(--color-focus-border);\n width: var(--checkbox-size);\n height: var(--checkbox-size);\n}\n\n/* ===== Buttons ===== */\n.actions,\n.form-actions {\n margin-top: 8px;\n}\n\nbutton[type=\"submit\"],\n.button-submit {\n width: 100%;\n height: var(--submit-btn-height);\n background: var(--color-submit-btn-bg);\n color: var(--color-submit-btn-text);\n border: 1px solid transparent;\n border-radius: var(--border-radius-input);\n font-size: 15px;\n font-weight: 700;\n letter-spacing: .2px;\n cursor: pointer;\n transition: transform .05s ease, filter .15s ease, box-shadow .15s ease;\n box-shadow: 0 6px 18px rgba(255, 109, 90, .25);\n}\n\nbutton[type=\"submit\"]:hover {\n filter: brightness(0.98);\n transform: translateY(-1px);\n box-shadow: 0 8px 24px rgba(255, 109, 90, .28);\n}\n\nbutton[type=\"submit\"]:active {\n transform: translateY(0);\n box-shadow: 0 6px 18px rgba(255, 109, 90, .22);\n}\n\nbutton[disabled] {\n opacity: .6;\n cursor: not-allowed;\n}\n\n/* Secondary link under button (e.g., “Clear form”) */\n.clear-link {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n margin-top: 10px;\n font-size: var(--font-size-link);\n color: var(--color-link);\n text-decoration: none;\n}\n.clear-link:hover { text-decoration: underline; }\n\n/* ===== Footer / “Automated with n8n” ===== */\n.form-footer {\n margin: 10px auto 24px;\n max-width: var(--container-width);\n text-align: center;\n color: var(--color-link);\n font-size: var(--font-size-link);\n}\n\n/* ===== Responsive ===== */\n@media (max-width: 520px) {\n .container, .form-wrapper, .n8n-form {\n margin: 20px auto;\n padding: 0 10px;\n }\n .form-card { padding: 20px; }\n .form-card h1, h1.form-title { font-size: 18px; }\n .form-subtitle { font-size: 13px; }\n}\n\n/* ===== Optional: Dark mode (auto) ===== */\n@media (prefers-color-scheme: dark) {\n body {\n background:\n radial-gradient(1200px 800px at 10% -10%, #272a33 0%, transparent 60%),\n radial-gradient(1000px 700px at 110% 0%, #2c2433 0%, transparent 55%),\n #16181d;\n color: #d8dbe3;\n }\n\n .test-notice {\n background: #2a2115;\n border-color: #3a2b18;\n color: #ffce74;\n }\n\n .form-card {\n background: #1c1f27;\n border-color: #2a2f3a;\n box-shadow: 0 8px 24px rgba(0,0,0,.35);\n }\n\n .form-card h1, h1.form-title { color: #e9ebf3; }\n .form-subtitle { color: #a6adbb; }\n\n input, textarea, select {\n background: #11151c;\n border-color: #2a2f3a;\n color: #d8dbe3;\n }\n input:hover, textarea:hover, select:hover { border-color: #3a4250; }\n input:focus, textarea:focus, select:focus {\n border-color: #b7a8ff;\n box-shadow: 0 0 0 4px rgba(183,168,255,.18);\n }\n\n .help-text { color: #9aa3b2; }\n\n button[type=\"submit\"],\n .button-submit {\n box-shadow: 0 10px 26px rgba(255,109,90,.34);\n }\n\n .form-footer { color: #9aa3b2; }\n}\n"
},
"formFields": {
"values": [
{
"fieldType": "textarea",
"fieldLabel": "What would you like to request?",
"requiredField": true
}
]
}
},
"typeVersion": 2.3
},
{
"id": "393cdcc8-af4d-42bb-b8c1-5891d1b089de",
"name": "OpenAI 聊天模型",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
2304,
400
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-nano",
"cachedResultName": "gpt-4.1-nano"
},
"options": {}
},
"credentials": {
"openAiApi": {
"id": "zwivGqRORUgpjepY",
"name": "OpenAi account"
}
},
"typeVersion": 1.2
},
{
"id": "fae1ff57-f79b-46e6-a9c1-97abab368585",
"name": "获取员工",
"type": "n8n-nodes-base.mySql",
"position": [
16,
-144
],
"parameters": {
"table": {
"__rl": true,
"mode": "list",
"value": "employees",
"cachedResultName": "employees"
},
"where": {
"values": [
{
"value": "={{ $json['Your employee enrollment'] }}",
"column": "enrollment_number"
}
]
},
"options": {},
"operation": "select",
"returnAll": true
},
"credentials": {
"mySql": {
"id": "sQeUBEQkBvNWbM2y",
"name": "MySQL - Estudos"
}
},
"typeVersion": 2.5,
"alwaysOutputData": true
},
{
"id": "c5d3a970-c61e-4a87-910e-18809d9f7cee",
"name": "员工数据",
"type": "n8n-nodes-base.set",
"position": [
720,
96
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "3f65a01e-465e-412e-b9f5-31b366724dc7",
"name": "employee.enrollment_number",
"type": "string",
"value": "={{ $json.enrollment_number }}"
},
{
"id": "e2f24a33-b96b-4106-a7cc-bdad369407b4",
"name": "employee.name",
"type": "string",
"value": "={{ $json.name }}"
},
{
"id": "c7fe12b0-389d-477b-ab73-62da0fcafd3a",
"name": "employee.email",
"type": "string",
"value": "={{ $json.email }}"
},
{
"id": "8ceb93de-c37a-4c9e-af13-54a582a2e7bd",
"name": "employee.manager",
"type": "number",
"value": "={{ $json.manager }}"
},
{
"id": "971f3df4-3b51-44a0-b824-9c0d43f86f92",
"name": "employee.requires_approval",
"type": "boolean",
"value": "={{ $json.manager ? true : false }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "1858f338-3622-4d98-8d07-051069436f39",
"name": "是否需要批准?",
"type": "n8n-nodes-base.if",
"position": [
1040,
48
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "16796776-0e35-4704-86fa-d65e91d9c20f",
"operator": {
"type": "number",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.employee.manager }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "c3e7dc19-8840-4108-8049-ad34aa0ef9f9",
"name": "获取经理",
"type": "n8n-nodes-base.mySql",
"position": [
1360,
32
],
"parameters": {
"table": {
"__rl": true,
"mode": "list",
"value": "employees",
"cachedResultName": "employees"
},
"where": {
"values": [
{
"value": "={{ $json.employee.manager }}",
"column": "id"
}
]
},
"options": {},
"operation": "select",
"returnAll": true
},
"credentials": {
"mySql": {
"id": "sQeUBEQkBvNWbM2y",
"name": "MySQL - Estudos"
}
},
"typeVersion": 2.5
},
{
"id": "d5df5874-e4e4-47c7-a224-120f2f69f0cc",
"name": "员工和经理",
"type": "n8n-nodes-base.merge",
"position": [
2016,
16
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition"
},
"typeVersion": 3.2
},
{
"id": "da2d6915-dcb3-4a11-a36c-a7e39ae5212b",
"name": "设置经理字段",
"type": "n8n-nodes-base.set",
"position": [
1680,
32
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "7b2ad1ea-22a6-4e66-b17b-12e6c8273a75",
"name": "manager",
"type": "object",
"value": "={{ $json }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "f85e302e-2285-43d8-a29d-8b11267cb442",
"name": "合并员工数据和请求",
"type": "n8n-nodes-base.merge",
"position": [
1360,
448
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition"
},
"typeVersion": 3.2
},
{
"id": "abb1fb4d-f473-41e8-aef2-9679dd56356c",
"name": "批准请求消息",
"type": "n8n-nodes-base.if",
"position": [
1696,
448
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "586bed7e-b297-47ef-bdd0-d0e17de39ef7",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.employee.requires_approval }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "ba028c9d-b723-4bac-9713-7ab9fb98d32a",
"name": "创建批准消息",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
2352,
256
],
"parameters": {
"text": "=Employee request:\n\n{{ $json['What would you like to request?'] }}\n",
"batching": {},
"messages": {
"messageValues": [
{
"message": "=You are a writing assistant. \nYou will receive an equipment/item request. \nYour tasks are: \n1. Sanitize the request (fix grammar, remove unnecessary words, strange characters, or informal language). \n2. Rewrite it into a clear, professional, and polite approval request message to a manager. \n3. Output the result as a valid JSON object containing both the improved request and the full e-mail text. \n\nKeep the tone formal, concise, and business-appropriate. \nDo not invent information; only improve grammar, clarity, and tone. \n\nFormat strictly as JSON, like this:\n\n{\n \"sanitized_request\": \"[sanitized and improved version of the request]\",\n \"email_text\": \"Dear {{ $json.manager.name }},\\n\\nPlease approve the procurement of [sanitized_request] for employee {{ $json.employee.name }} (Enrollment: {{ $json.employee.enrollment_number }}).\\n\\nThank you for your attention to this request.\\n\\nBest regards,\\nEquipment request AI\"\n}\n\nEmployee data:\nEnrollment: {{ $json.employee.enrollment_number }}\nName: {{ $json.employee.name }}\nEmail: {{ $json.employee.email }}\n\nManager data:\nName: {{ $json.manager.name }}\nEmail: {{ $json.manager.email }}\n"
}
]
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 1.7
},
{
"id": "8cffcddb-27f7-4f16-8e10-95302097c363",
"name": "数据和消息",
"type": "n8n-nodes-base.merge",
"position": [
2816,
272
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition"
},
"typeVersion": 3.2
},
{
"id": "836480a0-a663-4bcb-9357-034b889e2170",
"name": "结束员工请求",
"type": "n8n-nodes-base.form",
"position": [
3248,
272
],
"webhookId": "912c0497-44a9-4c79-9824-a3c7d4611177",
"parameters": {
"options": {
"customCss": "/* ===== Base layout ===== */\nhtml, body {\n height: 100%;\n font-family: var(--font-family);\n font-weight: var(--font-weight-normal);\n font-size: var(--font-size-body);\n background:\n radial-gradient(1200px 800px at 10% -10%, #f0f2ff 0%, transparent 60%),\n radial-gradient(1000px 700px at 110% 0%, #fef3f0 0%, transparent 55%),\n var(--color-background);\n color: var(--color-html-text);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n.container, .form-wrapper, .n8n-form {\n max-width: var(--container-width);\n margin: 32px auto;\n padding: 0 12px;\n}\n\n/* ===== Test banner (top) ===== */\n.test-notice {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0 auto 12px;\n max-width: var(--container-width);\n background: var(--color-test-notice-bg);\n border: 1px solid var(--color-test-notice-border);\n color: var(--color-test-notice-text);\n padding: var(--padding-test-notice-vertical) var(--padding-test-notice-horizontal);\n border-radius: var(--border-radius-input);\n font-size: var(--font-size-test-notice);\n}\n\n/* ===== Card ===== */\n.form-card {\n background: var(--color-card-bg);\n border: 1px solid var(--color-card-border);\n border-radius: var(--border-radius-card);\n box-shadow: var(--box-shadow-card);\n padding: var(--padding-card);\n margin-bottom: var(--margin-bottom-card);\n}\n\n/* ===== Headings & subtitle ===== */\n.form-card h1,\nh1.form-title {\n font-size: var(--font-size-header);\n color: var(--color-header);\n margin: 4px 0 6px;\n font-weight: var(--font-weight-bold);\n letter-spacing: -0.2px;\n}\n\n.form-subtitle {\n font-size: var(--font-size-paragraph);\n color: var(--color-header-subtext);\n margin: 0 0 16px;\n line-height: 1.5;\n}\n\n/* ===== Form fields ===== */\n.form-field {\n display: grid;\n gap: 6px;\n margin-bottom: 14px;\n}\n\n.form-field label {\n font-size: var(--font-size-label);\n color: var(--color-label);\n font-weight: 600;\n}\n\n.form-field label .required {\n color: var(--color-required);\n margin-left: 2px;\n}\n\n/* Inputs / selects / textareas */\ninput[type=\"text\"],\ninput[type=\"email\"],\ninput[type=\"number\"],\ninput[type=\"search\"],\ntextarea,\nselect {\n width: 100%;\n appearance: none;\n border: 1px solid var(--color-input-border);\n border-radius: var(--border-radius-input);\n padding: 12px 14px;\n font-size: var(--font-size-input);\n color: var(--color-input-text);\n background: #fff;\n transition: border-color .15s ease, box-shadow .15s ease, transform .05s ease;\n}\n\ninput::placeholder,\ntextarea::placeholder {\n opacity: var(--opacity-placeholder);\n}\n\n/* Hover / focus */\ninput:hover,\ntextarea:hover,\nselect:hover {\n border-color: #cfd5e2;\n}\n\ninput:focus,\ntextarea:focus,\nselect:focus {\n outline: none;\n border-color: var(--color-focus-border);\n box-shadow: 0 0 0 4px rgba(90, 76, 194, .12);\n}\n\n/* Invalid */\n.is-invalid,\ninput[aria-invalid=\"true\"],\ntextarea[aria-invalid=\"true\"],\nselect[aria-invalid=\"true\"] {\n border-color: var(--color-error);\n box-shadow: 0 0 0 4px rgba(234, 31, 48, .12);\n}\n\n/* Help / error text */\n.help-text {\n font-size: 12px;\n color: var(--color-link);\n}\n\n.error-text {\n font-size: var(--font-size-error);\n color: var(--color-error);\n margin-top: 4px;\n}\n\n/* ===== File input (custom) ===== */\n.input-file {\n position: relative;\n}\n\n.input-file input[type=\"file\"] {\n position: absolute;\n inset: 0;\n opacity: 0;\n cursor: pointer;\n}\n\n.input-file .file-ui {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n border: 1px dashed var(--color-input-border);\n border-radius: var(--border-radius-input);\n padding: 12px 14px;\n color: var(--color-input-text);\n background: #fff;\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n\n.input-file .file-ui:hover {\n border-color: #cfd5e2;\n}\n\n.input-file .file-ui:has(+ input[type=\"file\"]:focus) {\n border-color: var(--color-focus-border);\n box-shadow: 0 0 0 4px rgba(90, 76, 194, .12);\n}\n\n/* ===== Checkboxes / radios (accessible but nicer) ===== */\ninput[type=\"checkbox\"], input[type=\"radio\"] {\n accent-color: var(--color-focus-border);\n width: var(--checkbox-size);\n height: var(--checkbox-size);\n}\n\n/* ===== Buttons ===== */\n.actions,\n.form-actions {\n margin-top: 8px;\n}\n\nbutton[type=\"submit\"],\n.button-submit {\n width: 100%;\n height: var(--submit-btn-height);\n background: var(--color-submit-btn-bg);\n color: var(--color-submit-btn-text);\n border: 1px solid transparent;\n border-radius: var(--border-radius-input);\n font-size: 15px;\n font-weight: 700;\n letter-spacing: .2px;\n cursor: pointer;\n transition: transform .05s ease, filter .15s ease, box-shadow .15s ease;\n box-shadow: 0 6px 18px rgba(255, 109, 90, .25);\n}\n\nbutton[type=\"submit\"]:hover {\n filter: brightness(0.98);\n transform: translateY(-1px);\n box-shadow: 0 8px 24px rgba(255, 109, 90, .28);\n}\n\nbutton[type=\"submit\"]:active {\n transform: translateY(0);\n box-shadow: 0 6px 18px rgba(255, 109, 90, .22);\n}\n\nbutton[disabled] {\n opacity: .6;\n cursor: not-allowed;\n}\n\n/* Secondary link under button (e.g., “Clear form”) */\n.clear-link {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n margin-top: 10px;\n font-size: var(--font-size-link);\n color: var(--color-link);\n text-decoration: none;\n}\n.clear-link:hover { text-decoration: underline; }\n\n/* ===== Footer / “Automated with n8n” ===== */\n.form-footer {\n margin: 10px auto 24px;\n max-width: var(--container-width);\n text-align: center;\n color: var(--color-link);\n font-size: var(--font-size-link);\n}\n\n/* ===== Responsive ===== */\n@media (max-width: 520px) {\n .container, .form-wrapper, .n8n-form {\n margin: 20px auto;\n padding: 0 10px;\n }\n .form-card { padding: 20px; }\n .form-card h1, h1.form-title { font-size: 18px; }\n .form-subtitle { font-size: 13px; }\n}\n\n/* ===== Optional: Dark mode (auto) ===== */\n@media (prefers-color-scheme: dark) {\n body {\n background:\n radial-gradient(1200px 800px at 10% -10%, #272a33 0%, transparent 60%),\n radial-gradient(1000px 700px at 110% 0%, #2c2433 0%, transparent 55%),\n #16181d;\n color: #d8dbe3;\n }\n\n .test-notice {\n background: #2a2115;\n border-color: #3a2b18;\n color: #ffce74;\n }\n\n .form-card {\n background: #1c1f27;\n border-color: #2a2f3a;\n box-shadow: 0 8px 24px rgba(0,0,0,.35);\n }\n\n .form-card h1, h1.form-title { color: #e9ebf3; }\n .form-subtitle { color: #a6adbb; }\n\n input, textarea, select {\n background: #11151c;\n border-color: #2a2f3a;\n color: #d8dbe3;\n }\n input:hover, textarea:hover, select:hover { border-color: #3a4250; }\n input:focus, textarea:focus, select:focus {\n border-color: #b7a8ff;\n box-shadow: 0 0 0 4px rgba(183,168,255,.18);\n }\n\n .help-text { color: #9aa3b2; }\n\n button[type=\"submit\"],\n .button-submit {\n box-shadow: 0 10px 26px rgba(255,109,90,.34);\n }\n\n .form-footer { color: #9aa3b2; }\n}"
},
"operation": "completion",
"completionTitle": "Request sent for your manager’s approval",
"completionMessage": "Soon you will receive a response on whether your request has been approved."
},
"typeVersion": 2.3
},
{
"id": "cbb41697-dee5-40d1-9ae9-1618e0da91c2",
"name": "多表:您可以连接多个表以实现有组织的数据结构",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
2544,
400
],
"parameters": {
"jsonSchemaExample": "{\n\t\"email_text\": \"California\",\n\t\"sanitized_request\": \"request\"\n}"
},
"typeVersion": 1.3
},
{
"id": "eec5a816-c41b-4d53-80b5-0d73ffe7c92f",
"name": "通知已批准请求",
"type": "n8n-nodes-base.gmail",
"position": [
4480,
80
],
"webhookId": "1dcd89a1-1c9d-4fa2-a91d-a18dc99ea559",
"parameters": {
"sendTo": "={{ $('Data and message').item.json.employee.email }}",
"message": "=Dear {{ $('Data and message').item.json.employee.name }},\n\nYour request for the following item(s) has been approved and forwarded to the Procurement Department:\n\n{{ $('Data and message').item.json.output.sanitized_request }}\n\nThe purchasing team will proceed with the necessary steps and contact you if additional details are required.\n\nBest regards, \nEquipment request AI\n",
"options": {},
"subject": "Equipment Request Approved",
"emailType": "text"
},
"credentials": {
"gmailOAuth2": {
"id": "cyXtSoIVe4Tw1W50",
"name": "Gmail account"
}
},
"typeVersion": 2.1
},
{
"id": "1b39aac6-8136-4f70-b59b-df0dd81bbcd9",
"name": "通知已拒绝请求",
"type": "n8n-nodes-base.gmail",
"position": [
4480,
416
],
"webhookId": "1dcd89a1-1c9d-4fa2-a91d-a18dc99ea559",
"parameters": {
"sendTo": "={{ $('Data and message').item.json.employee.email }}",
"message": "=Dear {{ $('Data and message').item.json.employee.name }},\n\nYour request for the following item(s) has been approved and forwarded to the Procurement Department:\n\n{{ $('Data and message').item.json.output.sanitized_request }}\n\nThe purchasing team will proceed with the necessary steps and contact you if additional details are required.\n\nBest regards, \nEquipment request AI\n",
"options": {},
"subject": "Equipment Request Approved",
"emailType": "text"
},
"credentials": {
"gmailOAuth2": {
"id": "cyXtSoIVe4Tw1W50",
"name": "Gmail account"
}
},
"typeVersion": 2.1
},
{
"id": "ff97d6fb-ede9-4cfb-bf37-080fba03ead8",
"name": "OpenAI 聊天模型1",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
2304,
880
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-nano",
"cachedResultName": "gpt-4.1-nano"
},
"options": {}
},
"credentials": {
"openAiApi": {
"id": "zwivGqRORUgpjepY",
"name": "OpenAi account"
}
},
"typeVersion": 1.2
},
{
"id": "d8260916-6491-4b73-94cc-7bf1e8e0e42c",
"name": "结构化输出解析器1",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
2528,
880
],
"parameters": {
"jsonSchemaExample": "{\n\t\"email_text\": \"California\",\n\t\"sanitized_request\": \"request\"\n}"
},
"typeVersion": 1.3
},
{
"id": "f5d570f0-16f8-4610-8484-5d8338a95cae",
"name": "创建请求消息",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
2336,
736
],
"parameters": {
"text": "=Employee request:\n\n{{ $json['What would you like to request?'] }}\n",
"batching": {},
"messages": {
"messageValues": [
{
"message": "=You are a writing assistant. \nYou will receive an equipment/item request. \nYour tasks are: \n1. Sanitize the request (fix grammar, remove unnecessary words, strange characters, or informal language). \n2. Rewrite it into a clear, professional, and polite approval request message to a manager. \n3. Output the result as a valid JSON object containing both the improved request and the full e-mail text. \n\nKeep the tone formal, concise, and business-appropriate. \nDo not invent information; only improve grammar, clarity, and tone. \n\nFormat strictly as JSON, like this:\n\n{\n \"sanitized_request\": \"[sanitized and improved version of the request]\",\n \"email_text\": \"Dear {{ $json.manager.name }},\\n\\nPlease approve the procurement of [sanitized_request] for employee {{ $json.employee.name }} (Enrollment: {{ $json.employee.enrollment_number }}).\\n\\nThank you for your attention to this request.\\n\\nBest regards,\\nEquipment request AI\"\n}\n\nEmployee data:\nEnrollment: {{ $json.employee.enrollment_number }}\nName: {{ $json.employee.name }}\nEmail: {{ $json.employee.email }}\n\nManager data:\nName: {{ $json.manager.name }}\nEmail: {{ $json.manager.email }}\n"
}
]
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 1.7
},
{
"id": "7c273ebb-019b-4eed-ba76-254437904b2f",
"name": "OpenAI 聊天模型2",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
4832,
416
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-nano",
"cachedResultName": "gpt-4.1-nano"
},
"options": {}
},
"credentials": {
"openAiApi": {
"id": "zwivGqRORUgpjepY",
"name": "OpenAi account"
}
},
"typeVersion": 1.2
},
{
"id": "d7f6d4ff-5c14-46bd-8f88-57fbe9999058",
"name": "结构化输出解析器2",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
5088,
416
],
"parameters": {
"jsonSchemaExample": "{\n\t\"email_text\": \"California\",\n\t\"sanitized_request\": \"request\"\n}"
},
"typeVersion": 1.3
},
{
"id": "bd8d035b-b0da-46c6-9c8b-1712095f6df0",
"name": "创建已批准请求消息",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
4880,
256
],
"parameters": {
"text": "=Employee request:\n\n{{ $('Data and message').item.json['What would you like to request?'] }}\n",
"batching": {},
"messages": {
"messageValues": [
{
"message": "=You are a writing assistant. \nYou will receive an equipment/item request that has already been approved by the employee’s manager. \nYour tasks are: \n1. Sanitize the request (fix grammar, remove unnecessary words, strange characters, or informal language). \n2. Rewrite it into a clear, professional, and polite procurement request e-mail addressed to the Purchasing Department. \n3. Explicitly state in the message that the request was approved by the employee’s manager. \n4. Output the result as a valid JSON object containing both the improved request and the full e-mail text. \n\nKeep the tone formal, concise, and business-appropriate. \nDo not invent information; only improve grammar, clarity, and tone. \n\nFormat strictly as JSON, like this:\n\n{\n \"sanitized_request\": \"[sanitized and improved version of the request]\",\n \"email_text\": \"Dear Purchasing Department,\\n\\nPlease proceed with the procurement of [sanitized_request] for employee {{ $('Data and message').item.json.employee.name }} (Enrollment: {{ $('Data and message').item.json.employee.enrollment_number }}). This request has been reviewed and approved by the manager {{ $('Data and message').item.json.manager.name }}.\\n\\nThank you for your attention to this request.\\n\\nBest regards,\\nEquipment request AI\"\n}\n\nEmployee data: \nEnrollment: {{ $('Data and message').item.json.employee.enrollment_number }} \nName: {{ $('Data and message').item.json.employee.name }} \nEmail: {{ $('Data and message').item.json.employee.email }} \n\nManager data: \nName: {{ $('Data and message').item.json.manager.name }} \nEmail: {{ $('Data and message').item.json.manager.email }} \n"
}
]
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 1.7
},
{
"id": "c4896c62-35a5-4c34-b97f-0c28a9aa5225",
"name": "发送已批准请求至采购部门",
"type": "n8n-nodes-base.noOp",
"position": [
5440,
256
],
"parameters": {},
"typeVersion": 1
},
{
"id": "7374c685-d578-4b89-a730-21f12c1e3168",
"name": "发送请求至采购部门",
"type": "n8n-nodes-base.noOp",
"position": [
2880,
736
],
"parameters": {},
"typeVersion": 1
},
{
"id": "9f0e9bdd-d1ee-480c-8808-624dfbb661bf",
"name": "结束经理请求",
"type": "n8n-nodes-base.form",
"position": [
3360,
736
],
"webhookId": "912c0497-44a9-4c79-9824-a3c7d4611177",
"parameters": {
"options": {
"customCss": "/* ===== Base layout ===== */\nhtml, body {\n height: 100%;\n font-family: var(--font-family);\n font-weight: var(--font-weight-normal);\n font-size: var(--font-size-body);\n background:\n radial-gradient(1200px 800px at 10% -10%, #f0f2ff 0%, transparent 60%),\n radial-gradient(1000px 700px at 110% 0%, #fef3f0 0%, transparent 55%),\n var(--color-background);\n color: var(--color-html-text);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n.container, .form-wrapper, .n8n-form {\n max-width: var(--container-width);\n margin: 32px auto;\n padding: 0 12px;\n}\n\n/* ===== Test banner (top) ===== */\n.test-notice {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0 auto 12px;\n max-width: var(--container-width);\n background: var(--color-test-notice-bg);\n border: 1px solid var(--color-test-notice-border);\n color: var(--color-test-notice-text);\n padding: var(--padding-test-notice-vertical) var(--padding-test-notice-horizontal);\n border-radius: var(--border-radius-input);\n font-size: var(--font-size-test-notice);\n}\n\n/* ===== Card ===== */\n.form-card {\n background: var(--color-card-bg);\n border: 1px solid var(--color-card-border);\n border-radius: var(--border-radius-card);\n box-shadow: var(--box-shadow-card);\n padding: var(--padding-card);\n margin-bottom: var(--margin-bottom-card);\n}\n\n/* ===== Headings & subtitle ===== */\n.form-card h1,\nh1.form-title {\n font-size: var(--font-size-header);\n color: var(--color-header);\n margin: 4px 0 6px;\n font-weight: var(--font-weight-bold);\n letter-spacing: -0.2px;\n}\n\n.form-subtitle {\n font-size: var(--font-size-paragraph);\n color: var(--color-header-subtext);\n margin: 0 0 16px;\n line-height: 1.5;\n}\n\n/* ===== Form fields ===== */\n.form-field {\n display: grid;\n gap: 6px;\n margin-bottom: 14px;\n}\n\n.form-field label {\n font-size: var(--font-size-label);\n color: var(--color-label);\n font-weight: 600;\n}\n\n.form-field label .required {\n color: var(--color-required);\n margin-left: 2px;\n}\n\n/* Inputs / selects / textareas */\ninput[type=\"text\"],\ninput[type=\"email\"],\ninput[type=\"number\"],\ninput[type=\"search\"],\ntextarea,\nselect {\n width: 100%;\n appearance: none;\n border: 1px solid var(--color-input-border);\n border-radius: var(--border-radius-input);\n padding: 12px 14px;\n font-size: var(--font-size-input);\n color: var(--color-input-text);\n background: #fff;\n transition: border-color .15s ease, box-shadow .15s ease, transform .05s ease;\n}\n\ninput::placeholder,\ntextarea::placeholder {\n opacity: var(--opacity-placeholder);\n}\n\n/* Hover / focus */\ninput:hover,\ntextarea:hover,\nselect:hover {\n border-color: #cfd5e2;\n}\n\ninput:focus,\ntextarea:focus,\nselect:focus {\n outline: none;\n border-color: var(--color-focus-border);\n box-shadow: 0 0 0 4px rgba(90, 76, 194, .12);\n}\n\n/* Invalid */\n.is-invalid,\ninput[aria-invalid=\"true\"],\ntextarea[aria-invalid=\"true\"],\nselect[aria-invalid=\"true\"] {\n border-color: var(--color-error);\n box-shadow: 0 0 0 4px rgba(234, 31, 48, .12);\n}\n\n/* Help / error text */\n.help-text {\n font-size: 12px;\n color: var(--color-link);\n}\n\n.error-text {\n font-size: var(--font-size-error);\n color: var(--color-error);\n margin-top: 4px;\n}\n\n/* ===== File input (custom) ===== */\n.input-file {\n position: relative;\n}\n\n.input-file input[type=\"file\"] {\n position: absolute;\n inset: 0;\n opacity: 0;\n cursor: pointer;\n}\n\n.input-file .file-ui {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n border: 1px dashed var(--color-input-border);\n border-radius: var(--border-radius-input);\n padding: 12px 14px;\n color: var(--color-input-text);\n background: #fff;\n transition: border-color .15s ease, box-shadow .15s ease;\n}\n\n.input-file .file-ui:hover {\n border-color: #cfd5e2;\n}\n\n.input-file .file-ui:has(+ input[type=\"file\"]:focus) {\n border-color: var(--color-focus-border);\n box-shadow: 0 0 0 4px rgba(90, 76, 194, .12);\n}\n\n/* ===== Checkboxes / radios (accessible but nicer) ===== */\ninput[type=\"checkbox\"], input[type=\"radio\"] {\n accent-color: var(--color-focus-border);\n width: var(--checkbox-size);\n height: var(--checkbox-size);\n}\n\n/* ===== Buttons ===== */\n.actions,\n.form-actions {\n margin-top: 8px;\n}\n\nbutton[type=\"submit\"],\n.button-submit {\n width: 100%;\n height: var(--submit-btn-height);\n background: var(--color-submit-btn-bg);\n color: var(--color-submit-btn-text);\n border: 1px solid transparent;\n border-radius: var(--border-radius-input);\n font-size: 15px;\n font-weight: 700;\n letter-spacing: .2px;\n cursor: pointer;\n transition: transform .05s ease, filter .15s ease, box-shadow .15s ease;\n box-shadow: 0 6px 18px rgba(255, 109, 90, .25);\n}\n\nbutton[type=\"submit\"]:hover {\n filter: brightness(0.98);\n transform: translateY(-1px);\n box-shadow: 0 8px 24px rgba(255, 109, 90, .28);\n}\n\nbutton[type=\"submit\"]:active {\n transform: translateY(0);\n box-shadow: 0 6px 18px rgba(255, 109, 90, .22);\n}\n\nbutton[disabled] {\n opacity: .6;\n cursor: not-allowed;\n}\n\n/* Secondary link under button (e.g., “Clear form”) */\n.clear-link {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n margin-top: 10px;\n font-size: var(--font-size-link);\n color: var(--color-link);\n text-decoration: none;\n}\n.clear-link:hover { text-decoration: underline; }\n\n/* ===== Footer / “Automated with n8n” ===== */\n.form-footer {\n margin: 10px auto 24px;\n max-width: var(--container-width);\n text-align: center;\n color: var(--color-link);\n font-size: var(--font-size-link);\n}\n\n/* ===== Responsive ===== */\n@media (max-width: 520px) {\n .container, .form-wrapper, .n8n-form {\n margin: 20px auto;\n padding: 0 10px;\n }\n .form-card { padding: 20px; }\n .form-card h1, h1.form-title { font-size: 18px; }\n .form-subtitle { font-size: 13px; }\n}\n\n/* ===== Optional: Dark mode (auto) ===== */\n@media (prefers-color-scheme: dark) {\n body {\n background:\n radial-gradient(1200px 800px at 10% -10%, #272a33 0%, transparent 60%),\n radial-gradient(1000px 700px at 110% 0%, #2c2433 0%, transparent 55%),\n #16181d;\n color: #d8dbe3;\n }\n\n .test-notice {\n background: #2a2115;\n border-color: #3a2b18;\n color: #ffce74;\n }\n\n .form-card {\n background: #1c1f27;\n border-color: #2a2f3a;\n box-shadow: 0 8px 24px rgba(0,0,0,.35);\n }\n\n .form-card h1, h1.form-title { color: #e9ebf3; }\n .form-subtitle { color: #a6adbb; }\n\n input, textarea, select {\n background: #11151c;\n border-color: #2a2f3a;\n color: #d8dbe3;\n }\n input:hover, textarea:hover, select:hover { border-color: #3a4250; }\n input:focus, textarea:focus, select:focus {\n border-color: #b7a8ff;\n box-shadow: 0 0 0 4px rgba(183,168,255,.18);\n }\n\n .help-text { color: #9aa3b2; }\n\n button[type=\"submit\"],\n .button-submit {\n box-shadow: 0 10px 26px rgba(255,109,90,.34);\n }\n\n .form-footer { color: #9aa3b2; }\n}\n"
},
"operation": "completion",
"completionTitle": "Request sent to the purchasing department",
"completionMessage": "Your request has already been forwarded to the purchasing department."
},
"typeVersion": 2.3
},
{
"id": "2411f09c-2392-48d2-83a4-3ce0d087b8ea",
"name": "发送待批准请求",
"type": "n8n-nodes-base.gmail",
"position": [
3696,
272
],
"webhookId": "dde2b15d-a1d9-462b-ae23-f0eb024319c2",
"parameters": {
"sendTo": "={{ $('Data and message').item.json.manager.email }}",
"message": "={{ $('Data and message').item.json.output.email_text }}",
"options": {},
"subject": "=Equipment Request Approval – {{ $('Data and message').item.json.employee.name }} (Enrollment:{{ $('Data and message').item.json.employee.enrollment_number }})",
"operation": "sendAndWait",
"approvalOptions": {
"values": {
"approvalType": "double"
}
}
},
"credentials": {
"gmailOAuth2": {
"id": "cyXtSoIVe4Tw1W50",
"name": "Gmail account"
}
},
"typeVersion": 2.1
},
{
"id": "4f63e8a7-04d8-491d-acea-1a46108182dc",
"name": "便签",
"type": "n8n-nodes-base.stickyNote",
"position": [
-560,
-1056
],
"parameters": {
"width": 1072,
"height": 544,
"content": "## 设备请求 — 批准与采购流程"
},
"typeVersion": 1
},
{
"id": "13f364a2-b86e-4283-8fce-fa11e3e3d5f7",
"name": "便签1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-560,
-336
],
"parameters": {
"color": 4,
"width": 416,
"height": 400,
"content": "收集员工工号。"
},
"typeVersion": 1
},
{
"id": "d35815c9-44fb-465e-b2a0-19badd10711c",
"name": "便签2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-128,
-336
],
"parameters": {
"color": 5,
"width": 416,
"height": 400,
"content": "在 `employees` 中按 enrollment_number 查找员工。"
},
"typeVersion": 1
},
{
"id": "8a81d631-b8dd-4772-8bde-24d698b73074",
"name": "便签3",
"type": "n8n-nodes-base.stickyNote",
"position": [
304,
-336
],
"parameters": {
"color": 6,
"width": 304,
"height": 400,
"content": "检查数据库查找是否返回空。"
},
"typeVersion": 1
},
{
"id": "bb7851a9-a5cc-414f-bd1b-bae07fb2af89",
"name": "便签4",
"type": "n8n-nodes-base.stickyNote",
"position": [
624,
-528
],
"parameters": {
"color": 5,
"width": 304,
"height": 368,
"content": "当无法匹配工号时的用户面向消息。"
},
"typeVersion": 1
},
{
"id": "1b55d378-cdd2-44a3-a71a-7b5421bd4cb6",
"name": "便签5",
"type": "n8n-nodes-base.stickyNote",
"position": [
624,
-144
],
"parameters": {
"color": 5,
"width": 304,
"height": 400,
"content": "规范化员工对象供后续节点使用:"
},
"typeVersion": 1
},
{
"id": "f28a39e9-3817-410c-bf68-949bebc16b3f",
"name": "便签6",
"type": "n8n-nodes-base.stickyNote",
"position": [
944,
-144
],
"parameters": {
"color": 6,
"width": 304,
"height": 368,
"content": "分支:"
},
"typeVersion": 1
},
{
"id": "1d511e50-b193-432f-8115-d8171790948f",
"name": "便签7",
"type": "n8n-nodes-base.stickyNote",
"position": [
1264,
-144
],
"parameters": {
"color": 5,
"width": 304,
"height": 368,
"content": "通过 `id = employee.manager` 从 `employees` 获取经理记录。"
},
"typeVersion": 1
},
{
"id": "b00441ed-2399-485f-a129-1fb0e60cba0f",
"name": "便签8",
"type": "n8n-nodes-base.stickyNote",
"position": [
1584,
-144
],
"parameters": {
"color": 5,
"width": 304,
"height": 368,
"content": "将经理行包装为单个 `manager` 对象以便访问。"
},
"typeVersion": 1
},
{
"id": "66ac3153-bab7-4ad1-a878-9105a390b060",
"name": "便签9",
"type": "n8n-nodes-base.stickyNote",
"position": [
1904,
-144
],
"parameters": {
"color": 6,
"width": 304,
"height": 368,
"content": "在数据库查找后合并员工和经理流。"
},
"typeVersion": 1
},
{
"id": "50a8c7f3-a77e-4daa-942e-a98af2ff699a",
"name": "便签 10",
"type": "n8n-nodes-base.stickyNote",
"position": [
-560,
304
],
"parameters": {
"color": 4,
"width": 416,
"height": 400,
"content": "询问员工想要请求什么(自由文本)。"
},
"typeVersion": 1
},
{
"id": "6f027668-fc90-4824-9858-09b48e3ceade",
"name": "便签 11",
"type": "n8n-nodes-base.stickyNote",
"position": [
1264,
320
],
"parameters": {
"color": 6,
"width": 304,
"height": 368,
"content": "将员工上下文和自由文本请求合并为一个有效载荷。"
},
"typeVersion": 1
},
{
"id": "7ff2a7ab-94b6-48ca-82d5-0c09ed386233",
"name": "便签12",
"type": "n8n-nodes-base.stickyNote",
"position": [
1584,
320
],
"parameters": {
"color": 6,
"width": 304,
"height": 368,
"content": "简单门控:"
},
"typeVersion": 1
},
{
"id": "d15fafb7-c9a3-4cc2-aa85-be88371d809f",
"name": "便签13",
"type": "n8n-nodes-base.stickyNote",
"position": [
2224,
48
],
"parameters": {
"color": 5,
"width": 464,
"height": 480,
"content": "提示 LLM:"
},
"typeVersion": 1
},
{
"id": "15864539-f1ae-45ed-ae6f-a7fa57bc6507",
"name": "便签14",
"type": "n8n-nodes-base.stickyNote",
"position": [
2704,
48
],
"parameters": {
"color": 5,
"width": 336,
"height": 480,
"content": "将清理后的输出与员工/经理上下文合并供邮件节点使用。"
},
"typeVersion": 1
},
{
"id": "23d85284-cddc-4070-9d55-ff137832fcad",
"name": "便签15",
"type": "n8n-nodes-base.stickyNote",
"position": [
3056,
48
],
"parameters": {
"color": 3,
"width": 464,
"height": 448,
"content": "在\"需要批准\"路径中提交后请求者的最终屏幕。"
},
"typeVersion": 1
},
{
"id": "2a5c9acd-e1b9-4682-afac-6b326a417a11",
"name": "便签16",
"type": "n8n-nodes-base.stickyNote",
"position": [
3536,
48
],
"parameters": {
"color": 4,
"width": 416,
"height": 448,
"content": "向经理发送带有 `email_text` 的邮件并等待批准/拒绝(双重批准)。"
},
"typeVersion": 1
},
{
"id": "2d6051aa-aa42-47c4-9204-170113ee1765",
"name": "请求批准了吗?",
"type": "n8n-nodes-base.if",
"position": [
4080,
272
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "bc6a5fcf-a316-48b6-88cf-d6e24b9a37f0",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.data.approved }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "176cba48-2ecf-4b7d-b511-a6a8ad28dd5b",
"name": "便签17",
"type": "n8n-nodes-base.stickyNote",
"position": [
3968,
48
],
"parameters": {
"color": 6,
"width": 352,
"height": 448,
"content": "根据经理的决定路由:"
},
"typeVersion": 1
},
{
"id": "0c768e9a-d02d-4cfe-bb67-133b990d5e9c",
"name": "便签18",
"type": "n8n-nodes-base.stickyNote",
"position": [
4336,
-96
],
"parameters": {
"color": 4,
"width": 416,
"height": 336,
"content": "Sends a confirmation to the employee that the request was approved\nand forwarded to Procurement.\n\nCustomize copy for your internal SLA.\n"
},
"typeVersion": 1
},
{
"id": "6b4f3045-856b-4727-8fd5-e631bcf64852",
"name": "便签19",
"type": "n8n-nodes-base.stickyNote",
"position": [
4336,
256
],
"parameters": {
"color": 4,
"width": 416,
"height": 336,
"content": "Informs the employee that the manager denied the request.\nConsider including a short re-submission instruction.\n"
},
"typeVersion": 1
},
{
"id": "5ee6d466-de66-4cc8-ac37-3e5ccc10618f",
"name": "便签20",
"type": "n8n-nodes-base.stickyNote",
"position": [
4768,
64
],
"parameters": {
"color": 5,
"width": 464,
"height": 480,
"content": "Generates the Procurement email for an APPROVED request.\nExplicitly states the manager’s approval in the text.\n\nReturns JSON: { sanitized_request, email_text }.\n"
},
"typeVersion": 1
},
{
"id": "c1da8b91-d925-496e-82b7-625c7483988c",
"name": "便签21",
"type": "n8n-nodes-base.stickyNote",
"position": [
5248,
64
],
"parameters": {
"color": 5,
"width": 464,
"height": 480,
"content": "Replace this with your actual Procurement notifier:\n- Gmail node (to procurement@…),\n- Helpdesk ticket (Jira/Zendesk),\n- Slack/Teams message,\n- ERP API call.\n\nUse `email_text` from the approved message node.\n"
},
"typeVersion": 1
},
{
"id": "5b155d25-a94e-4986-bd3d-626d1a4e8508",
"name": "Sticky Note22",
"type": "n8n-nodes-base.stickyNote",
"position": [
2224,
544
],
"parameters": {
"color": 5,
"width": 464,
"height": 480,
"content": "For employees who are managers (no approval required):\n- Sanitizes the request,\n- Generates Procurement email (no approval mention).\n\nReturns JSON: { sanitized_request, email_text }.\n"
},
"typeVersion": 1
},
{
"id": "7c6efdbe-749e-4930-9a1a-d69765a0be0c",
"name": "Sticky Note23",
"type": "n8n-nodes-base.stickyNote",
"position": [
2704,
544
],
"parameters": {
"color": 5,
"width": 464,
"height": 480,
"content": "Replace this with your actual Procurement notifier:\n- Gmail node (to procurement@…),\n- Helpdesk ticket (Jira/Zendesk),\n- Slack/Teams message,\n- ERP API call.\n\nUse `email_text` from the approved message node.\n"
},
"typeVersion": 1
},
{
"id": "5ba873c7-8cc4-4b82-858e-720a5e899e2d",
"name": "便签24",
"type": "n8n-nodes-base.stickyNote",
"position": [
3184,
544
],
"parameters": {
"color": 3,
"width": 464,
"height": 448,
"content": "Final screen when the requester is a manager:\n- Confirms it was directly forwarded to Procurement.\n"
},
"typeVersion": 1
}
],
"active": true,
"pinData": {
"Question form": [
{
"json": {
"formMode": "production",
"submittedAt": "2025-09-18T11:10:33.897-03:00",
"Your employee enrollment": "000001"
}
}
],
"Get request information": [
{
"json": {
"formMode": "production",
"submittedAt": "2025-09-18T11:10:51.899-03:00",
"What would you like to request?": "One mouse and keyboard"
}
}
],
"Send request to approve": [
{
"json": {
"data": {
"approved": true
}
}
}
]
},
"settings": {
"executionOrder": "v1"
},
"versionId": "82bcb16a-4cec-4a22-b683-02337402ad3e",
"connections": {
"If": {
"main": [
[
{
"node": "Return Employee not found",
"type": "main",
"index": 0
}
],
[
{
"node": "Employee data",
"type": "main",
"index": 0
}
]
]
},
"Get Manager": {
"main": [
[
{
"node": "Set Manager field",
"type": "main",
"index": 0
}
]
]
},
"Get Employee": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"Employee data": {
"main": [
[
{
"node": "Get request information",
"type": "main",
"index": 0
},
{
"node": "Is approval required?",
"type": "main",
"index": 0
}
]
]
},
"Question form": {
"main": [
[
{
"node": "Get Employee",
"type": "main",
"index": 0
}
]
]
},
"Data and message": {
"main": [
[
{
"node": "End employee request",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "Create approval message",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Request approved?": {
"main": [
[
{
"node": "Notify approved request",
"type": "main",
"index": 0
},
{
"node": "Create request approved message",
"type": "main",
"index": 0
}
],
[
{
"node": "Notify denied request",
"type": "main",
"index": 0
}
]
]
},
"Set Manager field": {
"main": [
[
{
"node": "Employee and Manager",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model1": {
"ai_languageModel": [
[
{
"node": "Create request message",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"OpenAI Chat Model2": {
"ai_languageModel": [
[
{
"node": "Create request approved message",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Employee and Manager": {
"main": [
[
{
"node": "Merge Employee data and request",
"type": "main",
"index": 0
}
]
]
},
"End employee request": {
"main": [
[
{
"node": "Send request to approve",
"type": "main",
"index": 0
}
]
]
},
"Is approval required?": {
"main": [
[
{
"node": "Get Manager",
"type": "main",
"index": 0
},
{
"node": "Employee and Manager",
"type": "main",
"index": 1
}
],
[
{
"node": "Merge Employee data and request",
"type": "main",
"index": 0
}
]
]
},
"Create request message": {
"main": [
[
{
"node": "Send request to the purchasing department",
"type": "main",
"index": 0
}
]
]
},
"Create approval message": {
"main": [
[
{
"node": "Data and message",
"type": "main",
"index": 1
}
]
]
},
"Get request information": {
"main": [
[
{
"node": "Merge Employee data and request",
"type": "main",
"index": 1
}
]
]
},
"Send request to approve": {
"main": [
[
{
"node": "Request approved?",
"type": "main",
"index": 0
}
]
]
},
"Approval request message": {
"main": [
[
{
"node": "Create approval message",
"type": "main",
"index": 0
},
{
"node": "Data and message",
"type": "main",
"index": 0
}
],
[
{
"node": "Create request message",
"type": "main",
"index": 0
}
]
]
},
"Structured Output Parser": {
"ai_outputParser": [
[
{
"node": "Create approval message",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Structured Output Parser1": {
"ai_outputParser": [
[
{
"node": "Create request message",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Structured Output Parser2": {
"ai_outputParser": [
[
{
"node": "Create request approved message",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Create request approved message": {
"main": [
[
{
"node": "Send request approved to the purchasing department",
"type": "main",
"index": 0
}
]
]
},
"Merge Employee data and request": {
"main": [
[
{
"node": "Approval request message",
"type": "main",
"index": 0
}
]
]
},
"Send request to the purchasing department": {
"main": [
[
{
"node": "End manager request",
"type": "main",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
高级 - 内容创作, 多模态 AI
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
WordPress博客自动化专业版(深度研究)v2.1市场
使用GPT-4o、Perplexity AI和多语言支持自动化SEO优化的博客创建
If
Set
Xml
+27
125 节点Daniel Ng
内容创作
完整的 B2B 销售流程:Apollo 潜在客户生成、Mailgun 外展和 AI 回复管理
完整的 B2B 销售流程:Apollo 潜在客户生成、Mailgun 外展和 AI 回复管理
If
Set
Code
+26
116 节点Paul
内容创作
使用OpenAI和Firecrawl从产品URL创建AI生成的Meta广告活动
使用OpenAI和Firecrawl从产品URL创建AI生成的Meta广告活动
If
Set
Code
+15
40 节点Adam Crafts
内容创作
预订自动化模板
使用GPT-5和Gmail上下文分析生成个性化音乐推广邮件
If
Code
Gmail
+12
27 节点Václav Čikl
内容创作
HR候选人解析与评估 - 使用GPT-4.1和LinkedIn数据的CSV/XLSX
HR候选人解析与评估 - 使用GPT-4.1和LinkedIn数据的CSV/XLSX
Set
Code
Gmail
+15
33 节点Elay Guez
内容创作
Google Meet 月度人力资源问答
通过AI问题聚类和Google日历集成自动化人力资源问答会议
Set
Form
Merge
+9
26 节点Gabriel Santos
人力资源
工作流信息
难度等级
高级
节点数量55
分类2
节点类型12
作者
Gabriel Santos
@gabrielhmsantosEnthusiastic developer passionate about automation and system integration. I work mainly with Python, RPA, and N8N, building workflows and custom solutions to optimize processes and connect platforms. Always learning, sharing, and exploring new ways to automate smarter.
外部链接
在 n8n.io 查看 →
分享此工作流