From 9805857a6003cfe68d8593ceeb93f8562d48af3f Mon Sep 17 00:00:00 2001 From: Weiming Date: Sat, 27 Jun 2026 11:15:59 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AE=A1=E6=89=B9=E5=B7=A5?= =?UTF-8?q?=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- deploy/feishu-approval.service | 2 +- project.config.json | 2 +- 飞书审批配置文件_V1.0.html | 116 ++++++++++++++++++++++++++++++--- 4 files changed, 110 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 1dd32c2..62c6e53 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ node server.js { "projectId": "approval_of_design", "port": 8080, - "host": "0.0.0.0", + "host": "127.0.0.1", "label": "飞书审批配置服务" } ``` diff --git a/deploy/feishu-approval.service b/deploy/feishu-approval.service index 9886a16..fd41052 100644 --- a/deploy/feishu-approval.service +++ b/deploy/feishu-approval.service @@ -8,7 +8,7 @@ User=root WorkingDirectory=/opt/feishu-approval Environment=NODE_ENV=production Environment=PORT=8080 -Environment=HOST=0.0.0.0 +Environment=HOST=127.0.0.1 ExecStart=/usr/bin/node server.js Restart=on-failure RestartSec=5 diff --git a/project.config.json b/project.config.json index cf24eab..62bbf69 100644 --- a/project.config.json +++ b/project.config.json @@ -1,6 +1,6 @@ { "projectId": "approval_of_design", "port": 8080, - "host": "0.0.0.0", + "host": "127.0.0.1", "label": "飞书审批配置服务" } diff --git a/飞书审批配置文件_V1.0.html b/飞书审批配置文件_V1.0.html index 60317da..8fecc6d 100644 --- a/飞书审批配置文件_V1.0.html +++ b/飞书审批配置文件_V1.0.html @@ -98,6 +98,16 @@ a{color:#3370ff;text-decoration:none} .widget-item{display:flex;align-items:center;gap:10px;padding:9px 10px;border-radius:var(--radius-sm);cursor:pointer;transition:all .15s;margin-bottom:2px;font-size:13px;color:var(--text)} .widget-item:hover{background:var(--primary-light);color:var(--primary)} .widget-item .icon{width:20px;height:20px;display:flex;align-items:center;justify-content:center;font-size:14px} +.widget-item.is-limited{position:relative;color:var(--text-caption);cursor:not-allowed} +.widget-item.is-limited:hover{background:var(--bg);color:var(--text-caption)} +.widget-item.is-limited::after{content:attr(data-limited-tip);position:absolute;left:10px;top:calc(100% + 4px);z-index:20;width:max-content;max-width:210px;padding:6px 8px;border-radius:var(--radius-sm);background:#1f2937;color:#fff;font-size:12px;line-height:1.4;box-shadow:var(--shadow);opacity:0;pointer-events:none;transform:translateY(-2px);transition:opacity .15s,transform .15s} +.widget-item.is-limited:hover::after{opacity:1;transform:translateY(0)} +.tip-dialog-mask{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;background:rgba(15,23,42,.35)} +.tip-dialog-mask.hidden{display:none} +.tip-dialog{width:320px;max-width:calc(100vw - 32px);padding:20px;border-radius:var(--radius);background:var(--surface);box-shadow:var(--shadow-lg)} +.tip-dialog-title{font-size:16px;font-weight:600;color:var(--text);margin-bottom:12px} +.tip-dialog-content{font-size:14px;line-height:1.6;color:var(--text-secondary);margin-bottom:20px} +.tip-dialog-actions{display:flex;justify-content:flex-end} .form-design-right{width:360px;background:var(--surface);border-left:1px solid var(--border);overflow-y:auto;flex-shrink:0} @@ -493,7 +503,7 @@ document.documentElement.setAttribute('data-app-mode', window.APP_MODE);
1
-
+
📄云文档
@@ -703,6 +713,7 @@ let startCcRecipients = []; let endCcRecipients = [{ id: 'end_cc_1', approverType: 'self', approverName: '', approverLevel: 1, roleName: '', userGroupName: '', formContactField: '', formDeptField: '' }]; let startFormPermissions = {}; let endFormPermissions = {}; +const CLOUD_DOC_LIMIT_MESSAGE = '表单中只能放置一个文档类型的字段'; const submitterTypeLabels = { all: '全员', dept: '指定部门', member: '指定成员' }; const approverTypeLabels = { superior: '上级', dept_head: '部门负责人', role: '角色', user_group: '用户组', member: '指定成员', self_select: '提交人自选', self: '提交人本人', form_contact: '表单内联系人', form_dept: '表单内部门', node_approver: '节点审批人', node_cc: '节点抄送人', multi_level_superior: '连续多级上级', multi_level_dept_head: '连续多级部门负责人' }; @@ -861,6 +872,71 @@ function getSelectedDetailParent() { return null; } +function hasFormFieldType(type) { + for (const f of formFields) { + if (f.type === type) return true; + if (f.detailChildren?.some(c => c.type === type)) return true; + } + return false; +} + +function canAddFormFieldType(type, showMessage) { + if (type === 'cloud_doc' && hasFormFieldType('cloud_doc')) { + if (showMessage) showTipDialog(CLOUD_DOC_LIMIT_MESSAGE); + return false; + } + return true; +} + +function showTipDialog(message) { + let mask = document.getElementById('tip-dialog-mask'); + if (!mask) { + mask = document.createElement('div'); + mask.id = 'tip-dialog-mask'; + mask.className = 'tip-dialog-mask hidden'; + mask.setAttribute('role', 'dialog'); + mask.setAttribute('aria-modal', 'true'); + mask.onclick = e => { + if (e.target === mask) closeTipDialog(); + }; + mask.innerHTML = ` +
+
提示:
+
+
+
`; + document.body.appendChild(mask); + } + const content = document.getElementById('tip-dialog-content'); + if (content) content.textContent = message; + mask.classList.remove('hidden'); + mask.querySelector('button')?.focus(); +} + +function closeTipDialog() { + document.getElementById('tip-dialog-mask')?.classList.add('hidden'); +} + +function syncCloudDocWidgetState() { + const el = document.getElementById('widget-cloud-doc'); + if (!el) return; + const limited = hasFormFieldType('cloud_doc'); + el.classList.toggle('is-limited', limited); + el.draggable = !limited; + if (limited) { + el.setAttribute('title', CLOUD_DOC_LIMIT_MESSAGE); + el.setAttribute('data-limited-tip', CLOUD_DOC_LIMIT_MESSAGE); + } else { + el.removeAttribute('title'); + el.removeAttribute('data-limited-tip'); + } +} + +function handleCloudDocWidgetClick() { + if (!canAddFormFieldType('cloud_doc', true)) return; + addFormField('cloud_doc'); +} + function syncHeaderName() { const name = document.getElementById('approval-name')?.value || '审批配置'; const header = document.getElementById('header-approval-name'); @@ -1328,7 +1404,7 @@ function renderOtherOptionsSection(field) { } let printExtra = ''; if (field.type === 'attachment' && field.printable) { - printExtra = '
打印附件中的图片
'; + printExtra = ``; } return `
@@ -1425,6 +1501,7 @@ function createFormFieldData(type) { if (type === 'single_choice') field.linkage = { enabled: false, targetId: '', mappings: {} }; if (type === 'bitable') { field.bitableTitle = ''; field.tableName = ''; field.refFields = []; } if (type === 'image') { field.allowImage = true; field.allowVideo = true; field.mobileOnlyCapture = false; } + if (type === 'attachment') field.printAttachmentImages = false; if (type === 'department') { field.deptSelectionMode = 'single'; field.deptDisplayMode = 'leaf_only'; } if (type === 'contact' || type === 'member') { field.contactSelectionMode = 'single'; field.allowSelectSelf = true; } if (type === 'related_approval') { field.scopeConfig = ''; field.onlyApproved = false; } @@ -1480,6 +1557,7 @@ function normalizeFormField(field) { if (field.allowVideo === undefined) field.allowVideo = true; if (field.mobileOnlyCapture === undefined) field.mobileOnlyCapture = false; } + if (field.type === 'attachment' && field.printAttachmentImages === undefined) field.printAttachmentImages = false; if (field.type === 'department') { if (!field.deptSelectionMode) field.deptSelectionMode = 'single'; if (!field.deptDisplayMode) field.deptDisplayMode = 'leaf_only'; @@ -1507,6 +1585,7 @@ function normalizeFormField(field) { } function addFormField(type, index) { + if (!canAddFormFieldType(type, true)) return; if (type === 'detail') { const field = createFormFieldData(type); if (!field) return; @@ -1538,6 +1617,7 @@ function addFormField(type, index) { function addFormFieldToDetail(detailId, type) { if (type === 'detail' || !fieldMeta[type]) return; + if (!canAddFormFieldType(type, true)) return; const detail = formFields.find(f => f.id === detailId && f.type === 'detail'); if (!detail) return; if (!detail.detailChildren) detail.detailChildren = []; @@ -1551,6 +1631,11 @@ function addFormFieldToDetail(detailId, type) { } function dragWidget(ev, type) { + if (!canAddFormFieldType(type, true)) { + ev.preventDefault(); + ev.stopPropagation(); + return; + } ev.dataTransfer.setData('widgetType', type); } function allowDrop(ev) { ev.preventDefault(); } @@ -1626,6 +1711,7 @@ function commitFieldName(id, key) { function renderFormFields() { const list = document.getElementById('field-list'); if (!list) return; + syncCloudDocWidgetState(); if (formFields.length === 0) { list.innerHTML = FIELD_EMPTY_TIP_HTML; return; @@ -2331,6 +2417,7 @@ function updateField(id, key, val) { const f = findFormField(id); if (!f) return; f[key] = val; + if (key === 'printable' && f.type === 'attachment' && !val) f.printAttachmentImages = false; const liveTextKeys = ['title', 'startTitle', 'endTitle', 'durationTitle', 'placeholder', 'defaultValue', 'unit', 'minValue', 'maxValue', 'formula', 'bitableTitle', 'tableName', 'scopeConfig', 'archiveLocation', 'customDateStart', 'customDateEnd']; if (liveTextKeys.includes(key)) { if (['title', 'startTitle', 'endTitle', 'durationTitle'].includes(key)) { @@ -2346,7 +2433,7 @@ function updateField(id, key, val) { autoSave(); return; } - const configOnlyKeys = ['allowSelectSelf', 'deptDisplayMode', 'deptSelectionMode', 'contactSelectionMode', 'onlyApproved', 'autoLocate', 'showDetailAddress', 'fillDetailAddress', 'locationDisplayMode', 'validateBranchRequired', 'phoneType', 'allowImage', 'allowVideo', 'mobileOnlyCapture', 'printable', 'required', 'allowEditDuration', 'dateFormat', 'dateRangeOption']; + const configOnlyKeys = ['allowSelectSelf', 'deptDisplayMode', 'deptSelectionMode', 'contactSelectionMode', 'onlyApproved', 'autoLocate', 'showDetailAddress', 'fillDetailAddress', 'locationDisplayMode', 'validateBranchRequired', 'phoneType', 'allowImage', 'allowVideo', 'mobileOnlyCapture', 'printable', 'printAttachmentImages', 'required', 'allowEditDuration', 'dateFormat', 'dateRangeOption']; if (configOnlyKeys.includes(key)) { if (key === 'deptSelectionMode' || key === 'contactSelectionMode') renderFlowConfig(); renderFormConfig(); @@ -3276,6 +3363,21 @@ function renderEmptyHandlerPanel(node) {
`; } +function renderMultiPersonModeSection(node, title, oneLabel, allLabel, extraOptions) { + ensureNodeApprovers(node); + if (node.approvers.length <= 1) return ''; + const extraHtml = (extraOptions || []).map(opt => + `` + ).join(''); + return `
${title}
+ +
`; +} + function renderNodeCcPanel(node) { ensureNodeApprovers(node); if (!node.ccRecipients) node.ccRecipients = []; @@ -3833,12 +3935,7 @@ function renderApproverConfig(node) { ${renderNodePersonsPanel(node, '审批人', '+ 添加审批人')} ${renderEmptyApproverPanel(node)} ${renderSameAsSubmitterPanel(node)} -
多人审批方式
- -
+ ${renderMultiPersonModeSection(node, '多人审批时采用的审批方式', '或签(一名审批人同意即可)', '会签(须所有审批人同意)')}
抄送人设置
${renderNodeCcPanel(node)}
`; @@ -3873,6 +3970,7 @@ function renderHandlerConfig(node) { body = `
${renderNodePersonsPanel(node, '办理人', '+ 添加办理人')} ${renderEmptyHandlerPanel(node)} + ${renderMultiPersonModeSection(node, '多人办理时采用的办理方式', '或签(一名办理人处理即可)', '会签(须所有办理人处理)', [{ value: 'sequential', label: '依次办理(按顺序依次提交)' }])}

提示:

办理人不涉及审批人去重设置,不同节点相同的办理人仍需要执行。

若办理人离职,会自动转交给办理人的上级代为处理。

`; } else if (selectedFlowTab === 'formPerm') {