@robot-admin/naive-ui-components 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +257 -0
- package/dist/C_ActionBar-DWN-woTc.css.map +1 -0
- package/dist/C_ActionBar.cjs +5 -0
- package/dist/C_ActionBar.d.cts +2 -0
- package/dist/C_ActionBar.d.ts +2 -0
- package/dist/C_ActionBar.js +4 -0
- package/dist/C_ActionBar2.js +196 -0
- package/dist/C_ActionBar2.js.map +1 -0
- package/dist/C_AntV-AFKyK6hH.css.map +1 -0
- package/dist/C_AntV.cjs +8 -0
- package/dist/C_AntV.d.cts +2 -0
- package/dist/C_AntV.d.ts +2 -0
- package/dist/C_AntV.js +4 -0
- package/dist/C_AntV2.js +3150 -0
- package/dist/C_AntV2.js.map +1 -0
- package/dist/C_Barcode-P_EFj8dC.css.map +1 -0
- package/dist/C_Barcode.cjs +4 -0
- package/dist/C_Barcode.d.cts +2 -0
- package/dist/C_Barcode.d.ts +2 -0
- package/dist/C_Barcode.js +3 -0
- package/dist/C_Barcode2.js +68 -0
- package/dist/C_Barcode2.js.map +1 -0
- package/dist/C_Captcha-C-ef41xw.css.map +1 -0
- package/dist/C_Captcha.cjs +4 -0
- package/dist/C_Captcha.d.cts +2 -0
- package/dist/C_Captcha.d.ts +2 -0
- package/dist/C_Captcha.js +3 -0
- package/dist/C_Captcha2.js +155 -0
- package/dist/C_Captcha2.js.map +1 -0
- package/dist/C_Cascade-D9kNsjsV.css.map +1 -0
- package/dist/C_Cascade.cjs +4 -0
- package/dist/C_Cascade.d.cts +2 -0
- package/dist/C_Cascade.d.ts +2 -0
- package/dist/C_Cascade.js +3 -0
- package/dist/C_Cascade2.js +103 -0
- package/dist/C_Cascade2.js.map +1 -0
- package/dist/C_City-BCQ4ipiK.css.map +1 -0
- package/dist/C_City.cjs +4 -0
- package/dist/C_City.d.cts +2 -0
- package/dist/C_City.d.ts +2 -0
- package/dist/C_City.js +3 -0
- package/dist/C_City2.js +841 -0
- package/dist/C_City2.js.map +1 -0
- package/dist/C_Code-C9kvvEmO.css.map +1 -0
- package/dist/C_Code.cjs +5 -0
- package/dist/C_Code.d.cts +2 -0
- package/dist/C_Code.d.ts +2 -0
- package/dist/C_Code.js +4 -0
- package/dist/C_Code2.js +346 -0
- package/dist/C_Code2.js.map +1 -0
- package/dist/C_CollapsePanel-BUJHuYcU.css.map +1 -0
- package/dist/C_CollapsePanel.cjs +6 -0
- package/dist/C_CollapsePanel.d.cts +2 -0
- package/dist/C_CollapsePanel.d.ts +2 -0
- package/dist/C_CollapsePanel.js +4 -0
- package/dist/C_CollapsePanel2.js +319 -0
- package/dist/C_CollapsePanel2.js.map +1 -0
- package/dist/C_Cron-yx2Ob4Jl.css.map +1 -0
- package/dist/C_Cron.cjs +15 -0
- package/dist/C_Cron.d.cts +2 -0
- package/dist/C_Cron.d.ts +2 -0
- package/dist/C_Cron.js +4 -0
- package/dist/C_Cron2.js +1209 -0
- package/dist/C_Cron2.js.map +1 -0
- package/dist/C_Date.cjs +4 -0
- package/dist/C_Date.d.cts +2 -0
- package/dist/C_Date.d.ts +2 -0
- package/dist/C_Date.js +3 -0
- package/dist/C_Date2.js +219 -0
- package/dist/C_Date2.js.map +1 -0
- package/dist/C_Draggable-C483syRC.css.map +1 -0
- package/dist/C_Draggable.cjs +5 -0
- package/dist/C_Draggable.d.cts +2 -0
- package/dist/C_Draggable.d.ts +2 -0
- package/dist/C_Draggable.js +3 -0
- package/dist/C_Draggable2.js +295 -0
- package/dist/C_Draggable2.js.map +1 -0
- package/dist/C_Editor-Bp0SyIEw.css.map +1 -0
- package/dist/C_Editor.cjs +4 -0
- package/dist/C_Editor.d.cts +2 -0
- package/dist/C_Editor.d.ts +2 -0
- package/dist/C_Editor.js +3 -0
- package/dist/C_Editor2.js +160 -0
- package/dist/C_Editor2.js.map +1 -0
- package/dist/C_FilePreview-CPqvhoCy.css.map +1 -0
- package/dist/C_FilePreview.cjs +6 -0
- package/dist/C_FilePreview.d.cts +2 -0
- package/dist/C_FilePreview.d.ts +2 -0
- package/dist/C_FilePreview.js +3 -0
- package/dist/C_FilePreview2.js +1031 -0
- package/dist/C_FilePreview2.js.map +1 -0
- package/dist/C_Form-Jx7PY3sT.css.map +1 -0
- package/dist/C_Form.cjs +15 -0
- package/dist/C_Form.d.cts +2 -0
- package/dist/C_Form.d.ts +2 -0
- package/dist/C_Form.js +4 -0
- package/dist/C_Form2.js +2510 -0
- package/dist/C_Form2.js.map +1 -0
- package/dist/C_FormSearch-DvRgxlRn.css.map +1 -0
- package/dist/C_FormSearch.cjs +6 -0
- package/dist/C_FormSearch.d.cts +2 -0
- package/dist/C_FormSearch.d.ts +2 -0
- package/dist/C_FormSearch.js +3 -0
- package/dist/C_FormSearch2.js +356 -0
- package/dist/C_FormSearch2.js.map +1 -0
- package/dist/C_FormulaEditor-DtGkt4T_.css.map +1 -0
- package/dist/C_FormulaEditor.cjs +13 -0
- package/dist/C_FormulaEditor.d.cts +2 -0
- package/dist/C_FormulaEditor.d.ts +2 -0
- package/dist/C_FormulaEditor.js +4 -0
- package/dist/C_FormulaEditor2.js +1433 -0
- package/dist/C_FormulaEditor2.js.map +1 -0
- package/dist/C_FullCalendar-BF7H0YIx.css.map +1 -0
- package/dist/C_FullCalendar.cjs +9 -0
- package/dist/C_FullCalendar.d.cts +2 -0
- package/dist/C_FullCalendar.d.ts +2 -0
- package/dist/C_FullCalendar.js +3 -0
- package/dist/C_FullCalendar2.js +377 -0
- package/dist/C_FullCalendar2.js.map +1 -0
- package/dist/C_Guide.cjs +4 -0
- package/dist/C_Guide.d.cts +2 -0
- package/dist/C_Guide.d.ts +2 -0
- package/dist/C_Guide.js +3 -0
- package/dist/C_Guide2.js +58 -0
- package/dist/C_Guide2.js.map +1 -0
- package/dist/C_Icon.cjs +4 -0
- package/dist/C_Icon.d.cts +2 -0
- package/dist/C_Icon.d.ts +2 -0
- package/dist/C_Icon.js +3 -0
- package/dist/C_Icon2.js +286 -0
- package/dist/C_Icon2.js.map +1 -0
- package/dist/C_ImageCropper-BVJfUufl.css.map +1 -0
- package/dist/C_ImageCropper.cjs +6 -0
- package/dist/C_ImageCropper.d.cts +2 -0
- package/dist/C_ImageCropper.d.ts +2 -0
- package/dist/C_ImageCropper.js +4 -0
- package/dist/C_ImageCropper2.js +723 -0
- package/dist/C_ImageCropper2.js.map +1 -0
- package/dist/C_Language.cjs +4 -0
- package/dist/C_Language.d.cts +2 -0
- package/dist/C_Language.d.ts +2 -0
- package/dist/C_Language.js +3 -0
- package/dist/C_Language2.js +72 -0
- package/dist/C_Language2.js.map +1 -0
- package/dist/C_Map-DpzeuWdX.css.map +1 -0
- package/dist/C_Map.cjs +7 -0
- package/dist/C_Map.d.cts +2 -0
- package/dist/C_Map.d.ts +2 -0
- package/dist/C_Map.js +3 -0
- package/dist/C_Map2.js +199 -0
- package/dist/C_Map2.js.map +1 -0
- package/dist/C_Markdown-BEjxknqd.css.map +1 -0
- package/dist/C_Markdown.cjs +4 -0
- package/dist/C_Markdown.d.cts +2 -0
- package/dist/C_Markdown.d.ts +2 -0
- package/dist/C_Markdown.js +3 -0
- package/dist/C_Markdown2.js +186 -0
- package/dist/C_Markdown2.js.map +1 -0
- package/dist/C_NotificationCenter-0l3TY2Gn.css.map +1 -0
- package/dist/C_NotificationCenter.cjs +20 -0
- package/dist/C_NotificationCenter.d.cts +2 -0
- package/dist/C_NotificationCenter.d.ts +2 -0
- package/dist/C_NotificationCenter.js +4 -0
- package/dist/C_NotificationCenter2.js +1383 -0
- package/dist/C_NotificationCenter2.js.map +1 -0
- package/dist/C_Progress.cjs +4 -0
- package/dist/C_Progress.d.cts +2 -0
- package/dist/C_Progress.d.ts +2 -0
- package/dist/C_Progress.js +3 -0
- package/dist/C_Progress2.js +103 -0
- package/dist/C_Progress2.js.map +1 -0
- package/dist/C_QRCode-DbdiAIPg.css.map +1 -0
- package/dist/C_QRCode.cjs +5 -0
- package/dist/C_QRCode.d.cts +2 -0
- package/dist/C_QRCode.d.ts +2 -0
- package/dist/C_QRCode.js +3 -0
- package/dist/C_QRCode2.js +218 -0
- package/dist/C_QRCode2.js.map +1 -0
- package/dist/C_Signature-zhHCbra9.css.map +1 -0
- package/dist/C_Signature.cjs +8 -0
- package/dist/C_Signature.d.cts +2 -0
- package/dist/C_Signature.d.ts +2 -0
- package/dist/C_Signature.js +4 -0
- package/dist/C_Signature2.js +618 -0
- package/dist/C_Signature2.js.map +1 -0
- package/dist/C_SplitPane-C6sBsfKY.css.map +1 -0
- package/dist/C_SplitPane.cjs +6 -0
- package/dist/C_SplitPane.d.cts +2 -0
- package/dist/C_SplitPane.d.ts +2 -0
- package/dist/C_SplitPane.js +4 -0
- package/dist/C_SplitPane2.js +356 -0
- package/dist/C_SplitPane2.js.map +1 -0
- package/dist/C_Steps-CODHN5Hs.css.map +1 -0
- package/dist/C_Steps.cjs +4 -0
- package/dist/C_Steps.d.cts +2 -0
- package/dist/C_Steps.d.ts +2 -0
- package/dist/C_Steps.js +3 -0
- package/dist/C_Steps2.js +82 -0
- package/dist/C_Steps2.js.map +1 -0
- package/dist/C_Table-DSNsntmT.css.map +1 -0
- package/dist/C_Table.cjs +19 -0
- package/dist/C_Table.d.cts +2 -0
- package/dist/C_Table.d.ts +2 -0
- package/dist/C_Table.js +5 -0
- package/dist/C_Table2.js +3009 -0
- package/dist/C_Table2.js.map +1 -0
- package/dist/C_Theme.cjs +4 -0
- package/dist/C_Theme.d.cts +2 -0
- package/dist/C_Theme.d.ts +2 -0
- package/dist/C_Theme.js +3 -0
- package/dist/C_Theme2.js +60 -0
- package/dist/C_Theme2.js.map +1 -0
- package/dist/C_Time-BvZLYraL.css.map +1 -0
- package/dist/C_Time.cjs +5 -0
- package/dist/C_Time.d.cts +2 -0
- package/dist/C_Time.d.ts +2 -0
- package/dist/C_Time.js +3 -0
- package/dist/C_Time2.js +199 -0
- package/dist/C_Time2.js.map +1 -0
- package/dist/C_Tree-0GDv--jX.css.map +1 -0
- package/dist/C_Tree.cjs +7 -0
- package/dist/C_Tree.d.cts +2 -0
- package/dist/C_Tree.d.ts +2 -0
- package/dist/C_Tree.js +4 -0
- package/dist/C_Tree2.js +441 -0
- package/dist/C_Tree2.js.map +1 -0
- package/dist/C_Upload-BXd3YYLx.css.map +1 -0
- package/dist/C_Upload.cjs +12 -0
- package/dist/C_Upload.d.cts +2 -0
- package/dist/C_Upload.d.ts +2 -0
- package/dist/C_Upload.js +4 -0
- package/dist/C_Upload2.js +1388 -0
- package/dist/C_Upload2.js.map +1 -0
- package/dist/C_VideoPlayer-DYG3RL0Q.css.map +1 -0
- package/dist/C_VideoPlayer.cjs +23 -0
- package/dist/C_VideoPlayer.d.cts +2 -0
- package/dist/C_VideoPlayer.d.ts +2 -0
- package/dist/C_VideoPlayer.js +3 -0
- package/dist/C_VideoPlayer2.js +1932 -0
- package/dist/C_VideoPlayer2.js.map +1 -0
- package/dist/C_VtableGantt-fhItIiHE.css.map +1 -0
- package/dist/C_VtableGantt.cjs +6 -0
- package/dist/C_VtableGantt.d.cts +2 -0
- package/dist/C_VtableGantt.d.ts +2 -0
- package/dist/C_VtableGantt.js +4 -0
- package/dist/C_VtableGantt2.js +873 -0
- package/dist/C_VtableGantt2.js.map +1 -0
- package/dist/C_WaterFall-8sQDFXKg.css.map +1 -0
- package/dist/C_WaterFall.cjs +13 -0
- package/dist/C_WaterFall.d.cts +2 -0
- package/dist/C_WaterFall.d.ts +2 -0
- package/dist/C_WaterFall.js +3 -0
- package/dist/C_WaterFall2.js +365 -0
- package/dist/C_WaterFall2.js.map +1 -0
- package/dist/C_WorkFlow-J-dyIuh9.css.map +1 -0
- package/dist/C_WorkFlow.cjs +8 -0
- package/dist/C_WorkFlow.d.cts +2 -0
- package/dist/C_WorkFlow.d.ts +2 -0
- package/dist/C_WorkFlow.js +4 -0
- package/dist/C_WorkFlow2.js +1984 -0
- package/dist/C_WorkFlow2.js.map +1 -0
- package/dist/chunk.js +22 -0
- package/dist/city.js +4817 -0
- package/dist/city.js.map +1 -0
- package/dist/constants.d.ts +273 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants2.d.ts +178 -0
- package/dist/constants2.d.ts.map +1 -0
- package/dist/constants3.d.ts +475 -0
- package/dist/constants3.d.ts.map +1 -0
- package/dist/constants4.d.ts +430 -0
- package/dist/constants4.d.ts.map +1 -0
- package/dist/constants5.d.ts +4283 -0
- package/dist/constants5.d.ts.map +1 -0
- package/dist/data.d.ts +67 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/export-helper.js +9 -0
- package/dist/index.cjs +409 -0
- package/dist/index.d.cts +96 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +230 -0
- package/dist/index.js.map +1 -0
- package/dist/index.vue.d.ts +80 -0
- package/dist/index.vue.d.ts.map +1 -0
- package/dist/index10.vue.d.ts +72 -0
- package/dist/index10.vue.d.ts.map +1 -0
- package/dist/index11.vue.d.ts +26 -0
- package/dist/index11.vue.d.ts.map +1 -0
- package/dist/index12.vue.d.ts +81 -0
- package/dist/index12.vue.d.ts.map +1 -0
- package/dist/index13.vue.d.ts +55 -0
- package/dist/index13.vue.d.ts.map +1 -0
- package/dist/index14.vue.d.ts +33 -0
- package/dist/index14.vue.d.ts.map +1 -0
- package/dist/index15.vue.d.ts +18 -0
- package/dist/index15.vue.d.ts.map +1 -0
- package/dist/index16.vue.d.ts +662 -0
- package/dist/index16.vue.d.ts.map +1 -0
- package/dist/index2.vue.d.ts +38 -0
- package/dist/index2.vue.d.ts.map +1 -0
- package/dist/index3.vue.d.ts +45 -0
- package/dist/index3.vue.d.ts.map +1 -0
- package/dist/index4.vue.d.ts +31 -0
- package/dist/index4.vue.d.ts.map +1 -0
- package/dist/index5.vue.d.ts +35 -0
- package/dist/index5.vue.d.ts.map +1 -0
- package/dist/index6.vue.d.ts +48 -0
- package/dist/index6.vue.d.ts.map +1 -0
- package/dist/index7.vue.d.ts +56 -0
- package/dist/index7.vue.d.ts.map +1 -0
- package/dist/index8.vue.d.ts +41 -0
- package/dist/index8.vue.d.ts.map +1 -0
- package/dist/index9.vue.d.ts +30 -0
- package/dist/index9.vue.d.ts.map +1 -0
- package/dist/storage.js +31 -0
- package/dist/storage.js.map +1 -0
- package/dist/style.css +7725 -0
- package/dist/useCalendarEvents.d.ts +148 -0
- package/dist/useCalendarEvents.d.ts.map +1 -0
- package/dist/useCollapsePanel.d.ts +132 -0
- package/dist/useCollapsePanel.d.ts.map +1 -0
- package/dist/useCropperCore.d.ts +102 -0
- package/dist/useCropperCore.d.ts.map +1 -0
- package/dist/useDraggableLayout.d.ts +194 -0
- package/dist/useDraggableLayout.d.ts.map +1 -0
- package/dist/useDynamicFormState.d.ts +4248 -0
- package/dist/useDynamicFormState.d.ts.map +1 -0
- package/dist/useEdgeInteraction.d.ts +7614 -0
- package/dist/useEdgeInteraction.d.ts.map +1 -0
- package/dist/useFullscreen.d.ts +166 -0
- package/dist/useFullscreen.d.ts.map +1 -0
- package/dist/useInfiniteScroll.d.ts +169 -0
- package/dist/useInfiniteScroll.d.ts.map +1 -0
- package/dist/useModalEdit.d.ts +960 -0
- package/dist/useModalEdit.d.ts.map +1 -0
- package/dist/useQRCode.d.ts +87 -0
- package/dist/useQRCode.d.ts.map +1 -0
- package/dist/useSearchState.d.ts +180 -0
- package/dist/useSearchState.d.ts.map +1 -0
- package/dist/useSignatureHistory.d.ts +189 -0
- package/dist/useSignatureHistory.d.ts.map +1 -0
- package/dist/useSplitResize.d.ts +158 -0
- package/dist/useSplitResize.d.ts.map +1 -0
- package/dist/useTimeSelection.d.ts +105 -0
- package/dist/useTimeSelection.d.ts.map +1 -0
- package/dist/useTreeOperations.d.ts +183 -0
- package/dist/useTreeOperations.d.ts.map +1 -0
- package/dist/useWorkflowValidation.d.ts +1052 -0
- package/dist/useWorkflowValidation.d.ts.map +1 -0
- package/package.json +342 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"C_WorkFlow2.js","names":["data","data","data","data","StartNode","ApprovalNode","CopyNode","ConditionNode","users","departments"],"sources":["../src/components/C_WorkFlow/data.ts","../src/components/C_WorkFlow/NodeConfigModal.vue","../src/components/C_WorkFlow/NodeConfigModal.vue","../src/components/C_WorkFlow/NodeConfigModal.vue","../src/components/C_WorkFlow/nodes/StartNode.vue","../src/components/C_WorkFlow/nodes/StartNode.vue","../src/components/C_WorkFlow/nodes/StartNode.vue","../src/components/C_WorkFlow/nodes/ApprovalNode.vue","../src/components/C_WorkFlow/nodes/ApprovalNode.vue","../src/components/C_WorkFlow/nodes/ApprovalNode.vue","../src/components/C_WorkFlow/nodes/CopyNode.vue","../src/components/C_WorkFlow/nodes/CopyNode.vue","../src/components/C_WorkFlow/nodes/CopyNode.vue","../src/components/C_WorkFlow/nodes/ConditionNode.vue","../src/components/C_WorkFlow/nodes/ConditionNode.vue","../src/components/C_WorkFlow/nodes/ConditionNode.vue","../src/components/C_WorkFlow/composables/useWorkflowNodes.ts","../src/components/C_WorkFlow/composables/useWorkflowValidation.ts","../src/components/C_WorkFlow/composables/useWorkflowPreview.ts","../src/components/C_WorkFlow/index.vue","../src/components/C_WorkFlow/index.vue","../src/components/C_WorkFlow/index.vue"],"sourcesContent":["/*\r\n * @Description: 工作(审批流)流组件 - 数据层\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n */\r\nimport type { NodeType, WorkflowNode } from \"./types\";\r\n\r\n/* 节点类型选项 */\r\nexport const NODE_TYPE_OPTIONS = [\r\n {\r\n type: \"approval\" as NodeType,\r\n label: \"审批人\",\r\n icon: \"mdi:account-check\",\r\n iconClass: \"approval-icon\",\r\n },\r\n {\r\n type: \"copy\" as NodeType,\r\n label: \"抄送人\",\r\n icon: \"mdi:email-outline\",\r\n iconClass: \"copy-icon\",\r\n },\r\n {\r\n type: \"condition\" as NodeType,\r\n label: \"条件分支\",\r\n icon: \"mdi:source-branch\",\r\n iconClass: \"condition-icon\",\r\n },\r\n];\r\n\r\n/* 审批模式选项 */\r\nexport const APPROVAL_MODES = [\r\n { value: \"any\", label: \"或签\", desc: \"任意一人同意即可通过\" },\r\n { value: \"all\", label: \"会签\", desc: \"所有人都同意才能通过\" },\r\n { value: \"sequence\", label: \"顺序审批\", desc: \"按选择顺序依次审批\" },\r\n];\r\n\r\n/* 字段选项 */\r\nexport const FIELD_OPTIONS = [\r\n { label: \"申请金额\", value: \"amount\" },\r\n { label: \"申请人部门\", value: \"department\" },\r\n { label: \"申请类型\", value: \"type\" },\r\n { label: \"紧急程度\", value: \"priority\" },\r\n];\r\n\r\n/* 操作符选项 */\r\nexport const OPERATOR_OPTIONS = [\r\n { label: \"等于\", value: \"equals\" },\r\n { label: \"大于\", value: \"greater_than\" },\r\n { label: \"小于\", value: \"less_than\" },\r\n { label: \"包含\", value: \"contains\" },\r\n { label: \"不等于\", value: \"not_equals\" },\r\n];\r\n\r\n/* 节点标题映射 */\r\nexport const NODE_TITLES: Record<NodeType, string> = {\r\n start: \"发起人\",\r\n approval: \"审批人\",\r\n copy: \"抄送人\",\r\n condition: \"条件分支\",\r\n};\r\n\r\n/* 配置标题映射 */\r\nexport const CONFIG_TITLES = {\r\n approval: \"审批人设置\",\r\n copy: \"抄送人设置\",\r\n condition: \"条件分支设置\",\r\n};\r\n\r\n/* 字段显示名称映射 */\r\nexport const FIELD_DISPLAY_NAMES: Record<string, string> = {\r\n approvers: \"审批人\",\r\n conditions: \"条件配置\",\r\n connection: \"节点连接\",\r\n copyUsers: \"抄送人\",\r\n};\r\n\r\n/* 错误类型文本映射 */\r\nexport const ERROR_TYPE_TEXTS: Record<string, string> = {\r\n required: \"必填\",\r\n incomplete: \"不完整\",\r\n warning: \"警告\",\r\n error: \"错误\",\r\n};\r\n\r\n/** 节点之间的 Y 轴间距(px) */\r\nexport const NODE_Y_GAP = 180;\r\n\r\n/* 初始节点数据 */\r\nexport const INITIAL_NODE: WorkflowNode = {\r\n id: \"start-1\",\r\n type: \"start\",\r\n position: { x: 150, y: 100 },\r\n data: { title: \"发起人\", status: \"active\", initiators: [] },\r\n};\r\n\r\n/* 默认头像生成函数 */\r\nexport const getDefaultAvatar = (name: string): string =>\r\n `https://ui-avatars.com/api/?name=${encodeURIComponent(name)}&background=random`;\r\n\r\n/* 生成条件ID的函数 */\r\nexport const generateConditionId = (): string => `condition-${Date.now()}`;\r\n\r\n/* 生成边ID的函数 */\r\nexport const generateEdgeId = (sourceId: string, targetId: string): string =>\r\n `edge-${sourceId}-${targetId}`;\r\n\r\n/* 默认条件对象工厂函数 */\r\nexport const createDefaultCondition = () => ({\r\n id: generateConditionId(),\r\n name: \"\",\r\n field: \"\",\r\n operator: \"equals\" as const,\r\n value: \"\",\r\n});\r\n","<!--\r\n * @Description: 节点配置弹窗组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <NModal\r\n v-model:show=\"visible\"\r\n style=\"width: 900px\"\r\n :mask-closable=\"false\"\r\n preset=\"dialog\"\r\n :title=\"configTitle\"\r\n positive-text=\"确定\"\r\n negative-text=\"取消\"\r\n :loading=\"configLoading\"\r\n @positive-click=\"saveNodeConfig\"\r\n @negative-click=\"handleCancel\"\r\n >\r\n <!-- 公共选择面板 -->\r\n <template v-if=\"isUserSelectNode\">\r\n <div class=\"max-h-60vh overflow-y-auto\">\r\n <div class=\"config-section\">\r\n <h4\r\n class=\"flex items-center gap-2 mb-4 text-base font-semibold\"\r\n :style=\"{ color: 'var(--n-text-color-1)' }\"\r\n >\r\n <C_Icon\r\n :name=\"userSelectConfig.icon\"\r\n :size=\"16\"\r\n v-if=\"userSelectConfig.icon\"\r\n />\r\n {{ userSelectConfig.title }}\r\n </h4>\r\n\r\n <NInput\r\n v-model:value=\"searchKeyword\"\r\n placeholder=\"搜索用户姓名或部门\"\r\n clearable\r\n class=\"mb-4\"\r\n >\r\n <template #prefix>\r\n <C_Icon name=\"mdi:magnify\" :size=\"16\" />\r\n </template>\r\n </NInput>\r\n\r\n <div\r\n class=\"border rounded-lg p-3 mb-4 max-h-50 overflow-y-auto\"\r\n :style=\"{\r\n borderColor: 'var(--n-border-color)',\r\n background: 'var(--n-color-embedded)',\r\n }\"\r\n >\r\n <NTree\r\n :data=\"departmentUserTree\"\r\n :checked-keys=\"userSelectConfig.checkedKeys\"\r\n :selectable=\"false\"\r\n checkable\r\n cascade\r\n :virtual-scroll=\"true\"\r\n style=\"max-height: 300px\"\r\n @update:checked-keys=\"userSelectConfig.onSelect\"\r\n />\r\n </div>\r\n\r\n <div\r\n v-if=\"userSelectConfig.selectedUsers.length > 0\"\r\n class=\"rounded-lg p-4 mb-4 border\"\r\n :style=\"{\r\n borderColor: 'var(--n-border-color)',\r\n background: 'var(--n-color-embedded)',\r\n }\"\r\n >\r\n <h5\r\n class=\"mb-2 text-sm font-medium\"\r\n :style=\"{ color: 'var(--n-text-color-1)' }\"\r\n >\r\n {{ userSelectConfig.selectedLabel }}\r\n ({{ userSelectConfig.selectedUsers.length }})\r\n </h5>\r\n <div class=\"flex flex-wrap gap-3\">\r\n <NTag\r\n v-for=\"user in userSelectConfig.selectedUsers\"\r\n :key=\"user.id\"\r\n closable\r\n :type=\"userSelectConfig.tagType\"\r\n @close=\"userSelectConfig.onRemove(user.id)\"\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <NAvatar\r\n :src=\"user.avatar\"\r\n :fallback-src=\"getDefaultAvatar(user.name)\"\r\n size=\"small\"\r\n />\r\n <span class=\"font-medium text-sm\">{{ user.name }}</span>\r\n <span\r\n class=\"text-xs px-1.5 py-0.5 rounded\"\r\n :style=\"{\r\n color: 'var(--n-text-color-3)',\r\n background: 'var(--n-color-embedded)',\r\n }\"\r\n >{{ user.department }}</span\r\n >\r\n </div>\r\n </NTag>\r\n </div>\r\n </div>\r\n </div>\r\n <!-- 审批节点时附带审批模式 -->\r\n <template v-if=\"props.currentNode?.type === 'approval'\">\r\n <div class=\"mt-4\">\r\n <h5\r\n class=\"mb-3 text-sm font-medium\"\r\n :style=\"{ color: 'var(--n-text-color-1)' }\"\r\n >\r\n 审批模式\r\n </h5>\r\n <NRadioGroup v-model:value=\"approvalMode\">\r\n <NSpace vertical>\r\n <NRadio\r\n v-for=\"mode in APPROVAL_MODES\"\r\n :key=\"mode.value\"\r\n :value=\"mode.value\"\r\n >\r\n <div class=\"flex flex-col gap-1\">\r\n <strong class=\"text-sm\">{{ mode.label }}</strong>\r\n <span\r\n class=\"text-xs\"\r\n :style=\"{ color: 'var(--n-text-color-3)' }\"\r\n >{{ mode.desc }}</span\r\n >\r\n </div>\r\n </NRadio>\r\n </NSpace>\r\n </NRadioGroup>\r\n </div>\r\n </template>\r\n </div>\r\n </template>\r\n\r\n <!-- 条件节点配置 -->\r\n <div\r\n v-else-if=\"props.currentNode?.type === 'condition'\"\r\n class=\"max-h-60vh overflow-y-auto\"\r\n >\r\n <div class=\"config-section\">\r\n <h4\r\n class=\"flex items-center gap-2 mb-4 text-base font-semibold\"\r\n :style=\"{ color: 'var(--n-text-color-1)' }\"\r\n >\r\n <C_Icon name=\"mdi:source-branch\" :size=\"16\" />\r\n 条件分支设置\r\n </h4>\r\n <div class=\"space-y-3\">\r\n <div\r\n v-for=\"(condition, index) in conditions\"\r\n :key=\"condition.id\"\r\n class=\"condition-item\"\r\n >\r\n <NCard\r\n size=\"small\"\r\n class=\"border border-gray-200 hover:border-gray-300 transition-colors\"\r\n >\r\n <div class=\"flex items-center gap-3 flex-nowrap min-h-10 p-2\">\r\n <NInput\r\n v-model:value=\"condition.name\"\r\n placeholder=\"分支名称\"\r\n style=\"width: 150px\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <NSelect\r\n v-model:value=\"condition.field\"\r\n placeholder=\"选择字段\"\r\n :options=\"FIELD_OPTIONS\"\r\n style=\"width: 120px\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <NSelect\r\n v-model:value=\"condition.operator\"\r\n placeholder=\"操作符\"\r\n :options=\"OPERATOR_OPTIONS\"\r\n style=\"width: 100px\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <NInput\r\n v-model:value=\"condition.value\"\r\n placeholder=\"值\"\r\n style=\"width: 120px\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <NButton\r\n quaternary\r\n type=\"error\"\r\n @click=\"removeCondition(index)\"\r\n class=\"flex-shrink-0 ml-auto\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:delete\" :size=\"16\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n </NCard>\r\n </div>\r\n <NButton dashed block @click=\"addCondition\" class=\"w-full\">\r\n <template #icon><C_Icon name=\"mdi:plus\" :size=\"16\" /></template>\r\n 添加条件\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n </NModal>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from \"vue\";\r\nimport {\r\n NModal,\r\n NInput,\r\n NTree,\r\n NTag,\r\n NAvatar,\r\n NRadioGroup,\r\n NRadio,\r\n NSpace,\r\n NCard,\r\n NSelect,\r\n NButton,\r\n useMessage,\r\n} from \"naive-ui\";\r\nimport { C_Icon } from \"../C_Icon\";\r\nimport type { WorkflowNode, User, Department, Condition } from \"./types\";\r\nimport {\r\n APPROVAL_MODES,\r\n FIELD_OPTIONS,\r\n OPERATOR_OPTIONS,\r\n CONFIG_TITLES,\r\n getDefaultAvatar,\r\n createDefaultCondition,\r\n} from \"./data\";\r\n\r\ninterface Props {\r\n show: boolean;\r\n currentNode: WorkflowNode | null;\r\n users: User[];\r\n departments: Department[];\r\n}\r\nconst props = withDefaults(defineProps<Props>(), {\r\n show: false,\r\n currentNode: null,\r\n users: () => [],\r\n departments: () => [],\r\n});\r\nconst emit = defineEmits([\"update:show\", \"save\", \"cancel\"]);\r\nconst message = useMessage();\r\n\r\nconst searchKeyword = ref(\"\");\r\nconst selectedUsers = ref<string[]>([]);\r\nconst selectedCopyUsers = ref<string[]>([]);\r\nconst approvalMode = ref<\"any\" | \"all\" | \"sequence\">(\"any\");\r\nconst configLoading = ref(false);\r\nconst conditions = ref<Condition[]>([]);\r\n\r\nconst visible = computed({\r\n get: () => props.show,\r\n set: (value: boolean) => emit(\"update:show\", value),\r\n});\r\nconst configTitle = computed(() => {\r\n if (props.currentNode?.type === \"start\") return \"发起人设置\";\r\n const type = props.currentNode?.type as keyof typeof CONFIG_TITLES;\r\n return CONFIG_TITLES[type] || \"节点设置\";\r\n});\r\n\r\n/* 用户树 */\r\nconst departmentUserTree = computed(() => {\r\n const tree: any[] = [];\r\n const deptMap = new Map();\r\n props.departments?.forEach((dept) => {\r\n if (!deptMap.has(dept.id)) {\r\n deptMap.set(dept.id, {\r\n key: `dept-${dept.id}`,\r\n label: `${dept.name} ${dept.manager ? `(负责人: ${dept.manager})` : \"\"}`,\r\n children: [],\r\n isLeaf: false,\r\n disabled: true,\r\n });\r\n }\r\n });\r\n const filteredUsers =\r\n props.users?.filter(\r\n (user) =>\r\n !searchKeyword.value ||\r\n user.name.includes(searchKeyword.value) ||\r\n user.department.includes(searchKeyword.value),\r\n ) || [];\r\n\r\n filteredUsers.forEach((user) => {\r\n const dept = props.departments?.find((d) => d.name === user.department);\r\n if (dept && deptMap.has(dept.id)) {\r\n deptMap.get(dept.id).children.push({\r\n key: user.id,\r\n label: `${user.name}${user.role ? `(${user.role})` : \"\"}`,\r\n isLeaf: true,\r\n user,\r\n });\r\n }\r\n });\r\n\r\n deptMap.forEach((dept) => {\r\n if (dept.children.length > 0) {\r\n tree.push(dept);\r\n }\r\n });\r\n\r\n return tree;\r\n});\r\n\r\nconst selectedApprovers = computed(\r\n () => props.users?.filter((u) => selectedUsers.value.includes(u.id)) || [],\r\n);\r\nconst selectedCopyUserList = computed(\r\n () =>\r\n props.users?.filter((u) => selectedCopyUsers.value.includes(u.id)) || [],\r\n);\r\nconst selectedInitiators = computed(\r\n () => props.users?.filter((u) => selectedUsers.value.includes(u.id)) || [],\r\n);\r\n\r\nconst handleUserSelect = (keys: string[]) => {\r\n selectedUsers.value = keys.filter((key) => !key.startsWith(\"dept-\"));\r\n};\r\nconst handleCopyUserSelect = (keys: string[]) => {\r\n selectedCopyUsers.value = keys.filter((key) => !key.startsWith(\"dept-\"));\r\n};\r\nconst removeApprover = (userId: string) => {\r\n selectedUsers.value = selectedUsers.value.filter((id) => id !== userId);\r\n};\r\nconst removeCopyUser = (userId: string) => {\r\n selectedCopyUsers.value = selectedCopyUsers.value.filter(\r\n (id) => id !== userId,\r\n );\r\n};\r\nconst removeInitiator = (userId: string) => {\r\n selectedUsers.value = selectedUsers.value.filter((id) => id !== userId);\r\n};\r\n\r\nconst addCondition = () => conditions.value.push(createDefaultCondition());\r\nconst removeCondition = (index: number) => conditions.value.splice(index, 1);\r\n\r\nconst configureStartNode = (node: WorkflowNode) => {\r\n const { initiators } = node.data as any;\r\n selectedUsers.value = initiators ? initiators.map((u: User) => u.id) : [];\r\n};\r\nconst configureApprovalNode = (node: WorkflowNode) => {\r\n const approvers = (node.data as any).approvers || [];\r\n selectedUsers.value = approvers.map((u: User) => u.id);\r\n approvalMode.value = (node.data as any).approvalMode || \"any\";\r\n};\r\nconst configureCopyNode = (node: WorkflowNode) => {\r\n const copyUsers = (node.data as any).copyUsers || [];\r\n selectedCopyUsers.value = copyUsers.map((u: User) => u.id);\r\n};\r\nconst configureConditionNode = (node: WorkflowNode) => {\r\n conditions.value = (node.data as any).conditions || [];\r\n};\r\n\r\nconst saveStartNodeConfig = async (): Promise<boolean> => {\r\n if (selectedUsers.value.length === 0) {\r\n message.error(\"请选择发起人\");\r\n return false;\r\n }\r\n const selectedUserObjs = selectedInitiators.value;\r\n emit(\"save\", { initiators: selectedUserObjs });\r\n message.success(`已设置 ${selectedUserObjs.length} 个发起人`);\r\n return true;\r\n};\r\nconst saveApprovalNodeConfig = async (): Promise<boolean> => {\r\n if (selectedUsers.value.length === 0) {\r\n message.error(\"请至少选择一个审批人\");\r\n return false;\r\n }\r\n const selectedUserObjs = selectedApprovers.value;\r\n emit(\"save\", {\r\n approvers: selectedUserObjs,\r\n approvalMode: approvalMode.value,\r\n });\r\n message.success(`已设置 ${selectedUserObjs.length} 个审批人`);\r\n return true;\r\n};\r\nconst saveCopyNodeConfig = async (): Promise<boolean> => {\r\n const selectedUserObjs = selectedCopyUserList.value;\r\n emit(\"save\", { copyUsers: selectedUserObjs });\r\n message.success(`已设置 ${selectedUserObjs.length} 个抄送人`);\r\n return true;\r\n};\r\nconst saveConditionNodeConfig = async (): Promise<boolean> => {\r\n if (conditions.value.length === 0) {\r\n message.error(\"请至少添加一个条件分支\");\r\n return false;\r\n }\r\n const validConditions = conditions.value.filter(\r\n (c) => c.name && c.field && c.operator && c.value,\r\n );\r\n if (validConditions.length === 0) {\r\n message.error(\"请完善条件配置\");\r\n return false;\r\n }\r\n emit(\"save\", { conditions: validConditions });\r\n message.success(`已设置 ${validConditions.length} 个条件分支`);\r\n return true;\r\n};\r\nconst saveNodeConfig = async (): Promise<boolean> => {\r\n if (!props.currentNode) return false;\r\n configLoading.value = true;\r\n try {\r\n const nodeSavers = {\r\n start: saveStartNodeConfig,\r\n approval: saveApprovalNodeConfig,\r\n copy: saveCopyNodeConfig,\r\n condition: saveConditionNodeConfig,\r\n };\r\n const saver = nodeSavers[props.currentNode.type as keyof typeof nodeSavers];\r\n const success = saver ? await saver() : false;\r\n return success;\r\n } catch (error) {\r\n message.error(\"保存配置失败\");\r\n console.error(\"Save node config error:\", error);\r\n return false;\r\n } finally {\r\n configLoading.value = false;\r\n }\r\n};\r\nconst handleCancel = () => {\r\n emit(\"cancel\");\r\n};\r\nwatch(\r\n () => props.currentNode,\r\n (newNode) => {\r\n if (newNode) {\r\n searchKeyword.value = \"\";\r\n const nodeConfigurators = {\r\n start: configureStartNode,\r\n approval: configureApprovalNode,\r\n copy: configureCopyNode,\r\n condition: configureConditionNode,\r\n };\r\n const configurator =\r\n nodeConfigurators[newNode.type as keyof typeof nodeConfigurators];\r\n if (configurator) {\r\n configurator(newNode);\r\n }\r\n }\r\n },\r\n { immediate: true },\r\n);\r\n\r\nconst isUserSelectNode = computed(() =>\r\n [\"start\", \"approval\", \"copy\"].includes(props.currentNode?.type || \"\"),\r\n);\r\n\r\nconst userSelectConfig = computed(() => {\r\n const type = props.currentNode?.type;\r\n if (type === \"start\") {\r\n return {\r\n icon: \"mdi:account-star\",\r\n title: \"选择发起人\",\r\n checkedKeys: selectedUsers.value,\r\n onSelect: handleUserSelect,\r\n selectedUsers: selectedInitiators.value,\r\n selectedLabel: \"已选择发起人\",\r\n tagType: \"primary\" as const,\r\n onRemove: removeInitiator,\r\n };\r\n } else if (type === \"approval\") {\r\n return {\r\n icon: \"mdi:account-check\",\r\n title: \"选择审批人\",\r\n checkedKeys: selectedUsers.value,\r\n onSelect: handleUserSelect,\r\n selectedUsers: selectedApprovers.value,\r\n selectedLabel: \"已选择审批人\",\r\n tagType: \"info\" as const,\r\n onRemove: removeApprover,\r\n };\r\n } else if (type === \"copy\") {\r\n return {\r\n icon: \"mdi:email-outline\",\r\n title: \"选择抄送人\",\r\n checkedKeys: selectedCopyUsers.value,\r\n onSelect: handleCopyUserSelect,\r\n selectedUsers: selectedCopyUserList.value,\r\n selectedLabel: \"已选择抄送人\",\r\n tagType: \"success\" as const,\r\n onRemove: removeCopyUser,\r\n };\r\n }\r\n /* 安全兜底 */\r\n return {\r\n icon: \"\",\r\n title: \"\",\r\n checkedKeys: [] as string[],\r\n onSelect: (_keys: string[]) => {},\r\n selectedUsers: [] as User[],\r\n selectedLabel: \"\",\r\n tagType: \"default\" as const,\r\n onRemove: (_id: string) => {},\r\n };\r\n});\r\n</script>\r\n","<!--\r\n * @Description: 节点配置弹窗组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <NModal\r\n v-model:show=\"visible\"\r\n style=\"width: 900px\"\r\n :mask-closable=\"false\"\r\n preset=\"dialog\"\r\n :title=\"configTitle\"\r\n positive-text=\"确定\"\r\n negative-text=\"取消\"\r\n :loading=\"configLoading\"\r\n @positive-click=\"saveNodeConfig\"\r\n @negative-click=\"handleCancel\"\r\n >\r\n <!-- 公共选择面板 -->\r\n <template v-if=\"isUserSelectNode\">\r\n <div class=\"max-h-60vh overflow-y-auto\">\r\n <div class=\"config-section\">\r\n <h4\r\n class=\"flex items-center gap-2 mb-4 text-base font-semibold\"\r\n :style=\"{ color: 'var(--n-text-color-1)' }\"\r\n >\r\n <C_Icon\r\n :name=\"userSelectConfig.icon\"\r\n :size=\"16\"\r\n v-if=\"userSelectConfig.icon\"\r\n />\r\n {{ userSelectConfig.title }}\r\n </h4>\r\n\r\n <NInput\r\n v-model:value=\"searchKeyword\"\r\n placeholder=\"搜索用户姓名或部门\"\r\n clearable\r\n class=\"mb-4\"\r\n >\r\n <template #prefix>\r\n <C_Icon name=\"mdi:magnify\" :size=\"16\" />\r\n </template>\r\n </NInput>\r\n\r\n <div\r\n class=\"border rounded-lg p-3 mb-4 max-h-50 overflow-y-auto\"\r\n :style=\"{\r\n borderColor: 'var(--n-border-color)',\r\n background: 'var(--n-color-embedded)',\r\n }\"\r\n >\r\n <NTree\r\n :data=\"departmentUserTree\"\r\n :checked-keys=\"userSelectConfig.checkedKeys\"\r\n :selectable=\"false\"\r\n checkable\r\n cascade\r\n :virtual-scroll=\"true\"\r\n style=\"max-height: 300px\"\r\n @update:checked-keys=\"userSelectConfig.onSelect\"\r\n />\r\n </div>\r\n\r\n <div\r\n v-if=\"userSelectConfig.selectedUsers.length > 0\"\r\n class=\"rounded-lg p-4 mb-4 border\"\r\n :style=\"{\r\n borderColor: 'var(--n-border-color)',\r\n background: 'var(--n-color-embedded)',\r\n }\"\r\n >\r\n <h5\r\n class=\"mb-2 text-sm font-medium\"\r\n :style=\"{ color: 'var(--n-text-color-1)' }\"\r\n >\r\n {{ userSelectConfig.selectedLabel }}\r\n ({{ userSelectConfig.selectedUsers.length }})\r\n </h5>\r\n <div class=\"flex flex-wrap gap-3\">\r\n <NTag\r\n v-for=\"user in userSelectConfig.selectedUsers\"\r\n :key=\"user.id\"\r\n closable\r\n :type=\"userSelectConfig.tagType\"\r\n @close=\"userSelectConfig.onRemove(user.id)\"\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <NAvatar\r\n :src=\"user.avatar\"\r\n :fallback-src=\"getDefaultAvatar(user.name)\"\r\n size=\"small\"\r\n />\r\n <span class=\"font-medium text-sm\">{{ user.name }}</span>\r\n <span\r\n class=\"text-xs px-1.5 py-0.5 rounded\"\r\n :style=\"{\r\n color: 'var(--n-text-color-3)',\r\n background: 'var(--n-color-embedded)',\r\n }\"\r\n >{{ user.department }}</span\r\n >\r\n </div>\r\n </NTag>\r\n </div>\r\n </div>\r\n </div>\r\n <!-- 审批节点时附带审批模式 -->\r\n <template v-if=\"props.currentNode?.type === 'approval'\">\r\n <div class=\"mt-4\">\r\n <h5\r\n class=\"mb-3 text-sm font-medium\"\r\n :style=\"{ color: 'var(--n-text-color-1)' }\"\r\n >\r\n 审批模式\r\n </h5>\r\n <NRadioGroup v-model:value=\"approvalMode\">\r\n <NSpace vertical>\r\n <NRadio\r\n v-for=\"mode in APPROVAL_MODES\"\r\n :key=\"mode.value\"\r\n :value=\"mode.value\"\r\n >\r\n <div class=\"flex flex-col gap-1\">\r\n <strong class=\"text-sm\">{{ mode.label }}</strong>\r\n <span\r\n class=\"text-xs\"\r\n :style=\"{ color: 'var(--n-text-color-3)' }\"\r\n >{{ mode.desc }}</span\r\n >\r\n </div>\r\n </NRadio>\r\n </NSpace>\r\n </NRadioGroup>\r\n </div>\r\n </template>\r\n </div>\r\n </template>\r\n\r\n <!-- 条件节点配置 -->\r\n <div\r\n v-else-if=\"props.currentNode?.type === 'condition'\"\r\n class=\"max-h-60vh overflow-y-auto\"\r\n >\r\n <div class=\"config-section\">\r\n <h4\r\n class=\"flex items-center gap-2 mb-4 text-base font-semibold\"\r\n :style=\"{ color: 'var(--n-text-color-1)' }\"\r\n >\r\n <C_Icon name=\"mdi:source-branch\" :size=\"16\" />\r\n 条件分支设置\r\n </h4>\r\n <div class=\"space-y-3\">\r\n <div\r\n v-for=\"(condition, index) in conditions\"\r\n :key=\"condition.id\"\r\n class=\"condition-item\"\r\n >\r\n <NCard\r\n size=\"small\"\r\n class=\"border border-gray-200 hover:border-gray-300 transition-colors\"\r\n >\r\n <div class=\"flex items-center gap-3 flex-nowrap min-h-10 p-2\">\r\n <NInput\r\n v-model:value=\"condition.name\"\r\n placeholder=\"分支名称\"\r\n style=\"width: 150px\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <NSelect\r\n v-model:value=\"condition.field\"\r\n placeholder=\"选择字段\"\r\n :options=\"FIELD_OPTIONS\"\r\n style=\"width: 120px\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <NSelect\r\n v-model:value=\"condition.operator\"\r\n placeholder=\"操作符\"\r\n :options=\"OPERATOR_OPTIONS\"\r\n style=\"width: 100px\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <NInput\r\n v-model:value=\"condition.value\"\r\n placeholder=\"值\"\r\n style=\"width: 120px\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <NButton\r\n quaternary\r\n type=\"error\"\r\n @click=\"removeCondition(index)\"\r\n class=\"flex-shrink-0 ml-auto\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:delete\" :size=\"16\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n </NCard>\r\n </div>\r\n <NButton dashed block @click=\"addCondition\" class=\"w-full\">\r\n <template #icon><C_Icon name=\"mdi:plus\" :size=\"16\" /></template>\r\n 添加条件\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n </NModal>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from \"vue\";\r\nimport {\r\n NModal,\r\n NInput,\r\n NTree,\r\n NTag,\r\n NAvatar,\r\n NRadioGroup,\r\n NRadio,\r\n NSpace,\r\n NCard,\r\n NSelect,\r\n NButton,\r\n useMessage,\r\n} from \"naive-ui\";\r\nimport { C_Icon } from \"../C_Icon\";\r\nimport type { WorkflowNode, User, Department, Condition } from \"./types\";\r\nimport {\r\n APPROVAL_MODES,\r\n FIELD_OPTIONS,\r\n OPERATOR_OPTIONS,\r\n CONFIG_TITLES,\r\n getDefaultAvatar,\r\n createDefaultCondition,\r\n} from \"./data\";\r\n\r\ninterface Props {\r\n show: boolean;\r\n currentNode: WorkflowNode | null;\r\n users: User[];\r\n departments: Department[];\r\n}\r\nconst props = withDefaults(defineProps<Props>(), {\r\n show: false,\r\n currentNode: null,\r\n users: () => [],\r\n departments: () => [],\r\n});\r\nconst emit = defineEmits([\"update:show\", \"save\", \"cancel\"]);\r\nconst message = useMessage();\r\n\r\nconst searchKeyword = ref(\"\");\r\nconst selectedUsers = ref<string[]>([]);\r\nconst selectedCopyUsers = ref<string[]>([]);\r\nconst approvalMode = ref<\"any\" | \"all\" | \"sequence\">(\"any\");\r\nconst configLoading = ref(false);\r\nconst conditions = ref<Condition[]>([]);\r\n\r\nconst visible = computed({\r\n get: () => props.show,\r\n set: (value: boolean) => emit(\"update:show\", value),\r\n});\r\nconst configTitle = computed(() => {\r\n if (props.currentNode?.type === \"start\") return \"发起人设置\";\r\n const type = props.currentNode?.type as keyof typeof CONFIG_TITLES;\r\n return CONFIG_TITLES[type] || \"节点设置\";\r\n});\r\n\r\n/* 用户树 */\r\nconst departmentUserTree = computed(() => {\r\n const tree: any[] = [];\r\n const deptMap = new Map();\r\n props.departments?.forEach((dept) => {\r\n if (!deptMap.has(dept.id)) {\r\n deptMap.set(dept.id, {\r\n key: `dept-${dept.id}`,\r\n label: `${dept.name} ${dept.manager ? `(负责人: ${dept.manager})` : \"\"}`,\r\n children: [],\r\n isLeaf: false,\r\n disabled: true,\r\n });\r\n }\r\n });\r\n const filteredUsers =\r\n props.users?.filter(\r\n (user) =>\r\n !searchKeyword.value ||\r\n user.name.includes(searchKeyword.value) ||\r\n user.department.includes(searchKeyword.value),\r\n ) || [];\r\n\r\n filteredUsers.forEach((user) => {\r\n const dept = props.departments?.find((d) => d.name === user.department);\r\n if (dept && deptMap.has(dept.id)) {\r\n deptMap.get(dept.id).children.push({\r\n key: user.id,\r\n label: `${user.name}${user.role ? `(${user.role})` : \"\"}`,\r\n isLeaf: true,\r\n user,\r\n });\r\n }\r\n });\r\n\r\n deptMap.forEach((dept) => {\r\n if (dept.children.length > 0) {\r\n tree.push(dept);\r\n }\r\n });\r\n\r\n return tree;\r\n});\r\n\r\nconst selectedApprovers = computed(\r\n () => props.users?.filter((u) => selectedUsers.value.includes(u.id)) || [],\r\n);\r\nconst selectedCopyUserList = computed(\r\n () =>\r\n props.users?.filter((u) => selectedCopyUsers.value.includes(u.id)) || [],\r\n);\r\nconst selectedInitiators = computed(\r\n () => props.users?.filter((u) => selectedUsers.value.includes(u.id)) || [],\r\n);\r\n\r\nconst handleUserSelect = (keys: string[]) => {\r\n selectedUsers.value = keys.filter((key) => !key.startsWith(\"dept-\"));\r\n};\r\nconst handleCopyUserSelect = (keys: string[]) => {\r\n selectedCopyUsers.value = keys.filter((key) => !key.startsWith(\"dept-\"));\r\n};\r\nconst removeApprover = (userId: string) => {\r\n selectedUsers.value = selectedUsers.value.filter((id) => id !== userId);\r\n};\r\nconst removeCopyUser = (userId: string) => {\r\n selectedCopyUsers.value = selectedCopyUsers.value.filter(\r\n (id) => id !== userId,\r\n );\r\n};\r\nconst removeInitiator = (userId: string) => {\r\n selectedUsers.value = selectedUsers.value.filter((id) => id !== userId);\r\n};\r\n\r\nconst addCondition = () => conditions.value.push(createDefaultCondition());\r\nconst removeCondition = (index: number) => conditions.value.splice(index, 1);\r\n\r\nconst configureStartNode = (node: WorkflowNode) => {\r\n const { initiators } = node.data as any;\r\n selectedUsers.value = initiators ? initiators.map((u: User) => u.id) : [];\r\n};\r\nconst configureApprovalNode = (node: WorkflowNode) => {\r\n const approvers = (node.data as any).approvers || [];\r\n selectedUsers.value = approvers.map((u: User) => u.id);\r\n approvalMode.value = (node.data as any).approvalMode || \"any\";\r\n};\r\nconst configureCopyNode = (node: WorkflowNode) => {\r\n const copyUsers = (node.data as any).copyUsers || [];\r\n selectedCopyUsers.value = copyUsers.map((u: User) => u.id);\r\n};\r\nconst configureConditionNode = (node: WorkflowNode) => {\r\n conditions.value = (node.data as any).conditions || [];\r\n};\r\n\r\nconst saveStartNodeConfig = async (): Promise<boolean> => {\r\n if (selectedUsers.value.length === 0) {\r\n message.error(\"请选择发起人\");\r\n return false;\r\n }\r\n const selectedUserObjs = selectedInitiators.value;\r\n emit(\"save\", { initiators: selectedUserObjs });\r\n message.success(`已设置 ${selectedUserObjs.length} 个发起人`);\r\n return true;\r\n};\r\nconst saveApprovalNodeConfig = async (): Promise<boolean> => {\r\n if (selectedUsers.value.length === 0) {\r\n message.error(\"请至少选择一个审批人\");\r\n return false;\r\n }\r\n const selectedUserObjs = selectedApprovers.value;\r\n emit(\"save\", {\r\n approvers: selectedUserObjs,\r\n approvalMode: approvalMode.value,\r\n });\r\n message.success(`已设置 ${selectedUserObjs.length} 个审批人`);\r\n return true;\r\n};\r\nconst saveCopyNodeConfig = async (): Promise<boolean> => {\r\n const selectedUserObjs = selectedCopyUserList.value;\r\n emit(\"save\", { copyUsers: selectedUserObjs });\r\n message.success(`已设置 ${selectedUserObjs.length} 个抄送人`);\r\n return true;\r\n};\r\nconst saveConditionNodeConfig = async (): Promise<boolean> => {\r\n if (conditions.value.length === 0) {\r\n message.error(\"请至少添加一个条件分支\");\r\n return false;\r\n }\r\n const validConditions = conditions.value.filter(\r\n (c) => c.name && c.field && c.operator && c.value,\r\n );\r\n if (validConditions.length === 0) {\r\n message.error(\"请完善条件配置\");\r\n return false;\r\n }\r\n emit(\"save\", { conditions: validConditions });\r\n message.success(`已设置 ${validConditions.length} 个条件分支`);\r\n return true;\r\n};\r\nconst saveNodeConfig = async (): Promise<boolean> => {\r\n if (!props.currentNode) return false;\r\n configLoading.value = true;\r\n try {\r\n const nodeSavers = {\r\n start: saveStartNodeConfig,\r\n approval: saveApprovalNodeConfig,\r\n copy: saveCopyNodeConfig,\r\n condition: saveConditionNodeConfig,\r\n };\r\n const saver = nodeSavers[props.currentNode.type as keyof typeof nodeSavers];\r\n const success = saver ? await saver() : false;\r\n return success;\r\n } catch (error) {\r\n message.error(\"保存配置失败\");\r\n console.error(\"Save node config error:\", error);\r\n return false;\r\n } finally {\r\n configLoading.value = false;\r\n }\r\n};\r\nconst handleCancel = () => {\r\n emit(\"cancel\");\r\n};\r\nwatch(\r\n () => props.currentNode,\r\n (newNode) => {\r\n if (newNode) {\r\n searchKeyword.value = \"\";\r\n const nodeConfigurators = {\r\n start: configureStartNode,\r\n approval: configureApprovalNode,\r\n copy: configureCopyNode,\r\n condition: configureConditionNode,\r\n };\r\n const configurator =\r\n nodeConfigurators[newNode.type as keyof typeof nodeConfigurators];\r\n if (configurator) {\r\n configurator(newNode);\r\n }\r\n }\r\n },\r\n { immediate: true },\r\n);\r\n\r\nconst isUserSelectNode = computed(() =>\r\n [\"start\", \"approval\", \"copy\"].includes(props.currentNode?.type || \"\"),\r\n);\r\n\r\nconst userSelectConfig = computed(() => {\r\n const type = props.currentNode?.type;\r\n if (type === \"start\") {\r\n return {\r\n icon: \"mdi:account-star\",\r\n title: \"选择发起人\",\r\n checkedKeys: selectedUsers.value,\r\n onSelect: handleUserSelect,\r\n selectedUsers: selectedInitiators.value,\r\n selectedLabel: \"已选择发起人\",\r\n tagType: \"primary\" as const,\r\n onRemove: removeInitiator,\r\n };\r\n } else if (type === \"approval\") {\r\n return {\r\n icon: \"mdi:account-check\",\r\n title: \"选择审批人\",\r\n checkedKeys: selectedUsers.value,\r\n onSelect: handleUserSelect,\r\n selectedUsers: selectedApprovers.value,\r\n selectedLabel: \"已选择审批人\",\r\n tagType: \"info\" as const,\r\n onRemove: removeApprover,\r\n };\r\n } else if (type === \"copy\") {\r\n return {\r\n icon: \"mdi:email-outline\",\r\n title: \"选择抄送人\",\r\n checkedKeys: selectedCopyUsers.value,\r\n onSelect: handleCopyUserSelect,\r\n selectedUsers: selectedCopyUserList.value,\r\n selectedLabel: \"已选择抄送人\",\r\n tagType: \"success\" as const,\r\n onRemove: removeCopyUser,\r\n };\r\n }\r\n /* 安全兜底 */\r\n return {\r\n icon: \"\",\r\n title: \"\",\r\n checkedKeys: [] as string[],\r\n onSelect: (_keys: string[]) => {},\r\n selectedUsers: [] as User[],\r\n selectedLabel: \"\",\r\n tagType: \"default\" as const,\r\n onRemove: (_id: string) => {},\r\n };\r\n});\r\n</script>\r\n","<!--\r\n * @Description: 节点配置弹窗组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <NModal\r\n v-model:show=\"visible\"\r\n style=\"width: 900px\"\r\n :mask-closable=\"false\"\r\n preset=\"dialog\"\r\n :title=\"configTitle\"\r\n positive-text=\"确定\"\r\n negative-text=\"取消\"\r\n :loading=\"configLoading\"\r\n @positive-click=\"saveNodeConfig\"\r\n @negative-click=\"handleCancel\"\r\n >\r\n <!-- 公共选择面板 -->\r\n <template v-if=\"isUserSelectNode\">\r\n <div class=\"max-h-60vh overflow-y-auto\">\r\n <div class=\"config-section\">\r\n <h4\r\n class=\"flex items-center gap-2 mb-4 text-base font-semibold\"\r\n :style=\"{ color: 'var(--n-text-color-1)' }\"\r\n >\r\n <C_Icon\r\n :name=\"userSelectConfig.icon\"\r\n :size=\"16\"\r\n v-if=\"userSelectConfig.icon\"\r\n />\r\n {{ userSelectConfig.title }}\r\n </h4>\r\n\r\n <NInput\r\n v-model:value=\"searchKeyword\"\r\n placeholder=\"搜索用户姓名或部门\"\r\n clearable\r\n class=\"mb-4\"\r\n >\r\n <template #prefix>\r\n <C_Icon name=\"mdi:magnify\" :size=\"16\" />\r\n </template>\r\n </NInput>\r\n\r\n <div\r\n class=\"border rounded-lg p-3 mb-4 max-h-50 overflow-y-auto\"\r\n :style=\"{\r\n borderColor: 'var(--n-border-color)',\r\n background: 'var(--n-color-embedded)',\r\n }\"\r\n >\r\n <NTree\r\n :data=\"departmentUserTree\"\r\n :checked-keys=\"userSelectConfig.checkedKeys\"\r\n :selectable=\"false\"\r\n checkable\r\n cascade\r\n :virtual-scroll=\"true\"\r\n style=\"max-height: 300px\"\r\n @update:checked-keys=\"userSelectConfig.onSelect\"\r\n />\r\n </div>\r\n\r\n <div\r\n v-if=\"userSelectConfig.selectedUsers.length > 0\"\r\n class=\"rounded-lg p-4 mb-4 border\"\r\n :style=\"{\r\n borderColor: 'var(--n-border-color)',\r\n background: 'var(--n-color-embedded)',\r\n }\"\r\n >\r\n <h5\r\n class=\"mb-2 text-sm font-medium\"\r\n :style=\"{ color: 'var(--n-text-color-1)' }\"\r\n >\r\n {{ userSelectConfig.selectedLabel }}\r\n ({{ userSelectConfig.selectedUsers.length }})\r\n </h5>\r\n <div class=\"flex flex-wrap gap-3\">\r\n <NTag\r\n v-for=\"user in userSelectConfig.selectedUsers\"\r\n :key=\"user.id\"\r\n closable\r\n :type=\"userSelectConfig.tagType\"\r\n @close=\"userSelectConfig.onRemove(user.id)\"\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <NAvatar\r\n :src=\"user.avatar\"\r\n :fallback-src=\"getDefaultAvatar(user.name)\"\r\n size=\"small\"\r\n />\r\n <span class=\"font-medium text-sm\">{{ user.name }}</span>\r\n <span\r\n class=\"text-xs px-1.5 py-0.5 rounded\"\r\n :style=\"{\r\n color: 'var(--n-text-color-3)',\r\n background: 'var(--n-color-embedded)',\r\n }\"\r\n >{{ user.department }}</span\r\n >\r\n </div>\r\n </NTag>\r\n </div>\r\n </div>\r\n </div>\r\n <!-- 审批节点时附带审批模式 -->\r\n <template v-if=\"props.currentNode?.type === 'approval'\">\r\n <div class=\"mt-4\">\r\n <h5\r\n class=\"mb-3 text-sm font-medium\"\r\n :style=\"{ color: 'var(--n-text-color-1)' }\"\r\n >\r\n 审批模式\r\n </h5>\r\n <NRadioGroup v-model:value=\"approvalMode\">\r\n <NSpace vertical>\r\n <NRadio\r\n v-for=\"mode in APPROVAL_MODES\"\r\n :key=\"mode.value\"\r\n :value=\"mode.value\"\r\n >\r\n <div class=\"flex flex-col gap-1\">\r\n <strong class=\"text-sm\">{{ mode.label }}</strong>\r\n <span\r\n class=\"text-xs\"\r\n :style=\"{ color: 'var(--n-text-color-3)' }\"\r\n >{{ mode.desc }}</span\r\n >\r\n </div>\r\n </NRadio>\r\n </NSpace>\r\n </NRadioGroup>\r\n </div>\r\n </template>\r\n </div>\r\n </template>\r\n\r\n <!-- 条件节点配置 -->\r\n <div\r\n v-else-if=\"props.currentNode?.type === 'condition'\"\r\n class=\"max-h-60vh overflow-y-auto\"\r\n >\r\n <div class=\"config-section\">\r\n <h4\r\n class=\"flex items-center gap-2 mb-4 text-base font-semibold\"\r\n :style=\"{ color: 'var(--n-text-color-1)' }\"\r\n >\r\n <C_Icon name=\"mdi:source-branch\" :size=\"16\" />\r\n 条件分支设置\r\n </h4>\r\n <div class=\"space-y-3\">\r\n <div\r\n v-for=\"(condition, index) in conditions\"\r\n :key=\"condition.id\"\r\n class=\"condition-item\"\r\n >\r\n <NCard\r\n size=\"small\"\r\n class=\"border border-gray-200 hover:border-gray-300 transition-colors\"\r\n >\r\n <div class=\"flex items-center gap-3 flex-nowrap min-h-10 p-2\">\r\n <NInput\r\n v-model:value=\"condition.name\"\r\n placeholder=\"分支名称\"\r\n style=\"width: 150px\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <NSelect\r\n v-model:value=\"condition.field\"\r\n placeholder=\"选择字段\"\r\n :options=\"FIELD_OPTIONS\"\r\n style=\"width: 120px\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <NSelect\r\n v-model:value=\"condition.operator\"\r\n placeholder=\"操作符\"\r\n :options=\"OPERATOR_OPTIONS\"\r\n style=\"width: 100px\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <NInput\r\n v-model:value=\"condition.value\"\r\n placeholder=\"值\"\r\n style=\"width: 120px\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <NButton\r\n quaternary\r\n type=\"error\"\r\n @click=\"removeCondition(index)\"\r\n class=\"flex-shrink-0 ml-auto\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:delete\" :size=\"16\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n </NCard>\r\n </div>\r\n <NButton dashed block @click=\"addCondition\" class=\"w-full\">\r\n <template #icon><C_Icon name=\"mdi:plus\" :size=\"16\" /></template>\r\n 添加条件\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n </NModal>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from \"vue\";\r\nimport {\r\n NModal,\r\n NInput,\r\n NTree,\r\n NTag,\r\n NAvatar,\r\n NRadioGroup,\r\n NRadio,\r\n NSpace,\r\n NCard,\r\n NSelect,\r\n NButton,\r\n useMessage,\r\n} from \"naive-ui\";\r\nimport { C_Icon } from \"../C_Icon\";\r\nimport type { WorkflowNode, User, Department, Condition } from \"./types\";\r\nimport {\r\n APPROVAL_MODES,\r\n FIELD_OPTIONS,\r\n OPERATOR_OPTIONS,\r\n CONFIG_TITLES,\r\n getDefaultAvatar,\r\n createDefaultCondition,\r\n} from \"./data\";\r\n\r\ninterface Props {\r\n show: boolean;\r\n currentNode: WorkflowNode | null;\r\n users: User[];\r\n departments: Department[];\r\n}\r\nconst props = withDefaults(defineProps<Props>(), {\r\n show: false,\r\n currentNode: null,\r\n users: () => [],\r\n departments: () => [],\r\n});\r\nconst emit = defineEmits([\"update:show\", \"save\", \"cancel\"]);\r\nconst message = useMessage();\r\n\r\nconst searchKeyword = ref(\"\");\r\nconst selectedUsers = ref<string[]>([]);\r\nconst selectedCopyUsers = ref<string[]>([]);\r\nconst approvalMode = ref<\"any\" | \"all\" | \"sequence\">(\"any\");\r\nconst configLoading = ref(false);\r\nconst conditions = ref<Condition[]>([]);\r\n\r\nconst visible = computed({\r\n get: () => props.show,\r\n set: (value: boolean) => emit(\"update:show\", value),\r\n});\r\nconst configTitle = computed(() => {\r\n if (props.currentNode?.type === \"start\") return \"发起人设置\";\r\n const type = props.currentNode?.type as keyof typeof CONFIG_TITLES;\r\n return CONFIG_TITLES[type] || \"节点设置\";\r\n});\r\n\r\n/* 用户树 */\r\nconst departmentUserTree = computed(() => {\r\n const tree: any[] = [];\r\n const deptMap = new Map();\r\n props.departments?.forEach((dept) => {\r\n if (!deptMap.has(dept.id)) {\r\n deptMap.set(dept.id, {\r\n key: `dept-${dept.id}`,\r\n label: `${dept.name} ${dept.manager ? `(负责人: ${dept.manager})` : \"\"}`,\r\n children: [],\r\n isLeaf: false,\r\n disabled: true,\r\n });\r\n }\r\n });\r\n const filteredUsers =\r\n props.users?.filter(\r\n (user) =>\r\n !searchKeyword.value ||\r\n user.name.includes(searchKeyword.value) ||\r\n user.department.includes(searchKeyword.value),\r\n ) || [];\r\n\r\n filteredUsers.forEach((user) => {\r\n const dept = props.departments?.find((d) => d.name === user.department);\r\n if (dept && deptMap.has(dept.id)) {\r\n deptMap.get(dept.id).children.push({\r\n key: user.id,\r\n label: `${user.name}${user.role ? `(${user.role})` : \"\"}`,\r\n isLeaf: true,\r\n user,\r\n });\r\n }\r\n });\r\n\r\n deptMap.forEach((dept) => {\r\n if (dept.children.length > 0) {\r\n tree.push(dept);\r\n }\r\n });\r\n\r\n return tree;\r\n});\r\n\r\nconst selectedApprovers = computed(\r\n () => props.users?.filter((u) => selectedUsers.value.includes(u.id)) || [],\r\n);\r\nconst selectedCopyUserList = computed(\r\n () =>\r\n props.users?.filter((u) => selectedCopyUsers.value.includes(u.id)) || [],\r\n);\r\nconst selectedInitiators = computed(\r\n () => props.users?.filter((u) => selectedUsers.value.includes(u.id)) || [],\r\n);\r\n\r\nconst handleUserSelect = (keys: string[]) => {\r\n selectedUsers.value = keys.filter((key) => !key.startsWith(\"dept-\"));\r\n};\r\nconst handleCopyUserSelect = (keys: string[]) => {\r\n selectedCopyUsers.value = keys.filter((key) => !key.startsWith(\"dept-\"));\r\n};\r\nconst removeApprover = (userId: string) => {\r\n selectedUsers.value = selectedUsers.value.filter((id) => id !== userId);\r\n};\r\nconst removeCopyUser = (userId: string) => {\r\n selectedCopyUsers.value = selectedCopyUsers.value.filter(\r\n (id) => id !== userId,\r\n );\r\n};\r\nconst removeInitiator = (userId: string) => {\r\n selectedUsers.value = selectedUsers.value.filter((id) => id !== userId);\r\n};\r\n\r\nconst addCondition = () => conditions.value.push(createDefaultCondition());\r\nconst removeCondition = (index: number) => conditions.value.splice(index, 1);\r\n\r\nconst configureStartNode = (node: WorkflowNode) => {\r\n const { initiators } = node.data as any;\r\n selectedUsers.value = initiators ? initiators.map((u: User) => u.id) : [];\r\n};\r\nconst configureApprovalNode = (node: WorkflowNode) => {\r\n const approvers = (node.data as any).approvers || [];\r\n selectedUsers.value = approvers.map((u: User) => u.id);\r\n approvalMode.value = (node.data as any).approvalMode || \"any\";\r\n};\r\nconst configureCopyNode = (node: WorkflowNode) => {\r\n const copyUsers = (node.data as any).copyUsers || [];\r\n selectedCopyUsers.value = copyUsers.map((u: User) => u.id);\r\n};\r\nconst configureConditionNode = (node: WorkflowNode) => {\r\n conditions.value = (node.data as any).conditions || [];\r\n};\r\n\r\nconst saveStartNodeConfig = async (): Promise<boolean> => {\r\n if (selectedUsers.value.length === 0) {\r\n message.error(\"请选择发起人\");\r\n return false;\r\n }\r\n const selectedUserObjs = selectedInitiators.value;\r\n emit(\"save\", { initiators: selectedUserObjs });\r\n message.success(`已设置 ${selectedUserObjs.length} 个发起人`);\r\n return true;\r\n};\r\nconst saveApprovalNodeConfig = async (): Promise<boolean> => {\r\n if (selectedUsers.value.length === 0) {\r\n message.error(\"请至少选择一个审批人\");\r\n return false;\r\n }\r\n const selectedUserObjs = selectedApprovers.value;\r\n emit(\"save\", {\r\n approvers: selectedUserObjs,\r\n approvalMode: approvalMode.value,\r\n });\r\n message.success(`已设置 ${selectedUserObjs.length} 个审批人`);\r\n return true;\r\n};\r\nconst saveCopyNodeConfig = async (): Promise<boolean> => {\r\n const selectedUserObjs = selectedCopyUserList.value;\r\n emit(\"save\", { copyUsers: selectedUserObjs });\r\n message.success(`已设置 ${selectedUserObjs.length} 个抄送人`);\r\n return true;\r\n};\r\nconst saveConditionNodeConfig = async (): Promise<boolean> => {\r\n if (conditions.value.length === 0) {\r\n message.error(\"请至少添加一个条件分支\");\r\n return false;\r\n }\r\n const validConditions = conditions.value.filter(\r\n (c) => c.name && c.field && c.operator && c.value,\r\n );\r\n if (validConditions.length === 0) {\r\n message.error(\"请完善条件配置\");\r\n return false;\r\n }\r\n emit(\"save\", { conditions: validConditions });\r\n message.success(`已设置 ${validConditions.length} 个条件分支`);\r\n return true;\r\n};\r\nconst saveNodeConfig = async (): Promise<boolean> => {\r\n if (!props.currentNode) return false;\r\n configLoading.value = true;\r\n try {\r\n const nodeSavers = {\r\n start: saveStartNodeConfig,\r\n approval: saveApprovalNodeConfig,\r\n copy: saveCopyNodeConfig,\r\n condition: saveConditionNodeConfig,\r\n };\r\n const saver = nodeSavers[props.currentNode.type as keyof typeof nodeSavers];\r\n const success = saver ? await saver() : false;\r\n return success;\r\n } catch (error) {\r\n message.error(\"保存配置失败\");\r\n console.error(\"Save node config error:\", error);\r\n return false;\r\n } finally {\r\n configLoading.value = false;\r\n }\r\n};\r\nconst handleCancel = () => {\r\n emit(\"cancel\");\r\n};\r\nwatch(\r\n () => props.currentNode,\r\n (newNode) => {\r\n if (newNode) {\r\n searchKeyword.value = \"\";\r\n const nodeConfigurators = {\r\n start: configureStartNode,\r\n approval: configureApprovalNode,\r\n copy: configureCopyNode,\r\n condition: configureConditionNode,\r\n };\r\n const configurator =\r\n nodeConfigurators[newNode.type as keyof typeof nodeConfigurators];\r\n if (configurator) {\r\n configurator(newNode);\r\n }\r\n }\r\n },\r\n { immediate: true },\r\n);\r\n\r\nconst isUserSelectNode = computed(() =>\r\n [\"start\", \"approval\", \"copy\"].includes(props.currentNode?.type || \"\"),\r\n);\r\n\r\nconst userSelectConfig = computed(() => {\r\n const type = props.currentNode?.type;\r\n if (type === \"start\") {\r\n return {\r\n icon: \"mdi:account-star\",\r\n title: \"选择发起人\",\r\n checkedKeys: selectedUsers.value,\r\n onSelect: handleUserSelect,\r\n selectedUsers: selectedInitiators.value,\r\n selectedLabel: \"已选择发起人\",\r\n tagType: \"primary\" as const,\r\n onRemove: removeInitiator,\r\n };\r\n } else if (type === \"approval\") {\r\n return {\r\n icon: \"mdi:account-check\",\r\n title: \"选择审批人\",\r\n checkedKeys: selectedUsers.value,\r\n onSelect: handleUserSelect,\r\n selectedUsers: selectedApprovers.value,\r\n selectedLabel: \"已选择审批人\",\r\n tagType: \"info\" as const,\r\n onRemove: removeApprover,\r\n };\r\n } else if (type === \"copy\") {\r\n return {\r\n icon: \"mdi:email-outline\",\r\n title: \"选择抄送人\",\r\n checkedKeys: selectedCopyUsers.value,\r\n onSelect: handleCopyUserSelect,\r\n selectedUsers: selectedCopyUserList.value,\r\n selectedLabel: \"已选择抄送人\",\r\n tagType: \"success\" as const,\r\n onRemove: removeCopyUser,\r\n };\r\n }\r\n /* 安全兜底 */\r\n return {\r\n icon: \"\",\r\n title: \"\",\r\n checkedKeys: [] as string[],\r\n onSelect: (_keys: string[]) => {},\r\n selectedUsers: [] as User[],\r\n selectedLabel: \"\",\r\n tagType: \"default\" as const,\r\n onRemove: (_id: string) => {},\r\n };\r\n});\r\n</script>\r\n","<!--\r\n * @Description: 开始节点组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"start-node\">\r\n <!-- 主要内容区域 -->\r\n <div class=\"node-content\">\r\n <div class=\"node-icon\">🚀</div>\r\n <div class=\"node-info\">\r\n <div class=\"node-text\">{{ data.title }}</div>\r\n <div v-if=\"initiatorNames\" class=\"initiator-name\">\r\n {{ initiatorNames }}\r\n </div>\r\n <div v-else class=\"placeholder-text\">点击设置发起人</div>\r\n </div>\r\n </div>\r\n\r\n <!-- 添加节点按钮 -->\r\n <div class=\"add-node-btn\" @click=\"showAddMenu\" title=\"添加下一个节点\">\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { inject, computed } from \"vue\";\r\nimport { C_Icon } from \"../../C_Icon\";\r\n\r\ninterface Initiator {\r\n id: string;\r\n name: string;\r\n department: string;\r\n role: string;\r\n}\r\n\r\ninterface Props {\r\n id: string;\r\n data: {\r\n title: string;\r\n status?: string;\r\n initiators?: Initiator[];\r\n };\r\n}\r\n\r\nconst props = defineProps<Props>();\r\n\r\n/* 只注入需要的方法 */\r\nconst showAddMenuFn = inject(\"showAddMenu\") as\r\n | ((position: { x: number; y: number }, nodeId?: string) => void)\r\n | undefined;\r\n\r\n/* 计算发起人名称 */\r\nconst initiatorNames = computed(() => {\r\n const { initiators } = props.data;\r\n if (!initiators || !Array.isArray(initiators) || initiators.length === 0) {\r\n return \"\";\r\n }\r\n return initiators.map((user) => user?.name || \"未知用户\").join(\"、\");\r\n});\r\n\r\nconst showAddMenu = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();\r\n\r\n if (showAddMenuFn) {\r\n showAddMenuFn(\r\n {\r\n x: rect.left + rect.width / 2,\r\n y: rect.bottom + 10,\r\n },\r\n props.id,\r\n );\r\n }\r\n};\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.start-node {\r\n position: relative;\r\n}\r\n\r\n.node-content {\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n color: white;\r\n padding: 16px 20px;\r\n border-radius: 12px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 12px;\r\n min-width: 160px;\r\n box-shadow: 0 4px 20px rgba(102, 126, 234, 0.3);\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n cursor: pointer;\r\n\r\n &:hover {\r\n transform: translateY(-2px);\r\n box-shadow: 0 8px 30px rgba(102, 126, 234, 0.4);\r\n }\r\n}\r\n\r\n.node-icon {\r\n font-size: 16px;\r\n flex-shrink: 0;\r\n}\r\n\r\n.node-info {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n text-align: center;\r\n}\r\n\r\n.node-text {\r\n font-weight: 600;\r\n font-size: 14px;\r\n}\r\n\r\n.initiator-name {\r\n font-size: 12px;\r\n opacity: 0.9;\r\n background: rgba(255, 255, 255, 0.2);\r\n padding: 2px 8px;\r\n border-radius: 8px;\r\n}\r\n\r\n.placeholder-text {\r\n font-size: 11px;\r\n opacity: 0.7;\r\n font-style: italic;\r\n}\r\n\r\n.add-node-btn {\r\n position: absolute;\r\n left: 50%;\r\n bottom: -20px;\r\n transform: translateX(-50%);\r\n width: 32px;\r\n height: 32px;\r\n border-radius: 50%;\r\n background: linear-gradient(135deg, #1890ff, #722ed1);\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n font-size: 16px;\r\n font-weight: bold;\r\n z-index: 10;\r\n\r\n &:hover {\r\n transform: translateX(-50%) scale(1.1);\r\n box-shadow: 0 6px 20px rgba(24, 144, 255, 0.4);\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Description: 开始节点组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"start-node\">\r\n <!-- 主要内容区域 -->\r\n <div class=\"node-content\">\r\n <div class=\"node-icon\">🚀</div>\r\n <div class=\"node-info\">\r\n <div class=\"node-text\">{{ data.title }}</div>\r\n <div v-if=\"initiatorNames\" class=\"initiator-name\">\r\n {{ initiatorNames }}\r\n </div>\r\n <div v-else class=\"placeholder-text\">点击设置发起人</div>\r\n </div>\r\n </div>\r\n\r\n <!-- 添加节点按钮 -->\r\n <div class=\"add-node-btn\" @click=\"showAddMenu\" title=\"添加下一个节点\">\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { inject, computed } from \"vue\";\r\nimport { C_Icon } from \"../../C_Icon\";\r\n\r\ninterface Initiator {\r\n id: string;\r\n name: string;\r\n department: string;\r\n role: string;\r\n}\r\n\r\ninterface Props {\r\n id: string;\r\n data: {\r\n title: string;\r\n status?: string;\r\n initiators?: Initiator[];\r\n };\r\n}\r\n\r\nconst props = defineProps<Props>();\r\n\r\n/* 只注入需要的方法 */\r\nconst showAddMenuFn = inject(\"showAddMenu\") as\r\n | ((position: { x: number; y: number }, nodeId?: string) => void)\r\n | undefined;\r\n\r\n/* 计算发起人名称 */\r\nconst initiatorNames = computed(() => {\r\n const { initiators } = props.data;\r\n if (!initiators || !Array.isArray(initiators) || initiators.length === 0) {\r\n return \"\";\r\n }\r\n return initiators.map((user) => user?.name || \"未知用户\").join(\"、\");\r\n});\r\n\r\nconst showAddMenu = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();\r\n\r\n if (showAddMenuFn) {\r\n showAddMenuFn(\r\n {\r\n x: rect.left + rect.width / 2,\r\n y: rect.bottom + 10,\r\n },\r\n props.id,\r\n );\r\n }\r\n};\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.start-node {\r\n position: relative;\r\n}\r\n\r\n.node-content {\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n color: white;\r\n padding: 16px 20px;\r\n border-radius: 12px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 12px;\r\n min-width: 160px;\r\n box-shadow: 0 4px 20px rgba(102, 126, 234, 0.3);\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n cursor: pointer;\r\n\r\n &:hover {\r\n transform: translateY(-2px);\r\n box-shadow: 0 8px 30px rgba(102, 126, 234, 0.4);\r\n }\r\n}\r\n\r\n.node-icon {\r\n font-size: 16px;\r\n flex-shrink: 0;\r\n}\r\n\r\n.node-info {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n text-align: center;\r\n}\r\n\r\n.node-text {\r\n font-weight: 600;\r\n font-size: 14px;\r\n}\r\n\r\n.initiator-name {\r\n font-size: 12px;\r\n opacity: 0.9;\r\n background: rgba(255, 255, 255, 0.2);\r\n padding: 2px 8px;\r\n border-radius: 8px;\r\n}\r\n\r\n.placeholder-text {\r\n font-size: 11px;\r\n opacity: 0.7;\r\n font-style: italic;\r\n}\r\n\r\n.add-node-btn {\r\n position: absolute;\r\n left: 50%;\r\n bottom: -20px;\r\n transform: translateX(-50%);\r\n width: 32px;\r\n height: 32px;\r\n border-radius: 50%;\r\n background: linear-gradient(135deg, #1890ff, #722ed1);\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n font-size: 16px;\r\n font-weight: bold;\r\n z-index: 10;\r\n\r\n &:hover {\r\n transform: translateX(-50%) scale(1.1);\r\n box-shadow: 0 6px 20px rgba(24, 144, 255, 0.4);\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Description: 开始节点组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"start-node\">\r\n <!-- 主要内容区域 -->\r\n <div class=\"node-content\">\r\n <div class=\"node-icon\">🚀</div>\r\n <div class=\"node-info\">\r\n <div class=\"node-text\">{{ data.title }}</div>\r\n <div v-if=\"initiatorNames\" class=\"initiator-name\">\r\n {{ initiatorNames }}\r\n </div>\r\n <div v-else class=\"placeholder-text\">点击设置发起人</div>\r\n </div>\r\n </div>\r\n\r\n <!-- 添加节点按钮 -->\r\n <div class=\"add-node-btn\" @click=\"showAddMenu\" title=\"添加下一个节点\">\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { inject, computed } from \"vue\";\r\nimport { C_Icon } from \"../../C_Icon\";\r\n\r\ninterface Initiator {\r\n id: string;\r\n name: string;\r\n department: string;\r\n role: string;\r\n}\r\n\r\ninterface Props {\r\n id: string;\r\n data: {\r\n title: string;\r\n status?: string;\r\n initiators?: Initiator[];\r\n };\r\n}\r\n\r\nconst props = defineProps<Props>();\r\n\r\n/* 只注入需要的方法 */\r\nconst showAddMenuFn = inject(\"showAddMenu\") as\r\n | ((position: { x: number; y: number }, nodeId?: string) => void)\r\n | undefined;\r\n\r\n/* 计算发起人名称 */\r\nconst initiatorNames = computed(() => {\r\n const { initiators } = props.data;\r\n if (!initiators || !Array.isArray(initiators) || initiators.length === 0) {\r\n return \"\";\r\n }\r\n return initiators.map((user) => user?.name || \"未知用户\").join(\"、\");\r\n});\r\n\r\nconst showAddMenu = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();\r\n\r\n if (showAddMenuFn) {\r\n showAddMenuFn(\r\n {\r\n x: rect.left + rect.width / 2,\r\n y: rect.bottom + 10,\r\n },\r\n props.id,\r\n );\r\n }\r\n};\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.start-node {\r\n position: relative;\r\n}\r\n\r\n.node-content {\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n color: white;\r\n padding: 16px 20px;\r\n border-radius: 12px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 12px;\r\n min-width: 160px;\r\n box-shadow: 0 4px 20px rgba(102, 126, 234, 0.3);\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n cursor: pointer;\r\n\r\n &:hover {\r\n transform: translateY(-2px);\r\n box-shadow: 0 8px 30px rgba(102, 126, 234, 0.4);\r\n }\r\n}\r\n\r\n.node-icon {\r\n font-size: 16px;\r\n flex-shrink: 0;\r\n}\r\n\r\n.node-info {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n text-align: center;\r\n}\r\n\r\n.node-text {\r\n font-weight: 600;\r\n font-size: 14px;\r\n}\r\n\r\n.initiator-name {\r\n font-size: 12px;\r\n opacity: 0.9;\r\n background: rgba(255, 255, 255, 0.2);\r\n padding: 2px 8px;\r\n border-radius: 8px;\r\n}\r\n\r\n.placeholder-text {\r\n font-size: 11px;\r\n opacity: 0.7;\r\n font-style: italic;\r\n}\r\n\r\n.add-node-btn {\r\n position: absolute;\r\n left: 50%;\r\n bottom: -20px;\r\n transform: translateX(-50%);\r\n width: 32px;\r\n height: 32px;\r\n border-radius: 50%;\r\n background: linear-gradient(135deg, #1890ff, #722ed1);\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n font-size: 16px;\r\n font-weight: bold;\r\n z-index: 10;\r\n\r\n &:hover {\r\n transform: translateX(-50%) scale(1.1);\r\n box-shadow: 0 6px 20px rgba(24, 144, 255, 0.4);\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Description: 审批节点组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"approval-node\">\r\n <div class=\"status-indicator\" :class=\"data.status\"></div>\r\n\r\n <!-- 删除按钮 -->\r\n <div class=\"delete-btn\" @click=\"handleDelete\" title=\"删除节点\">\r\n <C_Icon name=\"mdi:close\" :size=\"12\" />\r\n </div>\r\n\r\n <div class=\"node-card\" @click=\"handleNodeClick\">\r\n <div class=\"node-header\">\r\n <div class=\"node-icon\">\r\n <C_Icon name=\"mdi:account\" :size=\"12\" color=\"white\" />\r\n </div>\r\n <span class=\"node-title\">{{ data.title }}</span>\r\n <div class=\"node-badge\" v-if=\"approverCount > 0\">\r\n {{ approverCount }}\r\n </div>\r\n <div class=\"approval-mode-badge\" v-if=\"data.approvalMode\">\r\n {{ getApprovalModeText(data.approvalMode) }}\r\n </div>\r\n </div>\r\n\r\n <div class=\"node-content\">\r\n <div v-if=\"approverCount > 0\" class=\"approvers-list\">\r\n <div\r\n v-for=\"approver in displayApprovers\"\r\n :key=\"approver.id\"\r\n class=\"approver-tag\"\r\n >\r\n <div class=\"approver-info\">\r\n <div class=\"approver-avatar\">\r\n <NAvatar\r\n :src=\"approver.avatar\"\r\n :fallback-src=\"getDefaultAvatar(approver.name)\"\r\n size=\"small\"\r\n />\r\n </div>\r\n <div class=\"approver-details\">\r\n <span class=\"approver-name\">{{ approver.name }}</span>\r\n <span class=\"approver-dept\">{{ approver.department }}</span>\r\n <span class=\"approver-role\">{{ approver.role }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n <div v-if=\"moreCount > 0\" class=\"more-count\">+{{ moreCount }}</div>\r\n </div>\r\n <div v-else class=\"placeholder\">\r\n <C_Icon name=\"mdi:account-plus\" :size=\"24\" color=\"#9ca3af\" />\r\n <span>请设置审批人</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"add-node-btn\" @click=\"showAddMenu\" title=\"添加下一个节点\">\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, inject } from \"vue\";\r\nimport { NAvatar } from \"naive-ui\";\r\nimport { C_Icon } from \"../../C_Icon\";\r\n\r\n/* 定义审批人类型 */\r\ninterface Approver {\r\n id: string;\r\n name: string;\r\n avatar?: string;\r\n department: string;\r\n role: string;\r\n email?: string;\r\n phone?: string;\r\n}\r\n\r\ninterface Props {\r\n id: string;\r\n data: {\r\n title: string;\r\n approvers?: Approver[];\r\n approvalMode?: \"any\" | \"all\" | \"sequence\";\r\n status?: string;\r\n };\r\n}\r\n\r\nconst props = defineProps<Props>();\r\n\r\nconst showAddMenuFn = inject(\"showAddMenu\") as\r\n | ((position: { x: number; y: number }, nodeId?: string) => void)\r\n | undefined;\r\n\r\nconst deleteNodeFn = inject(\"deleteNode\") as\r\n | ((nodeId: string) => void)\r\n | undefined;\r\n\r\n/* 使用安全的访问方式和默认值 */\r\nconst approvers = computed(() => props.data.approvers ?? []);\r\nconst approverCount = computed(() => approvers.value.length);\r\nconst displayApprovers = computed(() => approvers.value.slice(0, 3));\r\nconst moreCount = computed(() => Math.max(0, approverCount.value - 3));\r\n\r\nconst getApprovalModeText = (mode?: string) => {\r\n const modeMap = {\r\n any: \"或签\",\r\n all: \"会签\",\r\n sequence: \"顺序\",\r\n };\r\n return modeMap[mode as keyof typeof modeMap] || \"\";\r\n};\r\n\r\nconst getDefaultAvatar = (name: string) => {\r\n return `https://ui-avatars.com/api/?name=${encodeURIComponent(name)}&background=random`;\r\n};\r\n\r\nconst handleNodeClick = () => {\r\n /* 不阻止事件冒泡,让 VueFlow 的 node-click 事件自然触发 */\r\n};\r\n\r\nconst showAddMenu = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();\r\n\r\n if (showAddMenuFn) {\r\n showAddMenuFn(\r\n {\r\n x: rect.left + rect.width / 2,\r\n y: rect.bottom + 10,\r\n },\r\n props.id,\r\n );\r\n }\r\n};\r\n\r\nconst handleDelete = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n if (deleteNodeFn) {\r\n deleteNodeFn(props.id);\r\n }\r\n};\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.approval-node {\r\n position: relative;\r\n}\r\n\r\n.status-indicator {\r\n position: absolute;\r\n top: -4px;\r\n right: -4px;\r\n width: 12px;\r\n height: 12px;\r\n border-radius: 50%;\r\n border: 2px solid white;\r\n z-index: 2;\r\n background: #faad14;\r\n\r\n &.approved {\r\n background: #52c41a;\r\n }\r\n &.rejected {\r\n background: #ff4d4f;\r\n }\r\n &.pending {\r\n background: #1890ff;\r\n }\r\n}\r\n\r\n.delete-btn {\r\n position: absolute;\r\n top: -10px;\r\n right: -10px;\r\n width: 24px;\r\n height: 24px;\r\n border-radius: 50%;\r\n background: #ff4d4f;\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n z-index: 100; /* 确保在所有元素之上 */\r\n opacity: 0;\r\n transform: scale(0.8);\r\n transition: all 0.2s ease;\r\n border: 2px solid white;\r\n box-shadow: 0 2px 8px rgba(255, 77, 79, 0.3);\r\n\r\n &:hover {\r\n transform: scale(1);\r\n background: #ff7875;\r\n box-shadow: 0 4px 12px rgba(255, 77, 79, 0.5);\r\n }\r\n\r\n &:active {\r\n transform: scale(0.95);\r\n }\r\n}\r\n\r\n.approval-node:hover .delete-btn {\r\n opacity: 1;\r\n transform: scale(1);\r\n}\r\n\r\n.node-card {\r\n background: white;\r\n border-radius: 12px;\r\n min-width: 240px;\r\n max-width: 300px;\r\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\r\n border: 2px solid transparent;\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n overflow: hidden;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n transform: translateY(-2px);\r\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);\r\n border-color: #1890ff;\r\n }\r\n}\r\n\r\n.node-header {\r\n padding: 12px 16px;\r\n background: linear-gradient(135deg, #e6f7ff, #f0f9ff);\r\n border-bottom: 1px solid #91d5ff;\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n flex-wrap: wrap;\r\n}\r\n\r\n.node-icon {\r\n width: 24px;\r\n height: 24px;\r\n border-radius: 50%;\r\n background: #1890ff;\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.node-title {\r\n font-weight: 600;\r\n color: var(--n-text-color-1);\r\n font-size: 14px;\r\n flex: 1;\r\n}\r\n\r\n.node-badge {\r\n background: #1890ff;\r\n color: white;\r\n padding: 2px 6px;\r\n border-radius: 10px;\r\n font-size: 11px;\r\n font-weight: 600;\r\n min-width: 16px;\r\n text-align: center;\r\n}\r\n\r\n.approval-mode-badge {\r\n background: #f0f0f0;\r\n color: #666;\r\n padding: 2px 6px;\r\n border-radius: 8px;\r\n font-size: 10px;\r\n font-weight: 500;\r\n}\r\n\r\n.node-content {\r\n padding: 16px;\r\n}\r\n\r\n.approvers-list {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 8px;\r\n}\r\n\r\n.approver-tag {\r\n background: linear-gradient(135deg, #e6f7ff, #f0f9ff);\r\n border: 1px solid #91d5ff;\r\n border-radius: 8px;\r\n padding: 8px 12px;\r\n transition: all 0.2s ease;\r\n\r\n &:hover {\r\n transform: translateX(2px);\r\n box-shadow: 0 2px 8px rgba(24, 144, 255, 0.2);\r\n }\r\n\r\n .approver-info {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n\r\n .approver-avatar {\r\n flex-shrink: 0;\r\n }\r\n\r\n .approver-details {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 2px;\r\n min-width: 0;\r\n\r\n .approver-name {\r\n font-weight: 600;\r\n color: #1890ff;\r\n font-size: 13px;\r\n }\r\n\r\n .approver-dept {\r\n font-size: 11px;\r\n color: #666;\r\n background: rgba(0, 0, 0, 0.05);\r\n padding: 1px 4px;\r\n border-radius: 4px;\r\n display: inline-block;\r\n }\r\n\r\n .approver-role {\r\n font-size: 10px;\r\n color: #999;\r\n }\r\n }\r\n }\r\n}\r\n\r\n.more-count {\r\n background: #f0f0f0;\r\n color: #8c8c8c;\r\n padding: 6px 8px;\r\n border-radius: 12px;\r\n font-size: 12px;\r\n font-weight: 500;\r\n text-align: center;\r\n border: 1px dashed #d9d9d9;\r\n}\r\n\r\n.placeholder {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 8px;\r\n color: #8c8c8c;\r\n font-size: 12px;\r\n padding: 20px 0;\r\n border: 1px dashed #d9d9d9;\r\n border-radius: 8px;\r\n background: #fafafa;\r\n\r\n span {\r\n font-weight: 500;\r\n }\r\n}\r\n\r\n.add-node-btn {\r\n position: absolute;\r\n left: 50%;\r\n bottom: -20px;\r\n transform: translateX(-50%);\r\n width: 32px;\r\n height: 32px;\r\n border-radius: 50%;\r\n background: linear-gradient(135deg, #1890ff, #722ed1);\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n z-index: 10;\r\n\r\n &:hover {\r\n transform: translateX(-50%) scale(1.1);\r\n box-shadow: 0 6px 20px rgba(24, 144, 255, 0.4);\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Description: 审批节点组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"approval-node\">\r\n <div class=\"status-indicator\" :class=\"data.status\"></div>\r\n\r\n <!-- 删除按钮 -->\r\n <div class=\"delete-btn\" @click=\"handleDelete\" title=\"删除节点\">\r\n <C_Icon name=\"mdi:close\" :size=\"12\" />\r\n </div>\r\n\r\n <div class=\"node-card\" @click=\"handleNodeClick\">\r\n <div class=\"node-header\">\r\n <div class=\"node-icon\">\r\n <C_Icon name=\"mdi:account\" :size=\"12\" color=\"white\" />\r\n </div>\r\n <span class=\"node-title\">{{ data.title }}</span>\r\n <div class=\"node-badge\" v-if=\"approverCount > 0\">\r\n {{ approverCount }}\r\n </div>\r\n <div class=\"approval-mode-badge\" v-if=\"data.approvalMode\">\r\n {{ getApprovalModeText(data.approvalMode) }}\r\n </div>\r\n </div>\r\n\r\n <div class=\"node-content\">\r\n <div v-if=\"approverCount > 0\" class=\"approvers-list\">\r\n <div\r\n v-for=\"approver in displayApprovers\"\r\n :key=\"approver.id\"\r\n class=\"approver-tag\"\r\n >\r\n <div class=\"approver-info\">\r\n <div class=\"approver-avatar\">\r\n <NAvatar\r\n :src=\"approver.avatar\"\r\n :fallback-src=\"getDefaultAvatar(approver.name)\"\r\n size=\"small\"\r\n />\r\n </div>\r\n <div class=\"approver-details\">\r\n <span class=\"approver-name\">{{ approver.name }}</span>\r\n <span class=\"approver-dept\">{{ approver.department }}</span>\r\n <span class=\"approver-role\">{{ approver.role }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n <div v-if=\"moreCount > 0\" class=\"more-count\">+{{ moreCount }}</div>\r\n </div>\r\n <div v-else class=\"placeholder\">\r\n <C_Icon name=\"mdi:account-plus\" :size=\"24\" color=\"#9ca3af\" />\r\n <span>请设置审批人</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"add-node-btn\" @click=\"showAddMenu\" title=\"添加下一个节点\">\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, inject } from \"vue\";\r\nimport { NAvatar } from \"naive-ui\";\r\nimport { C_Icon } from \"../../C_Icon\";\r\n\r\n/* 定义审批人类型 */\r\ninterface Approver {\r\n id: string;\r\n name: string;\r\n avatar?: string;\r\n department: string;\r\n role: string;\r\n email?: string;\r\n phone?: string;\r\n}\r\n\r\ninterface Props {\r\n id: string;\r\n data: {\r\n title: string;\r\n approvers?: Approver[];\r\n approvalMode?: \"any\" | \"all\" | \"sequence\";\r\n status?: string;\r\n };\r\n}\r\n\r\nconst props = defineProps<Props>();\r\n\r\nconst showAddMenuFn = inject(\"showAddMenu\") as\r\n | ((position: { x: number; y: number }, nodeId?: string) => void)\r\n | undefined;\r\n\r\nconst deleteNodeFn = inject(\"deleteNode\") as\r\n | ((nodeId: string) => void)\r\n | undefined;\r\n\r\n/* 使用安全的访问方式和默认值 */\r\nconst approvers = computed(() => props.data.approvers ?? []);\r\nconst approverCount = computed(() => approvers.value.length);\r\nconst displayApprovers = computed(() => approvers.value.slice(0, 3));\r\nconst moreCount = computed(() => Math.max(0, approverCount.value - 3));\r\n\r\nconst getApprovalModeText = (mode?: string) => {\r\n const modeMap = {\r\n any: \"或签\",\r\n all: \"会签\",\r\n sequence: \"顺序\",\r\n };\r\n return modeMap[mode as keyof typeof modeMap] || \"\";\r\n};\r\n\r\nconst getDefaultAvatar = (name: string) => {\r\n return `https://ui-avatars.com/api/?name=${encodeURIComponent(name)}&background=random`;\r\n};\r\n\r\nconst handleNodeClick = () => {\r\n /* 不阻止事件冒泡,让 VueFlow 的 node-click 事件自然触发 */\r\n};\r\n\r\nconst showAddMenu = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();\r\n\r\n if (showAddMenuFn) {\r\n showAddMenuFn(\r\n {\r\n x: rect.left + rect.width / 2,\r\n y: rect.bottom + 10,\r\n },\r\n props.id,\r\n );\r\n }\r\n};\r\n\r\nconst handleDelete = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n if (deleteNodeFn) {\r\n deleteNodeFn(props.id);\r\n }\r\n};\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.approval-node {\r\n position: relative;\r\n}\r\n\r\n.status-indicator {\r\n position: absolute;\r\n top: -4px;\r\n right: -4px;\r\n width: 12px;\r\n height: 12px;\r\n border-radius: 50%;\r\n border: 2px solid white;\r\n z-index: 2;\r\n background: #faad14;\r\n\r\n &.approved {\r\n background: #52c41a;\r\n }\r\n &.rejected {\r\n background: #ff4d4f;\r\n }\r\n &.pending {\r\n background: #1890ff;\r\n }\r\n}\r\n\r\n.delete-btn {\r\n position: absolute;\r\n top: -10px;\r\n right: -10px;\r\n width: 24px;\r\n height: 24px;\r\n border-radius: 50%;\r\n background: #ff4d4f;\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n z-index: 100; /* 确保在所有元素之上 */\r\n opacity: 0;\r\n transform: scale(0.8);\r\n transition: all 0.2s ease;\r\n border: 2px solid white;\r\n box-shadow: 0 2px 8px rgba(255, 77, 79, 0.3);\r\n\r\n &:hover {\r\n transform: scale(1);\r\n background: #ff7875;\r\n box-shadow: 0 4px 12px rgba(255, 77, 79, 0.5);\r\n }\r\n\r\n &:active {\r\n transform: scale(0.95);\r\n }\r\n}\r\n\r\n.approval-node:hover .delete-btn {\r\n opacity: 1;\r\n transform: scale(1);\r\n}\r\n\r\n.node-card {\r\n background: white;\r\n border-radius: 12px;\r\n min-width: 240px;\r\n max-width: 300px;\r\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\r\n border: 2px solid transparent;\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n overflow: hidden;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n transform: translateY(-2px);\r\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);\r\n border-color: #1890ff;\r\n }\r\n}\r\n\r\n.node-header {\r\n padding: 12px 16px;\r\n background: linear-gradient(135deg, #e6f7ff, #f0f9ff);\r\n border-bottom: 1px solid #91d5ff;\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n flex-wrap: wrap;\r\n}\r\n\r\n.node-icon {\r\n width: 24px;\r\n height: 24px;\r\n border-radius: 50%;\r\n background: #1890ff;\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.node-title {\r\n font-weight: 600;\r\n color: var(--n-text-color-1);\r\n font-size: 14px;\r\n flex: 1;\r\n}\r\n\r\n.node-badge {\r\n background: #1890ff;\r\n color: white;\r\n padding: 2px 6px;\r\n border-radius: 10px;\r\n font-size: 11px;\r\n font-weight: 600;\r\n min-width: 16px;\r\n text-align: center;\r\n}\r\n\r\n.approval-mode-badge {\r\n background: #f0f0f0;\r\n color: #666;\r\n padding: 2px 6px;\r\n border-radius: 8px;\r\n font-size: 10px;\r\n font-weight: 500;\r\n}\r\n\r\n.node-content {\r\n padding: 16px;\r\n}\r\n\r\n.approvers-list {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 8px;\r\n}\r\n\r\n.approver-tag {\r\n background: linear-gradient(135deg, #e6f7ff, #f0f9ff);\r\n border: 1px solid #91d5ff;\r\n border-radius: 8px;\r\n padding: 8px 12px;\r\n transition: all 0.2s ease;\r\n\r\n &:hover {\r\n transform: translateX(2px);\r\n box-shadow: 0 2px 8px rgba(24, 144, 255, 0.2);\r\n }\r\n\r\n .approver-info {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n\r\n .approver-avatar {\r\n flex-shrink: 0;\r\n }\r\n\r\n .approver-details {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 2px;\r\n min-width: 0;\r\n\r\n .approver-name {\r\n font-weight: 600;\r\n color: #1890ff;\r\n font-size: 13px;\r\n }\r\n\r\n .approver-dept {\r\n font-size: 11px;\r\n color: #666;\r\n background: rgba(0, 0, 0, 0.05);\r\n padding: 1px 4px;\r\n border-radius: 4px;\r\n display: inline-block;\r\n }\r\n\r\n .approver-role {\r\n font-size: 10px;\r\n color: #999;\r\n }\r\n }\r\n }\r\n}\r\n\r\n.more-count {\r\n background: #f0f0f0;\r\n color: #8c8c8c;\r\n padding: 6px 8px;\r\n border-radius: 12px;\r\n font-size: 12px;\r\n font-weight: 500;\r\n text-align: center;\r\n border: 1px dashed #d9d9d9;\r\n}\r\n\r\n.placeholder {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 8px;\r\n color: #8c8c8c;\r\n font-size: 12px;\r\n padding: 20px 0;\r\n border: 1px dashed #d9d9d9;\r\n border-radius: 8px;\r\n background: #fafafa;\r\n\r\n span {\r\n font-weight: 500;\r\n }\r\n}\r\n\r\n.add-node-btn {\r\n position: absolute;\r\n left: 50%;\r\n bottom: -20px;\r\n transform: translateX(-50%);\r\n width: 32px;\r\n height: 32px;\r\n border-radius: 50%;\r\n background: linear-gradient(135deg, #1890ff, #722ed1);\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n z-index: 10;\r\n\r\n &:hover {\r\n transform: translateX(-50%) scale(1.1);\r\n box-shadow: 0 6px 20px rgba(24, 144, 255, 0.4);\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Description: 审批节点组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"approval-node\">\r\n <div class=\"status-indicator\" :class=\"data.status\"></div>\r\n\r\n <!-- 删除按钮 -->\r\n <div class=\"delete-btn\" @click=\"handleDelete\" title=\"删除节点\">\r\n <C_Icon name=\"mdi:close\" :size=\"12\" />\r\n </div>\r\n\r\n <div class=\"node-card\" @click=\"handleNodeClick\">\r\n <div class=\"node-header\">\r\n <div class=\"node-icon\">\r\n <C_Icon name=\"mdi:account\" :size=\"12\" color=\"white\" />\r\n </div>\r\n <span class=\"node-title\">{{ data.title }}</span>\r\n <div class=\"node-badge\" v-if=\"approverCount > 0\">\r\n {{ approverCount }}\r\n </div>\r\n <div class=\"approval-mode-badge\" v-if=\"data.approvalMode\">\r\n {{ getApprovalModeText(data.approvalMode) }}\r\n </div>\r\n </div>\r\n\r\n <div class=\"node-content\">\r\n <div v-if=\"approverCount > 0\" class=\"approvers-list\">\r\n <div\r\n v-for=\"approver in displayApprovers\"\r\n :key=\"approver.id\"\r\n class=\"approver-tag\"\r\n >\r\n <div class=\"approver-info\">\r\n <div class=\"approver-avatar\">\r\n <NAvatar\r\n :src=\"approver.avatar\"\r\n :fallback-src=\"getDefaultAvatar(approver.name)\"\r\n size=\"small\"\r\n />\r\n </div>\r\n <div class=\"approver-details\">\r\n <span class=\"approver-name\">{{ approver.name }}</span>\r\n <span class=\"approver-dept\">{{ approver.department }}</span>\r\n <span class=\"approver-role\">{{ approver.role }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n <div v-if=\"moreCount > 0\" class=\"more-count\">+{{ moreCount }}</div>\r\n </div>\r\n <div v-else class=\"placeholder\">\r\n <C_Icon name=\"mdi:account-plus\" :size=\"24\" color=\"#9ca3af\" />\r\n <span>请设置审批人</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"add-node-btn\" @click=\"showAddMenu\" title=\"添加下一个节点\">\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, inject } from \"vue\";\r\nimport { NAvatar } from \"naive-ui\";\r\nimport { C_Icon } from \"../../C_Icon\";\r\n\r\n/* 定义审批人类型 */\r\ninterface Approver {\r\n id: string;\r\n name: string;\r\n avatar?: string;\r\n department: string;\r\n role: string;\r\n email?: string;\r\n phone?: string;\r\n}\r\n\r\ninterface Props {\r\n id: string;\r\n data: {\r\n title: string;\r\n approvers?: Approver[];\r\n approvalMode?: \"any\" | \"all\" | \"sequence\";\r\n status?: string;\r\n };\r\n}\r\n\r\nconst props = defineProps<Props>();\r\n\r\nconst showAddMenuFn = inject(\"showAddMenu\") as\r\n | ((position: { x: number; y: number }, nodeId?: string) => void)\r\n | undefined;\r\n\r\nconst deleteNodeFn = inject(\"deleteNode\") as\r\n | ((nodeId: string) => void)\r\n | undefined;\r\n\r\n/* 使用安全的访问方式和默认值 */\r\nconst approvers = computed(() => props.data.approvers ?? []);\r\nconst approverCount = computed(() => approvers.value.length);\r\nconst displayApprovers = computed(() => approvers.value.slice(0, 3));\r\nconst moreCount = computed(() => Math.max(0, approverCount.value - 3));\r\n\r\nconst getApprovalModeText = (mode?: string) => {\r\n const modeMap = {\r\n any: \"或签\",\r\n all: \"会签\",\r\n sequence: \"顺序\",\r\n };\r\n return modeMap[mode as keyof typeof modeMap] || \"\";\r\n};\r\n\r\nconst getDefaultAvatar = (name: string) => {\r\n return `https://ui-avatars.com/api/?name=${encodeURIComponent(name)}&background=random`;\r\n};\r\n\r\nconst handleNodeClick = () => {\r\n /* 不阻止事件冒泡,让 VueFlow 的 node-click 事件自然触发 */\r\n};\r\n\r\nconst showAddMenu = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();\r\n\r\n if (showAddMenuFn) {\r\n showAddMenuFn(\r\n {\r\n x: rect.left + rect.width / 2,\r\n y: rect.bottom + 10,\r\n },\r\n props.id,\r\n );\r\n }\r\n};\r\n\r\nconst handleDelete = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n if (deleteNodeFn) {\r\n deleteNodeFn(props.id);\r\n }\r\n};\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.approval-node {\r\n position: relative;\r\n}\r\n\r\n.status-indicator {\r\n position: absolute;\r\n top: -4px;\r\n right: -4px;\r\n width: 12px;\r\n height: 12px;\r\n border-radius: 50%;\r\n border: 2px solid white;\r\n z-index: 2;\r\n background: #faad14;\r\n\r\n &.approved {\r\n background: #52c41a;\r\n }\r\n &.rejected {\r\n background: #ff4d4f;\r\n }\r\n &.pending {\r\n background: #1890ff;\r\n }\r\n}\r\n\r\n.delete-btn {\r\n position: absolute;\r\n top: -10px;\r\n right: -10px;\r\n width: 24px;\r\n height: 24px;\r\n border-radius: 50%;\r\n background: #ff4d4f;\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n z-index: 100; /* 确保在所有元素之上 */\r\n opacity: 0;\r\n transform: scale(0.8);\r\n transition: all 0.2s ease;\r\n border: 2px solid white;\r\n box-shadow: 0 2px 8px rgba(255, 77, 79, 0.3);\r\n\r\n &:hover {\r\n transform: scale(1);\r\n background: #ff7875;\r\n box-shadow: 0 4px 12px rgba(255, 77, 79, 0.5);\r\n }\r\n\r\n &:active {\r\n transform: scale(0.95);\r\n }\r\n}\r\n\r\n.approval-node:hover .delete-btn {\r\n opacity: 1;\r\n transform: scale(1);\r\n}\r\n\r\n.node-card {\r\n background: white;\r\n border-radius: 12px;\r\n min-width: 240px;\r\n max-width: 300px;\r\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\r\n border: 2px solid transparent;\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n overflow: hidden;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n transform: translateY(-2px);\r\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);\r\n border-color: #1890ff;\r\n }\r\n}\r\n\r\n.node-header {\r\n padding: 12px 16px;\r\n background: linear-gradient(135deg, #e6f7ff, #f0f9ff);\r\n border-bottom: 1px solid #91d5ff;\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n flex-wrap: wrap;\r\n}\r\n\r\n.node-icon {\r\n width: 24px;\r\n height: 24px;\r\n border-radius: 50%;\r\n background: #1890ff;\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.node-title {\r\n font-weight: 600;\r\n color: var(--n-text-color-1);\r\n font-size: 14px;\r\n flex: 1;\r\n}\r\n\r\n.node-badge {\r\n background: #1890ff;\r\n color: white;\r\n padding: 2px 6px;\r\n border-radius: 10px;\r\n font-size: 11px;\r\n font-weight: 600;\r\n min-width: 16px;\r\n text-align: center;\r\n}\r\n\r\n.approval-mode-badge {\r\n background: #f0f0f0;\r\n color: #666;\r\n padding: 2px 6px;\r\n border-radius: 8px;\r\n font-size: 10px;\r\n font-weight: 500;\r\n}\r\n\r\n.node-content {\r\n padding: 16px;\r\n}\r\n\r\n.approvers-list {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 8px;\r\n}\r\n\r\n.approver-tag {\r\n background: linear-gradient(135deg, #e6f7ff, #f0f9ff);\r\n border: 1px solid #91d5ff;\r\n border-radius: 8px;\r\n padding: 8px 12px;\r\n transition: all 0.2s ease;\r\n\r\n &:hover {\r\n transform: translateX(2px);\r\n box-shadow: 0 2px 8px rgba(24, 144, 255, 0.2);\r\n }\r\n\r\n .approver-info {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n\r\n .approver-avatar {\r\n flex-shrink: 0;\r\n }\r\n\r\n .approver-details {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 2px;\r\n min-width: 0;\r\n\r\n .approver-name {\r\n font-weight: 600;\r\n color: #1890ff;\r\n font-size: 13px;\r\n }\r\n\r\n .approver-dept {\r\n font-size: 11px;\r\n color: #666;\r\n background: rgba(0, 0, 0, 0.05);\r\n padding: 1px 4px;\r\n border-radius: 4px;\r\n display: inline-block;\r\n }\r\n\r\n .approver-role {\r\n font-size: 10px;\r\n color: #999;\r\n }\r\n }\r\n }\r\n}\r\n\r\n.more-count {\r\n background: #f0f0f0;\r\n color: #8c8c8c;\r\n padding: 6px 8px;\r\n border-radius: 12px;\r\n font-size: 12px;\r\n font-weight: 500;\r\n text-align: center;\r\n border: 1px dashed #d9d9d9;\r\n}\r\n\r\n.placeholder {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 8px;\r\n color: #8c8c8c;\r\n font-size: 12px;\r\n padding: 20px 0;\r\n border: 1px dashed #d9d9d9;\r\n border-radius: 8px;\r\n background: #fafafa;\r\n\r\n span {\r\n font-weight: 500;\r\n }\r\n}\r\n\r\n.add-node-btn {\r\n position: absolute;\r\n left: 50%;\r\n bottom: -20px;\r\n transform: translateX(-50%);\r\n width: 32px;\r\n height: 32px;\r\n border-radius: 50%;\r\n background: linear-gradient(135deg, #1890ff, #722ed1);\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n z-index: 10;\r\n\r\n &:hover {\r\n transform: translateX(-50%) scale(1.1);\r\n box-shadow: 0 6px 20px rgba(24, 144, 255, 0.4);\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Description: 抄送节点组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"copy-node\">\r\n <div class=\"status-indicator\" :class=\"data.status\"></div>\r\n\r\n <!-- 删除按钮 -->\r\n <div class=\"delete-btn\" @click=\"handleDelete\" title=\"删除节点\">\r\n <C_Icon name=\"mdi:close\" :size=\"12\" />\r\n </div>\r\n\r\n <div class=\"node-card\" @click=\"handleNodeClick\">\r\n <div class=\"node-header\">\r\n <div class=\"node-icon\">📋</div>\r\n <span class=\"node-title\">{{ data.title }}</span>\r\n <div class=\"node-badge\" v-if=\"copyCount > 0\">{{ copyCount }}</div>\r\n </div>\r\n\r\n <div class=\"node-content\">\r\n <div v-if=\"copyCount > 0\" class=\"copy-users-list\">\r\n <div\r\n v-for=\"user in displayCopyUsers\"\r\n :key=\"user.id\"\r\n class=\"copy-user-tag\"\r\n >\r\n {{ user.name }}\r\n </div>\r\n <div v-if=\"moreCount > 0\" class=\"more-count\">+{{ moreCount }}</div>\r\n </div>\r\n <div v-else class=\"placeholder\">请设置抄送人</div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"add-node-btn\" @click=\"showAddMenu\" title=\"添加下一个节点\">\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, inject } from \"vue\";\r\nimport { C_Icon } from \"../../C_Icon\";\r\n\r\ninterface User {\r\n id: string;\r\n name: string;\r\n department: string;\r\n role: string;\r\n}\r\n\r\ninterface Props {\r\n id: string;\r\n data: {\r\n title: string;\r\n copyUsers?: User[];\r\n status?: string;\r\n };\r\n}\r\n\r\nconst props = defineProps<Props>();\r\n\r\nconst showAddMenuFn = inject(\"showAddMenu\") as\r\n | ((position: { x: number; y: number }, nodeId?: string) => void)\r\n | undefined;\r\n\r\nconst deleteNodeFn = inject(\"deleteNode\") as\r\n | ((nodeId: string) => void)\r\n | undefined;\r\n\r\n/* 统一使用安全的访问方式 */\r\nconst copyUsers = computed(() => props.data.copyUsers ?? []);\r\nconst copyCount = computed(() => copyUsers.value.length);\r\nconst displayCopyUsers = computed(() => copyUsers.value.slice(0, 3));\r\nconst moreCount = computed(() => Math.max(0, copyCount.value - 3));\r\n\r\nconst handleNodeClick = () => {\r\n /* 不阻止事件冒泡,让 VueFlow 的 node-click 事件自然触发 */\r\n};\r\n\r\nconst showAddMenu = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();\r\n\r\n if (showAddMenuFn) {\r\n showAddMenuFn(\r\n {\r\n x: rect.left + rect.width / 2,\r\n y: rect.bottom + 10,\r\n },\r\n props.id,\r\n );\r\n }\r\n};\r\n\r\nconst handleDelete = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n if (deleteNodeFn) {\r\n deleteNodeFn(props.id);\r\n }\r\n};\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.copy-node {\r\n position: relative;\r\n}\r\n\r\n.status-indicator {\r\n position: absolute;\r\n top: -4px;\r\n right: -4px;\r\n width: 12px;\r\n height: 12px;\r\n border-radius: 50%;\r\n border: 2px solid white;\r\n z-index: 2;\r\n background: #faad14;\r\n\r\n &.completed {\r\n background: #52c41a;\r\n }\r\n &.active {\r\n background: #1890ff;\r\n }\r\n}\r\n\r\n.delete-btn {\r\n position: absolute;\r\n top: -10px;\r\n right: -10px;\r\n width: 24px;\r\n height: 24px;\r\n border-radius: 50%;\r\n background: #ff4d4f;\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n z-index: 100;\r\n opacity: 0;\r\n transform: scale(0.8);\r\n transition: all 0.2s ease;\r\n border: 2px solid white;\r\n box-shadow: 0 2px 8px rgba(255, 77, 79, 0.3);\r\n\r\n &:hover {\r\n transform: scale(1);\r\n background: #ff7875;\r\n box-shadow: 0 4px 12px rgba(255, 77, 79, 0.5);\r\n }\r\n\r\n &:active {\r\n transform: scale(0.95);\r\n }\r\n}\r\n\r\n.copy-node:hover .delete-btn {\r\n opacity: 1;\r\n transform: scale(1);\r\n}\r\n\r\n.node-card {\r\n background: white;\r\n border-radius: 12px;\r\n min-width: 200px;\r\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\r\n border: 2px solid transparent;\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n overflow: hidden;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n transform: translateY(-2px);\r\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);\r\n border-color: #52c41a;\r\n }\r\n}\r\n\r\n.node-header {\r\n padding: 12px 16px;\r\n background: #f6ffed;\r\n border-bottom: 1px solid #d9f7be;\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n}\r\n\r\n.node-icon {\r\n width: 24px;\r\n height: 24px;\r\n border-radius: 50%;\r\n background: #52c41a;\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-size: 12px;\r\n}\r\n\r\n.node-title {\r\n font-weight: 600;\r\n color: #262626;\r\n font-size: 14px;\r\n flex: 1;\r\n}\r\n\r\n.node-badge {\r\n background: #52c41a;\r\n color: white;\r\n padding: 2px 6px;\r\n border-radius: 10px;\r\n font-size: 11px;\r\n font-weight: 600;\r\n min-width: 16px;\r\n text-align: center;\r\n}\r\n\r\n.node-content {\r\n padding: 16px;\r\n}\r\n\r\n.copy-users-list {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 6px;\r\n align-items: center;\r\n}\r\n\r\n.copy-user-tag {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n background: #f6ffed;\r\n color: #52c41a;\r\n padding: 4px 8px;\r\n border-radius: 12px;\r\n font-size: 12px;\r\n border: 1px solid #b7eb8f;\r\n}\r\n\r\n.more-count {\r\n background: #f0f0f0;\r\n color: #8c8c8c;\r\n padding: 4px 8px;\r\n border-radius: 12px;\r\n font-size: 12px;\r\n font-weight: 500;\r\n}\r\n\r\n.placeholder {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n color: #8c8c8c;\r\n font-size: 12px;\r\n padding: 8px 0;\r\n}\r\n\r\n.add-node-btn {\r\n position: absolute;\r\n left: 50%;\r\n bottom: -20px;\r\n transform: translateX(-50%);\r\n width: 32px;\r\n height: 32px;\r\n border-radius: 50%;\r\n background: linear-gradient(135deg, #52c41a, #389e0d);\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n box-shadow: 0 4px 12px rgba(82, 196, 26, 0.3);\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n font-size: 16px;\r\n font-weight: bold;\r\n z-index: 10;\r\n\r\n &:hover {\r\n transform: translateX(-50%) scale(1.1);\r\n box-shadow: 0 6px 20px rgba(82, 196, 26, 0.4);\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Description: 抄送节点组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"copy-node\">\r\n <div class=\"status-indicator\" :class=\"data.status\"></div>\r\n\r\n <!-- 删除按钮 -->\r\n <div class=\"delete-btn\" @click=\"handleDelete\" title=\"删除节点\">\r\n <C_Icon name=\"mdi:close\" :size=\"12\" />\r\n </div>\r\n\r\n <div class=\"node-card\" @click=\"handleNodeClick\">\r\n <div class=\"node-header\">\r\n <div class=\"node-icon\">📋</div>\r\n <span class=\"node-title\">{{ data.title }}</span>\r\n <div class=\"node-badge\" v-if=\"copyCount > 0\">{{ copyCount }}</div>\r\n </div>\r\n\r\n <div class=\"node-content\">\r\n <div v-if=\"copyCount > 0\" class=\"copy-users-list\">\r\n <div\r\n v-for=\"user in displayCopyUsers\"\r\n :key=\"user.id\"\r\n class=\"copy-user-tag\"\r\n >\r\n {{ user.name }}\r\n </div>\r\n <div v-if=\"moreCount > 0\" class=\"more-count\">+{{ moreCount }}</div>\r\n </div>\r\n <div v-else class=\"placeholder\">请设置抄送人</div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"add-node-btn\" @click=\"showAddMenu\" title=\"添加下一个节点\">\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, inject } from \"vue\";\r\nimport { C_Icon } from \"../../C_Icon\";\r\n\r\ninterface User {\r\n id: string;\r\n name: string;\r\n department: string;\r\n role: string;\r\n}\r\n\r\ninterface Props {\r\n id: string;\r\n data: {\r\n title: string;\r\n copyUsers?: User[];\r\n status?: string;\r\n };\r\n}\r\n\r\nconst props = defineProps<Props>();\r\n\r\nconst showAddMenuFn = inject(\"showAddMenu\") as\r\n | ((position: { x: number; y: number }, nodeId?: string) => void)\r\n | undefined;\r\n\r\nconst deleteNodeFn = inject(\"deleteNode\") as\r\n | ((nodeId: string) => void)\r\n | undefined;\r\n\r\n/* 统一使用安全的访问方式 */\r\nconst copyUsers = computed(() => props.data.copyUsers ?? []);\r\nconst copyCount = computed(() => copyUsers.value.length);\r\nconst displayCopyUsers = computed(() => copyUsers.value.slice(0, 3));\r\nconst moreCount = computed(() => Math.max(0, copyCount.value - 3));\r\n\r\nconst handleNodeClick = () => {\r\n /* 不阻止事件冒泡,让 VueFlow 的 node-click 事件自然触发 */\r\n};\r\n\r\nconst showAddMenu = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();\r\n\r\n if (showAddMenuFn) {\r\n showAddMenuFn(\r\n {\r\n x: rect.left + rect.width / 2,\r\n y: rect.bottom + 10,\r\n },\r\n props.id,\r\n );\r\n }\r\n};\r\n\r\nconst handleDelete = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n if (deleteNodeFn) {\r\n deleteNodeFn(props.id);\r\n }\r\n};\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.copy-node {\r\n position: relative;\r\n}\r\n\r\n.status-indicator {\r\n position: absolute;\r\n top: -4px;\r\n right: -4px;\r\n width: 12px;\r\n height: 12px;\r\n border-radius: 50%;\r\n border: 2px solid white;\r\n z-index: 2;\r\n background: #faad14;\r\n\r\n &.completed {\r\n background: #52c41a;\r\n }\r\n &.active {\r\n background: #1890ff;\r\n }\r\n}\r\n\r\n.delete-btn {\r\n position: absolute;\r\n top: -10px;\r\n right: -10px;\r\n width: 24px;\r\n height: 24px;\r\n border-radius: 50%;\r\n background: #ff4d4f;\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n z-index: 100;\r\n opacity: 0;\r\n transform: scale(0.8);\r\n transition: all 0.2s ease;\r\n border: 2px solid white;\r\n box-shadow: 0 2px 8px rgba(255, 77, 79, 0.3);\r\n\r\n &:hover {\r\n transform: scale(1);\r\n background: #ff7875;\r\n box-shadow: 0 4px 12px rgba(255, 77, 79, 0.5);\r\n }\r\n\r\n &:active {\r\n transform: scale(0.95);\r\n }\r\n}\r\n\r\n.copy-node:hover .delete-btn {\r\n opacity: 1;\r\n transform: scale(1);\r\n}\r\n\r\n.node-card {\r\n background: white;\r\n border-radius: 12px;\r\n min-width: 200px;\r\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\r\n border: 2px solid transparent;\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n overflow: hidden;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n transform: translateY(-2px);\r\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);\r\n border-color: #52c41a;\r\n }\r\n}\r\n\r\n.node-header {\r\n padding: 12px 16px;\r\n background: #f6ffed;\r\n border-bottom: 1px solid #d9f7be;\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n}\r\n\r\n.node-icon {\r\n width: 24px;\r\n height: 24px;\r\n border-radius: 50%;\r\n background: #52c41a;\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-size: 12px;\r\n}\r\n\r\n.node-title {\r\n font-weight: 600;\r\n color: #262626;\r\n font-size: 14px;\r\n flex: 1;\r\n}\r\n\r\n.node-badge {\r\n background: #52c41a;\r\n color: white;\r\n padding: 2px 6px;\r\n border-radius: 10px;\r\n font-size: 11px;\r\n font-weight: 600;\r\n min-width: 16px;\r\n text-align: center;\r\n}\r\n\r\n.node-content {\r\n padding: 16px;\r\n}\r\n\r\n.copy-users-list {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 6px;\r\n align-items: center;\r\n}\r\n\r\n.copy-user-tag {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n background: #f6ffed;\r\n color: #52c41a;\r\n padding: 4px 8px;\r\n border-radius: 12px;\r\n font-size: 12px;\r\n border: 1px solid #b7eb8f;\r\n}\r\n\r\n.more-count {\r\n background: #f0f0f0;\r\n color: #8c8c8c;\r\n padding: 4px 8px;\r\n border-radius: 12px;\r\n font-size: 12px;\r\n font-weight: 500;\r\n}\r\n\r\n.placeholder {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n color: #8c8c8c;\r\n font-size: 12px;\r\n padding: 8px 0;\r\n}\r\n\r\n.add-node-btn {\r\n position: absolute;\r\n left: 50%;\r\n bottom: -20px;\r\n transform: translateX(-50%);\r\n width: 32px;\r\n height: 32px;\r\n border-radius: 50%;\r\n background: linear-gradient(135deg, #52c41a, #389e0d);\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n box-shadow: 0 4px 12px rgba(82, 196, 26, 0.3);\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n font-size: 16px;\r\n font-weight: bold;\r\n z-index: 10;\r\n\r\n &:hover {\r\n transform: translateX(-50%) scale(1.1);\r\n box-shadow: 0 6px 20px rgba(82, 196, 26, 0.4);\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Description: 抄送节点组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"copy-node\">\r\n <div class=\"status-indicator\" :class=\"data.status\"></div>\r\n\r\n <!-- 删除按钮 -->\r\n <div class=\"delete-btn\" @click=\"handleDelete\" title=\"删除节点\">\r\n <C_Icon name=\"mdi:close\" :size=\"12\" />\r\n </div>\r\n\r\n <div class=\"node-card\" @click=\"handleNodeClick\">\r\n <div class=\"node-header\">\r\n <div class=\"node-icon\">📋</div>\r\n <span class=\"node-title\">{{ data.title }}</span>\r\n <div class=\"node-badge\" v-if=\"copyCount > 0\">{{ copyCount }}</div>\r\n </div>\r\n\r\n <div class=\"node-content\">\r\n <div v-if=\"copyCount > 0\" class=\"copy-users-list\">\r\n <div\r\n v-for=\"user in displayCopyUsers\"\r\n :key=\"user.id\"\r\n class=\"copy-user-tag\"\r\n >\r\n {{ user.name }}\r\n </div>\r\n <div v-if=\"moreCount > 0\" class=\"more-count\">+{{ moreCount }}</div>\r\n </div>\r\n <div v-else class=\"placeholder\">请设置抄送人</div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"add-node-btn\" @click=\"showAddMenu\" title=\"添加下一个节点\">\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, inject } from \"vue\";\r\nimport { C_Icon } from \"../../C_Icon\";\r\n\r\ninterface User {\r\n id: string;\r\n name: string;\r\n department: string;\r\n role: string;\r\n}\r\n\r\ninterface Props {\r\n id: string;\r\n data: {\r\n title: string;\r\n copyUsers?: User[];\r\n status?: string;\r\n };\r\n}\r\n\r\nconst props = defineProps<Props>();\r\n\r\nconst showAddMenuFn = inject(\"showAddMenu\") as\r\n | ((position: { x: number; y: number }, nodeId?: string) => void)\r\n | undefined;\r\n\r\nconst deleteNodeFn = inject(\"deleteNode\") as\r\n | ((nodeId: string) => void)\r\n | undefined;\r\n\r\n/* 统一使用安全的访问方式 */\r\nconst copyUsers = computed(() => props.data.copyUsers ?? []);\r\nconst copyCount = computed(() => copyUsers.value.length);\r\nconst displayCopyUsers = computed(() => copyUsers.value.slice(0, 3));\r\nconst moreCount = computed(() => Math.max(0, copyCount.value - 3));\r\n\r\nconst handleNodeClick = () => {\r\n /* 不阻止事件冒泡,让 VueFlow 的 node-click 事件自然触发 */\r\n};\r\n\r\nconst showAddMenu = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();\r\n\r\n if (showAddMenuFn) {\r\n showAddMenuFn(\r\n {\r\n x: rect.left + rect.width / 2,\r\n y: rect.bottom + 10,\r\n },\r\n props.id,\r\n );\r\n }\r\n};\r\n\r\nconst handleDelete = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n if (deleteNodeFn) {\r\n deleteNodeFn(props.id);\r\n }\r\n};\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.copy-node {\r\n position: relative;\r\n}\r\n\r\n.status-indicator {\r\n position: absolute;\r\n top: -4px;\r\n right: -4px;\r\n width: 12px;\r\n height: 12px;\r\n border-radius: 50%;\r\n border: 2px solid white;\r\n z-index: 2;\r\n background: #faad14;\r\n\r\n &.completed {\r\n background: #52c41a;\r\n }\r\n &.active {\r\n background: #1890ff;\r\n }\r\n}\r\n\r\n.delete-btn {\r\n position: absolute;\r\n top: -10px;\r\n right: -10px;\r\n width: 24px;\r\n height: 24px;\r\n border-radius: 50%;\r\n background: #ff4d4f;\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n z-index: 100;\r\n opacity: 0;\r\n transform: scale(0.8);\r\n transition: all 0.2s ease;\r\n border: 2px solid white;\r\n box-shadow: 0 2px 8px rgba(255, 77, 79, 0.3);\r\n\r\n &:hover {\r\n transform: scale(1);\r\n background: #ff7875;\r\n box-shadow: 0 4px 12px rgba(255, 77, 79, 0.5);\r\n }\r\n\r\n &:active {\r\n transform: scale(0.95);\r\n }\r\n}\r\n\r\n.copy-node:hover .delete-btn {\r\n opacity: 1;\r\n transform: scale(1);\r\n}\r\n\r\n.node-card {\r\n background: white;\r\n border-radius: 12px;\r\n min-width: 200px;\r\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\r\n border: 2px solid transparent;\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n overflow: hidden;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n transform: translateY(-2px);\r\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);\r\n border-color: #52c41a;\r\n }\r\n}\r\n\r\n.node-header {\r\n padding: 12px 16px;\r\n background: #f6ffed;\r\n border-bottom: 1px solid #d9f7be;\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n}\r\n\r\n.node-icon {\r\n width: 24px;\r\n height: 24px;\r\n border-radius: 50%;\r\n background: #52c41a;\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-size: 12px;\r\n}\r\n\r\n.node-title {\r\n font-weight: 600;\r\n color: #262626;\r\n font-size: 14px;\r\n flex: 1;\r\n}\r\n\r\n.node-badge {\r\n background: #52c41a;\r\n color: white;\r\n padding: 2px 6px;\r\n border-radius: 10px;\r\n font-size: 11px;\r\n font-weight: 600;\r\n min-width: 16px;\r\n text-align: center;\r\n}\r\n\r\n.node-content {\r\n padding: 16px;\r\n}\r\n\r\n.copy-users-list {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 6px;\r\n align-items: center;\r\n}\r\n\r\n.copy-user-tag {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n background: #f6ffed;\r\n color: #52c41a;\r\n padding: 4px 8px;\r\n border-radius: 12px;\r\n font-size: 12px;\r\n border: 1px solid #b7eb8f;\r\n}\r\n\r\n.more-count {\r\n background: #f0f0f0;\r\n color: #8c8c8c;\r\n padding: 4px 8px;\r\n border-radius: 12px;\r\n font-size: 12px;\r\n font-weight: 500;\r\n}\r\n\r\n.placeholder {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n color: #8c8c8c;\r\n font-size: 12px;\r\n padding: 8px 0;\r\n}\r\n\r\n.add-node-btn {\r\n position: absolute;\r\n left: 50%;\r\n bottom: -20px;\r\n transform: translateX(-50%);\r\n width: 32px;\r\n height: 32px;\r\n border-radius: 50%;\r\n background: linear-gradient(135deg, #52c41a, #389e0d);\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n box-shadow: 0 4px 12px rgba(82, 196, 26, 0.3);\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n font-size: 16px;\r\n font-weight: bold;\r\n z-index: 10;\r\n\r\n &:hover {\r\n transform: translateX(-50%) scale(1.1);\r\n box-shadow: 0 6px 20px rgba(82, 196, 26, 0.4);\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Description: 条件节点组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"condition-node\">\r\n <div class=\"status-indicator\" :class=\"data.status\"></div>\r\n\r\n <!-- 删除按钮 -->\r\n <div class=\"delete-btn\" @click=\"handleDelete\" title=\"删除节点\">\r\n <C_Icon name=\"mdi:close\" :size=\"12\" />\r\n </div>\r\n\r\n <div class=\"node-card\">\r\n <div class=\"node-header\">\r\n <div class=\"node-icon\">🔀</div>\r\n <span class=\"node-title\">{{ data.title }}</span>\r\n <div class=\"node-badge\" v-if=\"conditionCount > 0\">\r\n {{ conditionCount }}\r\n </div>\r\n </div>\r\n\r\n <div class=\"node-content\">\r\n <div v-if=\"conditionCount > 0\" class=\"conditions-list\">\r\n <div\r\n v-for=\"condition in displayConditions\"\r\n :key=\"condition.id\"\r\n class=\"condition-tag\"\r\n >\r\n <span class=\"condition-icon\">→</span>\r\n <span>{{\r\n condition.name ||\r\n \"条件\" + (displayConditions.indexOf(condition) + 1)\r\n }}</span>\r\n </div>\r\n <div v-if=\"moreCount > 0\" class=\"more-count\">+{{ moreCount }}</div>\r\n </div>\r\n <div v-else class=\"placeholder\">请设置分支条件</div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"add-node-btn\" @click=\"showAddMenu\" title=\"添加下一个节点\">\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, inject } from \"vue\";\r\nimport { C_Icon } from \"../../C_Icon\";\r\n\r\n/* 定义条件类型 */\r\ninterface Condition {\r\n id: string;\r\n name: string;\r\n operator: \"equals\" | \"not_equals\" | \"greater_than\" | \"less_than\" | \"contains\";\r\n value: string;\r\n}\r\n\r\ninterface Props {\r\n id: string;\r\n data: {\r\n title: string;\r\n conditions?: Condition[];\r\n status?: string;\r\n };\r\n}\r\n\r\nconst props = defineProps<Props>();\r\n\r\nconst showAddMenuFn = inject(\"showAddMenu\") as\r\n | ((position: { x: number; y: number }, nodeId?: string) => void)\r\n | undefined;\r\n\r\nconst deleteNodeFn = inject(\"deleteNode\") as\r\n | ((nodeId: string) => void)\r\n | undefined;\r\n\r\n/* 使用安全的访问方式和默认值 */\r\nconst conditions = computed(() => props.data.conditions ?? []);\r\nconst conditionCount = computed(() => conditions.value.length);\r\nconst displayConditions = computed(() => conditions.value.slice(0, 2));\r\nconst moreCount = computed(() => Math.max(0, conditionCount.value - 2));\r\n\r\nconst showAddMenu = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();\r\n\r\n if (showAddMenuFn) {\r\n showAddMenuFn(\r\n {\r\n x: rect.left + rect.width / 2,\r\n y: rect.bottom + 10,\r\n },\r\n props.id,\r\n );\r\n }\r\n};\r\n\r\nconst handleDelete = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n if (deleteNodeFn) {\r\n deleteNodeFn(props.id);\r\n }\r\n};\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.condition-node {\r\n position: relative;\r\n}\r\n\r\n.status-indicator {\r\n position: absolute;\r\n top: -4px;\r\n right: -4px;\r\n width: 12px;\r\n height: 12px;\r\n border-radius: 50%;\r\n border: 2px solid white;\r\n z-index: 2;\r\n background: #faad14;\r\n\r\n &.completed {\r\n background: #52c41a;\r\n }\r\n &.active {\r\n background: #fa8c16;\r\n }\r\n}\r\n\r\n.delete-btn {\r\n position: absolute;\r\n top: -10px;\r\n right: -10px;\r\n width: 24px;\r\n height: 24px;\r\n border-radius: 50%;\r\n background: #ff4d4f;\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n z-index: 100;\r\n opacity: 0;\r\n transform: scale(0.8);\r\n transition: all 0.2s ease;\r\n border: 2px solid white;\r\n box-shadow: 0 2px 8px rgba(255, 77, 79, 0.3);\r\n\r\n &:hover {\r\n transform: scale(1);\r\n background: #ff7875;\r\n box-shadow: 0 4px 12px rgba(255, 77, 79, 0.5);\r\n }\r\n\r\n &:active {\r\n transform: scale(0.95);\r\n }\r\n}\r\n\r\n.condition-node:hover .delete-btn {\r\n opacity: 1;\r\n transform: scale(1);\r\n}\r\n\r\n.node-card {\r\n background: white;\r\n border-radius: 12px;\r\n min-width: 220px;\r\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\r\n border: 2px solid transparent;\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n overflow: hidden;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n transform: translateY(-2px);\r\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);\r\n border-color: #fa8c16;\r\n }\r\n}\r\n\r\n.node-header {\r\n padding: 12px 16px;\r\n background: #fff7e6;\r\n border-bottom: 1px solid #ffe7ba;\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n}\r\n\r\n.node-icon {\r\n width: 24px;\r\n height: 24px;\r\n border-radius: 50%;\r\n background: #fa8c16;\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-size: 12px;\r\n}\r\n\r\n.node-title {\r\n font-weight: 600;\r\n color: #262626;\r\n font-size: 14px;\r\n flex: 1;\r\n}\r\n\r\n.node-badge {\r\n background: #fa8c16;\r\n color: white;\r\n padding: 2px 6px;\r\n border-radius: 10px;\r\n font-size: 11px;\r\n font-weight: 600;\r\n min-width: 16px;\r\n text-align: center;\r\n}\r\n\r\n.node-content {\r\n padding: 16px;\r\n}\r\n\r\n.conditions-list {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 6px;\r\n}\r\n\r\n.condition-tag {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n background: #fff7e6;\r\n color: #fa8c16;\r\n padding: 6px 10px;\r\n border-radius: 8px;\r\n font-size: 12px;\r\n border: 1px solid #ffd591;\r\n transition: all 0.2s ease;\r\n\r\n &:hover {\r\n background: #ffe7ba;\r\n transform: translateX(2px);\r\n }\r\n}\r\n\r\n.condition-icon {\r\n font-weight: bold;\r\n color: #d48806;\r\n}\r\n\r\n.more-count {\r\n background: #f0f0f0;\r\n color: #8c8c8c;\r\n padding: 4px 8px;\r\n border-radius: 12px;\r\n font-size: 12px;\r\n font-weight: 500;\r\n text-align: center;\r\n margin-top: 4px;\r\n}\r\n\r\n.placeholder {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n color: #8c8c8c;\r\n font-size: 12px;\r\n padding: 8px 0;\r\n}\r\n\r\n.add-node-btn {\r\n position: absolute;\r\n left: 50%;\r\n bottom: -20px;\r\n transform: translateX(-50%);\r\n width: 32px;\r\n height: 32px;\r\n border-radius: 50%;\r\n background: linear-gradient(135deg, #fa8c16, #d48806);\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n box-shadow: 0 4px 12px rgba(250, 140, 22, 0.3);\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n font-size: 16px;\r\n font-weight: bold;\r\n z-index: 10;\r\n\r\n &:hover {\r\n transform: translateX(-50%) scale(1.1);\r\n box-shadow: 0 6px 20px rgba(250, 140, 22, 0.4);\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Description: 条件节点组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"condition-node\">\r\n <div class=\"status-indicator\" :class=\"data.status\"></div>\r\n\r\n <!-- 删除按钮 -->\r\n <div class=\"delete-btn\" @click=\"handleDelete\" title=\"删除节点\">\r\n <C_Icon name=\"mdi:close\" :size=\"12\" />\r\n </div>\r\n\r\n <div class=\"node-card\">\r\n <div class=\"node-header\">\r\n <div class=\"node-icon\">🔀</div>\r\n <span class=\"node-title\">{{ data.title }}</span>\r\n <div class=\"node-badge\" v-if=\"conditionCount > 0\">\r\n {{ conditionCount }}\r\n </div>\r\n </div>\r\n\r\n <div class=\"node-content\">\r\n <div v-if=\"conditionCount > 0\" class=\"conditions-list\">\r\n <div\r\n v-for=\"condition in displayConditions\"\r\n :key=\"condition.id\"\r\n class=\"condition-tag\"\r\n >\r\n <span class=\"condition-icon\">→</span>\r\n <span>{{\r\n condition.name ||\r\n \"条件\" + (displayConditions.indexOf(condition) + 1)\r\n }}</span>\r\n </div>\r\n <div v-if=\"moreCount > 0\" class=\"more-count\">+{{ moreCount }}</div>\r\n </div>\r\n <div v-else class=\"placeholder\">请设置分支条件</div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"add-node-btn\" @click=\"showAddMenu\" title=\"添加下一个节点\">\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, inject } from \"vue\";\r\nimport { C_Icon } from \"../../C_Icon\";\r\n\r\n/* 定义条件类型 */\r\ninterface Condition {\r\n id: string;\r\n name: string;\r\n operator: \"equals\" | \"not_equals\" | \"greater_than\" | \"less_than\" | \"contains\";\r\n value: string;\r\n}\r\n\r\ninterface Props {\r\n id: string;\r\n data: {\r\n title: string;\r\n conditions?: Condition[];\r\n status?: string;\r\n };\r\n}\r\n\r\nconst props = defineProps<Props>();\r\n\r\nconst showAddMenuFn = inject(\"showAddMenu\") as\r\n | ((position: { x: number; y: number }, nodeId?: string) => void)\r\n | undefined;\r\n\r\nconst deleteNodeFn = inject(\"deleteNode\") as\r\n | ((nodeId: string) => void)\r\n | undefined;\r\n\r\n/* 使用安全的访问方式和默认值 */\r\nconst conditions = computed(() => props.data.conditions ?? []);\r\nconst conditionCount = computed(() => conditions.value.length);\r\nconst displayConditions = computed(() => conditions.value.slice(0, 2));\r\nconst moreCount = computed(() => Math.max(0, conditionCount.value - 2));\r\n\r\nconst showAddMenu = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();\r\n\r\n if (showAddMenuFn) {\r\n showAddMenuFn(\r\n {\r\n x: rect.left + rect.width / 2,\r\n y: rect.bottom + 10,\r\n },\r\n props.id,\r\n );\r\n }\r\n};\r\n\r\nconst handleDelete = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n if (deleteNodeFn) {\r\n deleteNodeFn(props.id);\r\n }\r\n};\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.condition-node {\r\n position: relative;\r\n}\r\n\r\n.status-indicator {\r\n position: absolute;\r\n top: -4px;\r\n right: -4px;\r\n width: 12px;\r\n height: 12px;\r\n border-radius: 50%;\r\n border: 2px solid white;\r\n z-index: 2;\r\n background: #faad14;\r\n\r\n &.completed {\r\n background: #52c41a;\r\n }\r\n &.active {\r\n background: #fa8c16;\r\n }\r\n}\r\n\r\n.delete-btn {\r\n position: absolute;\r\n top: -10px;\r\n right: -10px;\r\n width: 24px;\r\n height: 24px;\r\n border-radius: 50%;\r\n background: #ff4d4f;\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n z-index: 100;\r\n opacity: 0;\r\n transform: scale(0.8);\r\n transition: all 0.2s ease;\r\n border: 2px solid white;\r\n box-shadow: 0 2px 8px rgba(255, 77, 79, 0.3);\r\n\r\n &:hover {\r\n transform: scale(1);\r\n background: #ff7875;\r\n box-shadow: 0 4px 12px rgba(255, 77, 79, 0.5);\r\n }\r\n\r\n &:active {\r\n transform: scale(0.95);\r\n }\r\n}\r\n\r\n.condition-node:hover .delete-btn {\r\n opacity: 1;\r\n transform: scale(1);\r\n}\r\n\r\n.node-card {\r\n background: white;\r\n border-radius: 12px;\r\n min-width: 220px;\r\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\r\n border: 2px solid transparent;\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n overflow: hidden;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n transform: translateY(-2px);\r\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);\r\n border-color: #fa8c16;\r\n }\r\n}\r\n\r\n.node-header {\r\n padding: 12px 16px;\r\n background: #fff7e6;\r\n border-bottom: 1px solid #ffe7ba;\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n}\r\n\r\n.node-icon {\r\n width: 24px;\r\n height: 24px;\r\n border-radius: 50%;\r\n background: #fa8c16;\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-size: 12px;\r\n}\r\n\r\n.node-title {\r\n font-weight: 600;\r\n color: #262626;\r\n font-size: 14px;\r\n flex: 1;\r\n}\r\n\r\n.node-badge {\r\n background: #fa8c16;\r\n color: white;\r\n padding: 2px 6px;\r\n border-radius: 10px;\r\n font-size: 11px;\r\n font-weight: 600;\r\n min-width: 16px;\r\n text-align: center;\r\n}\r\n\r\n.node-content {\r\n padding: 16px;\r\n}\r\n\r\n.conditions-list {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 6px;\r\n}\r\n\r\n.condition-tag {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n background: #fff7e6;\r\n color: #fa8c16;\r\n padding: 6px 10px;\r\n border-radius: 8px;\r\n font-size: 12px;\r\n border: 1px solid #ffd591;\r\n transition: all 0.2s ease;\r\n\r\n &:hover {\r\n background: #ffe7ba;\r\n transform: translateX(2px);\r\n }\r\n}\r\n\r\n.condition-icon {\r\n font-weight: bold;\r\n color: #d48806;\r\n}\r\n\r\n.more-count {\r\n background: #f0f0f0;\r\n color: #8c8c8c;\r\n padding: 4px 8px;\r\n border-radius: 12px;\r\n font-size: 12px;\r\n font-weight: 500;\r\n text-align: center;\r\n margin-top: 4px;\r\n}\r\n\r\n.placeholder {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n color: #8c8c8c;\r\n font-size: 12px;\r\n padding: 8px 0;\r\n}\r\n\r\n.add-node-btn {\r\n position: absolute;\r\n left: 50%;\r\n bottom: -20px;\r\n transform: translateX(-50%);\r\n width: 32px;\r\n height: 32px;\r\n border-radius: 50%;\r\n background: linear-gradient(135deg, #fa8c16, #d48806);\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n box-shadow: 0 4px 12px rgba(250, 140, 22, 0.3);\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n font-size: 16px;\r\n font-weight: bold;\r\n z-index: 10;\r\n\r\n &:hover {\r\n transform: translateX(-50%) scale(1.1);\r\n box-shadow: 0 6px 20px rgba(250, 140, 22, 0.4);\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Description: 条件节点组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"condition-node\">\r\n <div class=\"status-indicator\" :class=\"data.status\"></div>\r\n\r\n <!-- 删除按钮 -->\r\n <div class=\"delete-btn\" @click=\"handleDelete\" title=\"删除节点\">\r\n <C_Icon name=\"mdi:close\" :size=\"12\" />\r\n </div>\r\n\r\n <div class=\"node-card\">\r\n <div class=\"node-header\">\r\n <div class=\"node-icon\">🔀</div>\r\n <span class=\"node-title\">{{ data.title }}</span>\r\n <div class=\"node-badge\" v-if=\"conditionCount > 0\">\r\n {{ conditionCount }}\r\n </div>\r\n </div>\r\n\r\n <div class=\"node-content\">\r\n <div v-if=\"conditionCount > 0\" class=\"conditions-list\">\r\n <div\r\n v-for=\"condition in displayConditions\"\r\n :key=\"condition.id\"\r\n class=\"condition-tag\"\r\n >\r\n <span class=\"condition-icon\">→</span>\r\n <span>{{\r\n condition.name ||\r\n \"条件\" + (displayConditions.indexOf(condition) + 1)\r\n }}</span>\r\n </div>\r\n <div v-if=\"moreCount > 0\" class=\"more-count\">+{{ moreCount }}</div>\r\n </div>\r\n <div v-else class=\"placeholder\">请设置分支条件</div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"add-node-btn\" @click=\"showAddMenu\" title=\"添加下一个节点\">\r\n <C_Icon name=\"mdi:plus\" :size=\"16\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, inject } from \"vue\";\r\nimport { C_Icon } from \"../../C_Icon\";\r\n\r\n/* 定义条件类型 */\r\ninterface Condition {\r\n id: string;\r\n name: string;\r\n operator: \"equals\" | \"not_equals\" | \"greater_than\" | \"less_than\" | \"contains\";\r\n value: string;\r\n}\r\n\r\ninterface Props {\r\n id: string;\r\n data: {\r\n title: string;\r\n conditions?: Condition[];\r\n status?: string;\r\n };\r\n}\r\n\r\nconst props = defineProps<Props>();\r\n\r\nconst showAddMenuFn = inject(\"showAddMenu\") as\r\n | ((position: { x: number; y: number }, nodeId?: string) => void)\r\n | undefined;\r\n\r\nconst deleteNodeFn = inject(\"deleteNode\") as\r\n | ((nodeId: string) => void)\r\n | undefined;\r\n\r\n/* 使用安全的访问方式和默认值 */\r\nconst conditions = computed(() => props.data.conditions ?? []);\r\nconst conditionCount = computed(() => conditions.value.length);\r\nconst displayConditions = computed(() => conditions.value.slice(0, 2));\r\nconst moreCount = computed(() => Math.max(0, conditionCount.value - 2));\r\n\r\nconst showAddMenu = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();\r\n\r\n if (showAddMenuFn) {\r\n showAddMenuFn(\r\n {\r\n x: rect.left + rect.width / 2,\r\n y: rect.bottom + 10,\r\n },\r\n props.id,\r\n );\r\n }\r\n};\r\n\r\nconst handleDelete = (event: MouseEvent) => {\r\n event.stopPropagation();\r\n if (deleteNodeFn) {\r\n deleteNodeFn(props.id);\r\n }\r\n};\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.condition-node {\r\n position: relative;\r\n}\r\n\r\n.status-indicator {\r\n position: absolute;\r\n top: -4px;\r\n right: -4px;\r\n width: 12px;\r\n height: 12px;\r\n border-radius: 50%;\r\n border: 2px solid white;\r\n z-index: 2;\r\n background: #faad14;\r\n\r\n &.completed {\r\n background: #52c41a;\r\n }\r\n &.active {\r\n background: #fa8c16;\r\n }\r\n}\r\n\r\n.delete-btn {\r\n position: absolute;\r\n top: -10px;\r\n right: -10px;\r\n width: 24px;\r\n height: 24px;\r\n border-radius: 50%;\r\n background: #ff4d4f;\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n z-index: 100;\r\n opacity: 0;\r\n transform: scale(0.8);\r\n transition: all 0.2s ease;\r\n border: 2px solid white;\r\n box-shadow: 0 2px 8px rgba(255, 77, 79, 0.3);\r\n\r\n &:hover {\r\n transform: scale(1);\r\n background: #ff7875;\r\n box-shadow: 0 4px 12px rgba(255, 77, 79, 0.5);\r\n }\r\n\r\n &:active {\r\n transform: scale(0.95);\r\n }\r\n}\r\n\r\n.condition-node:hover .delete-btn {\r\n opacity: 1;\r\n transform: scale(1);\r\n}\r\n\r\n.node-card {\r\n background: white;\r\n border-radius: 12px;\r\n min-width: 220px;\r\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\r\n border: 2px solid transparent;\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n overflow: hidden;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n transform: translateY(-2px);\r\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);\r\n border-color: #fa8c16;\r\n }\r\n}\r\n\r\n.node-header {\r\n padding: 12px 16px;\r\n background: #fff7e6;\r\n border-bottom: 1px solid #ffe7ba;\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n}\r\n\r\n.node-icon {\r\n width: 24px;\r\n height: 24px;\r\n border-radius: 50%;\r\n background: #fa8c16;\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-size: 12px;\r\n}\r\n\r\n.node-title {\r\n font-weight: 600;\r\n color: #262626;\r\n font-size: 14px;\r\n flex: 1;\r\n}\r\n\r\n.node-badge {\r\n background: #fa8c16;\r\n color: white;\r\n padding: 2px 6px;\r\n border-radius: 10px;\r\n font-size: 11px;\r\n font-weight: 600;\r\n min-width: 16px;\r\n text-align: center;\r\n}\r\n\r\n.node-content {\r\n padding: 16px;\r\n}\r\n\r\n.conditions-list {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 6px;\r\n}\r\n\r\n.condition-tag {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n background: #fff7e6;\r\n color: #fa8c16;\r\n padding: 6px 10px;\r\n border-radius: 8px;\r\n font-size: 12px;\r\n border: 1px solid #ffd591;\r\n transition: all 0.2s ease;\r\n\r\n &:hover {\r\n background: #ffe7ba;\r\n transform: translateX(2px);\r\n }\r\n}\r\n\r\n.condition-icon {\r\n font-weight: bold;\r\n color: #d48806;\r\n}\r\n\r\n.more-count {\r\n background: #f0f0f0;\r\n color: #8c8c8c;\r\n padding: 4px 8px;\r\n border-radius: 12px;\r\n font-size: 12px;\r\n font-weight: 500;\r\n text-align: center;\r\n margin-top: 4px;\r\n}\r\n\r\n.placeholder {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n color: #8c8c8c;\r\n font-size: 12px;\r\n padding: 8px 0;\r\n}\r\n\r\n.add-node-btn {\r\n position: absolute;\r\n left: 50%;\r\n bottom: -20px;\r\n transform: translateX(-50%);\r\n width: 32px;\r\n height: 32px;\r\n border-radius: 50%;\r\n background: linear-gradient(135deg, #fa8c16, #d48806);\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n box-shadow: 0 4px 12px rgba(250, 140, 22, 0.3);\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n font-size: 16px;\r\n font-weight: bold;\r\n z-index: 10;\r\n\r\n &:hover {\r\n transform: translateX(-50%) scale(1.1);\r\n box-shadow: 0 6px 20px rgba(250, 140, 22, 0.4);\r\n }\r\n}\r\n</style>\r\n","/**\r\n * 工作流节点管理 Composable\r\n * 封装节点 CRUD、provide/inject、边重连、画布操作等逻辑\r\n */\r\n\r\nimport {\r\n ref,\r\n computed,\r\n provide,\r\n watch,\r\n onMounted,\r\n nextTick,\r\n markRaw,\r\n} from \"vue\";\r\nimport type { Component, Ref } from \"vue\";\r\nimport { useMessage } from \"naive-ui\";\r\nimport type {\r\n WorkflowNode,\r\n WorkflowEdge,\r\n WorkflowData,\r\n WorkflowProps,\r\n NodeType,\r\n MenuPosition,\r\n} from \"../types\";\r\nimport { NODE_TITLES, INITIAL_NODE, NODE_Y_GAP, generateEdgeId } from \"../data\";\r\n\r\nimport StartNode from \"../nodes/StartNode.vue\";\r\nimport ApprovalNode from \"../nodes/ApprovalNode.vue\";\r\nimport CopyNode from \"../nodes/CopyNode.vue\";\r\nimport ConditionNode from \"../nodes/ConditionNode.vue\";\r\n\r\n/** 节点组件映射(模块级常量,仅初始化一次) */\r\nconst NODE_COMPONENT_MAP: Record<string, Component> = {\r\n start: markRaw(StartNode),\r\n approval: markRaw(ApprovalNode),\r\n copy: markRaw(CopyNode),\r\n condition: markRaw(ConditionNode),\r\n};\r\n\r\ntype EmitFn = (event: string, ...args: any[]) => void;\r\n\r\n/** 工作流节点管理 —— 封装节点 CRUD、provide/inject、边重连、画布操作等逻辑 */\r\nexport function useWorkflowNodes(\r\n props: WorkflowProps,\r\n emit: EmitFn,\r\n vueFlowRef: Ref,\r\n) {\r\n const message = useMessage();\r\n\r\n /* ─── 响应式状态 ────────────────────────────────────────── */\r\n const nodes = ref<WorkflowNode[]>([{ ...INITIAL_NODE }]);\r\n const edges = ref<WorkflowEdge[]>([]);\r\n const showAddMenu = ref(false);\r\n const menuPosition = ref<MenuPosition>({ x: 0, y: 0 });\r\n const showNodeConfig = ref(false);\r\n const currentNode = ref<WorkflowNode | null>(null);\r\n const currentAddNodeId = ref<string | null>(null);\r\n\r\n /* ─── 计算属性 ──────────────────────────────────────────── */\r\n const nodeTypes = computed(() => NODE_COMPONENT_MAP);\r\n\r\n const workflowStats = computed(() => {\r\n const stats = {\r\n totalNodes: nodes.value.length,\r\n approvalNodes: 0,\r\n copyNodes: 0,\r\n conditionNodes: 0,\r\n };\r\n nodes.value.forEach((n) => {\r\n if (n.type === \"approval\") stats.approvalNodes++;\r\n else if (n.type === \"copy\") stats.copyNodes++;\r\n else if (n.type === \"condition\") stats.conditionNodes++;\r\n });\r\n return stats;\r\n });\r\n\r\n /* ─── 数据操作 ──────────────────────────────────────────── */\r\n /** 获取当前工作流完整数据 */\r\n const getCurrentWorkflowData = (): WorkflowData => ({\r\n nodes: nodes.value,\r\n edges: edges.value,\r\n config: { version: \"1.0\", createdAt: new Date().toISOString() },\r\n });\r\n\r\n /** 触发数据变更事件 */\r\n const emitChange = (): void => {\r\n const data = getCurrentWorkflowData();\r\n emit(\"update:modelValue\", data);\r\n emit(\"change\", data);\r\n };\r\n\r\n /** 延迟适应画布视图 */\r\n const deferFitView = (padding = 60, duration = 400): void => {\r\n nextTick(() => {\r\n setTimeout(() => {\r\n vueFlowRef.value?.fitView?.({ padding, duration });\r\n }, 100);\r\n });\r\n };\r\n\r\n /* ─── 添加菜单 ──────────────────────────────────────────── */\r\n const handleShowAddMenu = (position: MenuPosition, nodeId?: string): void => {\r\n menuPosition.value = {\r\n x: typeof position.x === \"number\" ? position.x : 0,\r\n y: typeof position.y === \"number\" ? position.y : 0,\r\n };\r\n currentAddNodeId.value = nodeId || null;\r\n showAddMenu.value = true;\r\n };\r\n\r\n const closeAddMenu = (): void => {\r\n showAddMenu.value = false;\r\n };\r\n\r\n /* ─── 节点删除 ──────────────────────────────────────────── */\r\n const deleteNode = (nodeId: string): void => {\r\n if (nodeId === \"start-1\") {\r\n message.warning(\"不能删除开始节点\");\r\n return;\r\n }\r\n\r\n const nodeIndex = nodes.value.findIndex((n) => n.id === nodeId);\r\n if (nodeIndex === -1) return;\r\n\r\n /* 收集上下游边,用于重连 */\r\n const incomingEdges = edges.value.filter((e) => e.target === nodeId);\r\n const outgoingEdges = edges.value.filter((e) => e.source === nodeId);\r\n\r\n /* 移除节点和关联边 */\r\n nodes.value.splice(nodeIndex, 1);\r\n edges.value = edges.value.filter(\r\n (e) => e.source !== nodeId && e.target !== nodeId,\r\n );\r\n\r\n /* 重连上下游 */\r\n incomingEdges.forEach((inEdge) => {\r\n outgoingEdges.forEach((outEdge) => {\r\n edges.value.push({\r\n id: generateEdgeId(inEdge.source, outEdge.target),\r\n source: inEdge.source,\r\n target: outEdge.target,\r\n animated: true,\r\n type: \"default\",\r\n });\r\n });\r\n });\r\n\r\n /* 下移后续节点位置 */\r\n nodes.value.forEach((node, i) => {\r\n if (i >= nodeIndex) node.position.y -= NODE_Y_GAP;\r\n });\r\n\r\n emitChange();\r\n message.success(\"节点已删除\");\r\n deferFitView();\r\n };\r\n\r\n /* ─── provide 注入给子节点组件 ───────────────────────────── */\r\n provide(\"showAddMenu\", handleShowAddMenu);\r\n provide(\"deleteNode\", deleteNode);\r\n\r\n /* ─── 节点添加(内部辅助) ───────────────────────────────── */\r\n const getTargetNodeInfo = () => {\r\n let targetNodeIndex = nodes.value.length - 1;\r\n let targetNode = nodes.value[targetNodeIndex];\r\n\r\n if (currentAddNodeId.value) {\r\n const foundIndex = nodes.value.findIndex(\r\n (n) => n.id === currentAddNodeId.value,\r\n );\r\n if (foundIndex !== -1) {\r\n targetNodeIndex = foundIndex;\r\n targetNode = nodes.value[targetNodeIndex];\r\n }\r\n }\r\n return { targetNodeIndex, targetNode };\r\n };\r\n\r\n const createNewNode = (\r\n type: NodeType,\r\n targetNode: WorkflowNode | null,\r\n ): WorkflowNode => ({\r\n id: `${type}-${Date.now()}`,\r\n type,\r\n position: {\r\n x: targetNode?.position.x || 150,\r\n y: (targetNode?.position.y || 130) + NODE_Y_GAP,\r\n },\r\n data: {\r\n title: NODE_TITLES[type],\r\n status: \"pending\",\r\n ...(type === \"approval\" && { approvers: [], approvalMode: \"any\" }),\r\n ...(type === \"copy\" && { copyUsers: [] }),\r\n ...(type === \"condition\" && { conditions: [] }),\r\n },\r\n });\r\n\r\n /** 将新节点插入到 targetNode 之后,重连所有边 */\r\n const reconnectEdges = (\r\n targetNode: WorkflowNode,\r\n newNode: WorkflowNode,\r\n ): void => {\r\n const outgoing = edges.value.filter((e) => e.source === targetNode.id);\r\n edges.value = edges.value.filter((e) => e.source !== targetNode.id);\r\n\r\n /* 原节点 → 新节点 */\r\n edges.value.push({\r\n id: generateEdgeId(targetNode.id, newNode.id),\r\n source: targetNode.id,\r\n target: newNode.id,\r\n animated: true,\r\n type: \"default\",\r\n });\r\n\r\n /* 新节点 → 原下游 */\r\n outgoing.forEach((edge) => {\r\n edges.value.push({\r\n id: generateEdgeId(newNode.id, edge.target),\r\n source: newNode.id,\r\n target: edge.target,\r\n animated: true,\r\n type: \"default\",\r\n });\r\n });\r\n };\r\n\r\n /* ─── 节点添加(公开) ───────────────────────────────────── */\r\n const addNode = (type: NodeType): void => {\r\n try {\r\n const { targetNodeIndex, targetNode } = getTargetNodeInfo();\r\n const newNode = createNewNode(type, targetNode);\r\n\r\n nodes.value.splice(targetNodeIndex + 1, 0, newNode);\r\n\r\n /* 下推后续节点 */\r\n for (let i = targetNodeIndex + 2; i < nodes.value.length; i++) {\r\n nodes.value[i].position.y += NODE_Y_GAP;\r\n }\r\n\r\n if (targetNode) reconnectEdges(targetNode, newNode);\r\n\r\n showAddMenu.value = false;\r\n currentAddNodeId.value = null;\r\n emitChange();\r\n deferFitView();\r\n } catch (error) {\r\n console.error(\"Error adding node:\", error);\r\n message.error(\"添加节点失败,请重试\");\r\n }\r\n };\r\n\r\n /* ─── 节点交互 ──────────────────────────────────────────── */\r\n const onNodeClick = (event: { node: WorkflowNode }): void => {\r\n const { node } = event;\r\n currentNode.value = node;\r\n showNodeConfig.value = true;\r\n emit(\"node-click\", node);\r\n };\r\n\r\n const handleConfigSave = (configData: Record<string, unknown>): void => {\r\n if (!currentNode.value) return;\r\n\r\n const nodeIndex = nodes.value.findIndex(\r\n (n) => n.id === currentNode.value!.id,\r\n );\r\n if (nodeIndex !== -1) {\r\n const updatedNode = {\r\n ...nodes.value[nodeIndex],\r\n data: { ...nodes.value[nodeIndex].data, ...configData },\r\n };\r\n nodes.value.splice(nodeIndex, 1, updatedNode);\r\n currentNode.value = updatedNode;\r\n }\r\n\r\n emitChange();\r\n showNodeConfig.value = false;\r\n message.success(\"节点配置已保存\");\r\n };\r\n\r\n /* ─── 画布操作 ──────────────────────────────────────────── */\r\n const fitView = (): void => {\r\n if (!vueFlowRef.value?.fitView) {\r\n message.warning(\"画布未准备就绪,请稍后重试\");\r\n return;\r\n }\r\n nextTick(() => {\r\n vueFlowRef.value.fitView({\r\n padding: 50,\r\n includeHiddenNodes: false,\r\n minZoom: 0.5,\r\n maxZoom: 1.5,\r\n duration: 800,\r\n });\r\n });\r\n message.success(\"已适应窗口大小\");\r\n };\r\n\r\n /** 重置节点和边(不涉及验证状态) */\r\n const resetNodes = (): void => {\r\n nodes.value = [{ ...INITIAL_NODE }];\r\n edges.value = [];\r\n emitChange();\r\n deferFitView(80, 600);\r\n message.success(\"画布已清空\");\r\n };\r\n\r\n /* ─── 生命周期 ──────────────────────────────────────────── */\r\n watch(\r\n () => props.modelValue,\r\n (newValue) => {\r\n if (newValue && newValue !== getCurrentWorkflowData()) {\r\n nodes.value = newValue.nodes || [];\r\n edges.value = newValue.edges || [];\r\n }\r\n },\r\n { deep: true },\r\n );\r\n\r\n onMounted(() => {\r\n nextTick(() => {\r\n setTimeout(() => {\r\n vueFlowRef.value?.fitView?.({\r\n padding: 80,\r\n includeHiddenNodes: false,\r\n minZoom: 0.8,\r\n maxZoom: 1.2,\r\n duration: 600,\r\n });\r\n }, 300);\r\n });\r\n });\r\n\r\n return {\r\n /* 状态 */\r\n nodes,\r\n edges,\r\n showAddMenu,\r\n menuPosition,\r\n showNodeConfig,\r\n currentNode,\r\n /* 计算属性 */\r\n nodeTypes,\r\n workflowStats,\r\n /* 方法 */\r\n addNode,\r\n onNodeClick,\r\n closeAddMenu,\r\n handleConfigSave,\r\n resetNodes,\r\n getCurrentWorkflowData,\r\n fitView,\r\n deleteNode,\r\n };\r\n}\r\n","/**\r\n * 工作流验证 Composable\r\n * 封装流程校验逻辑、错误展示和节点定位\r\n */\r\n\r\nimport { ref } from \"vue\";\r\nimport type { Ref } from \"vue\";\r\nimport { useMessage } from \"naive-ui\";\r\nimport type { WorkflowNode, WorkflowEdge, ValidationError } from \"../types\";\r\nimport { FIELD_DISPLAY_NAMES, ERROR_TYPE_TEXTS } from \"../data\";\r\n\r\nexport interface WorkflowValidationOptions {\r\n /** 定位错误节点后打开配置弹窗的回调 */\r\n onShowNodeConfig?: (node: WorkflowNode) => void;\r\n /** 验证失败时的外部回调(通常用于 emit) */\r\n onValidateError?: (errors: ValidationError[]) => void;\r\n}\r\n\r\n/** 工作流验证 —— 封装流程校验逻辑、错误展示和节点定位 */\r\nexport function useWorkflowValidation(\r\n nodes: Ref<WorkflowNode[]>,\r\n edges: Ref<WorkflowEdge[]>,\r\n vueFlowRef: Ref,\r\n options?: WorkflowValidationOptions,\r\n) {\r\n const message = useMessage();\r\n\r\n /* ─── 响应式状态 ────────────────────────────────────────── */\r\n const validationErrors = ref<ValidationError[]>([]);\r\n const showValidationErrors = ref(false);\r\n\r\n /* ─── 核心验证 ──────────────────────────────────────────── */\r\n /** 执行工作流验证,返回错误列表(纯逻辑,不操作 UI) */\r\n const validateWorkflow = (): ValidationError[] => {\r\n const errors: ValidationError[] = [];\r\n\r\n nodes.value.forEach((node) => {\r\n /* 审批节点:必须有审批人 */\r\n if (node.type === \"approval\") {\r\n const approvers = (node.data as any).approvers || [];\r\n if (approvers.length === 0) {\r\n errors.push({\r\n nodeId: node.id,\r\n nodeName: node.data.title,\r\n field: \"approvers\",\r\n message: \"审批节点必须设置至少一个审批人,否则流程无法正常运行\",\r\n type: \"required\",\r\n });\r\n }\r\n }\r\n\r\n /* 条件节点:必须有分支且配置完整 */\r\n if (node.type === \"condition\") {\r\n const conditions = (node.data as any).conditions || [];\r\n if (conditions.length === 0) {\r\n errors.push({\r\n nodeId: node.id,\r\n nodeName: node.data.title,\r\n field: \"conditions\",\r\n message:\r\n \"条件分支节点必须配置至少一个分支条件,否则无法进行条件判断\",\r\n type: \"required\",\r\n });\r\n } else {\r\n const incomplete = conditions.filter(\r\n (c: any) => !c.name || !c.field || !c.operator || !c.value,\r\n );\r\n if (incomplete.length > 0) {\r\n errors.push({\r\n nodeId: node.id,\r\n nodeName: node.data.title,\r\n field: \"conditions\",\r\n message: `有 ${incomplete.length} 个条件分支配置不完整,请完善所有必填字段`,\r\n type: \"incomplete\",\r\n });\r\n }\r\n }\r\n }\r\n });\r\n\r\n /* 检查断连节点 */\r\n const connectedNodes = new Set<string>();\r\n edges.value.forEach((edge) => {\r\n connectedNodes.add(edge.source);\r\n connectedNodes.add(edge.target);\r\n });\r\n\r\n nodes.value.forEach((node) => {\r\n if (node.type !== \"start\" && !connectedNodes.has(node.id)) {\r\n errors.push({\r\n nodeId: node.id,\r\n nodeName: node.data.title,\r\n field: \"connection\",\r\n message: \"此节点未与其他节点连接,可能导致流程中断\",\r\n type: \"warning\",\r\n });\r\n }\r\n });\r\n\r\n return errors;\r\n };\r\n\r\n /* ─── UI 交互 ───────────────────────────────────────────── */\r\n /** 执行验证并更新 UI 状态(消息提示 + 抽屉) */\r\n const validateCurrentWorkflow = (): void => {\r\n const errors = validateWorkflow();\r\n validationErrors.value = errors;\r\n\r\n if (errors.length === 0) {\r\n message.success(\"工作流验证通过!所有节点配置正确\");\r\n showValidationErrors.value = false;\r\n } else {\r\n message.error(`发现 ${errors.length} 个问题,请查看详细错误`);\r\n showValidationErrors.value = true;\r\n options?.onValidateError?.(errors);\r\n }\r\n };\r\n\r\n /** 定位到错误节点并打开配置弹窗 */\r\n const jumpToErrorNode = (nodeId: string): void => {\r\n const node = nodes.value.find((n) => n.id === nodeId);\r\n if (!node || !vueFlowRef.value) return;\r\n\r\n vueFlowRef.value.setCenter(node.position.x, node.position.y, {\r\n zoom: 1.2,\r\n duration: 800,\r\n });\r\n\r\n setTimeout(() => {\r\n options?.onShowNodeConfig?.(node);\r\n showValidationErrors.value = false;\r\n }, 900);\r\n\r\n message.info(`已定位到节点:${node.data.title}`);\r\n };\r\n\r\n /* ─── 辅助方法 ──────────────────────────────────────────── */\r\n const getFieldDisplayName = (field: string): string =>\r\n FIELD_DISPLAY_NAMES[field] || field;\r\n\r\n const getErrorTypeText = (type: string): string =>\r\n ERROR_TYPE_TEXTS[type] || type;\r\n\r\n /** 重置验证状态 */\r\n const resetValidation = (): void => {\r\n validationErrors.value = [];\r\n showValidationErrors.value = false;\r\n };\r\n\r\n return {\r\n validationErrors,\r\n showValidationErrors,\r\n validateWorkflow,\r\n validateCurrentWorkflow,\r\n jumpToErrorNode,\r\n getFieldDisplayName,\r\n getErrorTypeText,\r\n resetValidation,\r\n };\r\n}\r\n","/**\r\n * 工作流预览 Composable\r\n * 封装流程预览逻辑、节点排序和步骤展示\r\n */\r\n\r\nimport { ref } from \"vue\";\r\nimport type { Ref } from \"vue\";\r\nimport type { WorkflowNode, WorkflowEdge } from \"../types\";\r\n\r\n/** 详情项类型 */\r\nexport type DetailItemType = \"text\" | \"mode\" | \"users\" | \"warning\";\r\n\r\n/** 结构化详情项 */\r\nexport interface DetailItem {\r\n type: DetailItemType;\r\n label: string;\r\n value?: string;\r\n users?: { name: string; department?: string }[];\r\n modeKey?: string;\r\n}\r\n\r\n/** 流程步骤(已排序) */\r\nexport interface FlowStep {\r\n order: number;\r\n node: WorkflowNode;\r\n nodeTypeLabel: string;\r\n icon: string;\r\n colorClass: string;\r\n details: DetailItem[];\r\n children?: FlowStep[];\r\n}\r\n\r\n/** 预览统计信息 */\r\nexport interface PreviewStats {\r\n totalNodes: number;\r\n approvalNodes: number;\r\n copyNodes: number;\r\n conditionNodes: number;\r\n totalEdges: number;\r\n}\r\n\r\nconst NODE_TYPE_META: Record<\r\n string,\r\n { label: string; icon: string; colorClass: string }\r\n> = {\r\n start: { label: \"发起人\", icon: \"mdi:play-circle\", colorClass: \"start\" },\r\n approval: {\r\n label: \"审批节点\",\r\n icon: \"mdi:account-check\",\r\n colorClass: \"approval\",\r\n },\r\n copy: { label: \"抄送节点\", icon: \"mdi:email-outline\", colorClass: \"copy\" },\r\n condition: {\r\n label: \"条件分支\",\r\n icon: \"mdi:source-branch\",\r\n colorClass: \"condition\",\r\n },\r\n};\r\n\r\nconst APPROVAL_MODE_LABELS: Record<string, string> = {\r\n any: \"或签(任一审批人通过即可)\",\r\n all: \"会签(所有审批人必须通过)\",\r\n sequence: \"顺序审批(按顺序依次审批)\",\r\n};\r\n\r\nconst OPERATOR_LABELS: Record<string, string> = {\r\n equals: \"等于\",\r\n not_equals: \"不等于\",\r\n greater_than: \"大于\",\r\n less_than: \"小于\",\r\n contains: \"包含\",\r\n};\r\n\r\n/** 工作流预览 —— 封装流程预览逻辑、节点排序和步骤展示 */\r\nexport function useWorkflowPreview(\r\n nodes: Ref<WorkflowNode[]>,\r\n edges: Ref<WorkflowEdge[]>,\r\n) {\r\n /* ─── 响应式状态 ────────────────────────────────────────── */\r\n const showPreview = ref(false);\r\n const previewSteps = ref<FlowStep[]>([]);\r\n const previewStats = ref<PreviewStats>({\r\n totalNodes: 0,\r\n approvalNodes: 0,\r\n copyNodes: 0,\r\n conditionNodes: 0,\r\n totalEdges: 0,\r\n });\r\n\r\n /* ─── 节点详情提取 ──────────────────────────────────────── */\r\n /** 提取开始节点详情 */\r\n const extractStartDetails = (data: any): DetailItem[] => {\r\n const initiators = data.initiators || [];\r\n return initiators.length > 0\r\n ? [{ type: \"users\", label: \"发起人\", users: initiators }]\r\n : [{ type: \"text\", label: \"发起人\", value: \"所有人\" }];\r\n };\r\n\r\n /** 提取审批节点详情 */\r\n const extractApprovalDetails = (data: any): DetailItem[] => {\r\n const mode = data.approvalMode || \"any\";\r\n const approvers = data.approvers || [];\r\n const items: DetailItem[] = [\r\n {\r\n type: \"mode\",\r\n label: \"审批方式\",\r\n value: APPROVAL_MODE_LABELS[mode] || mode,\r\n modeKey: mode,\r\n },\r\n ];\r\n if (approvers.length > 0) {\r\n items.push({ type: \"users\", label: \"审批人\", users: approvers });\r\n } else {\r\n items.push({ type: \"warning\", label: \"审批人\", value: \"未设置\" });\r\n }\r\n return items;\r\n };\r\n\r\n /** 提取抄送节点详情 */\r\n const extractCopyDetails = (data: any): DetailItem[] => {\r\n const copyUsers = data.copyUsers || [];\r\n return copyUsers.length > 0\r\n ? [{ type: \"users\", label: \"抄送人\", users: copyUsers }]\r\n : [{ type: \"text\", label: \"抄送人\", value: \"未设置\" }];\r\n };\r\n\r\n /** 提取条件节点详情 */\r\n const extractConditionDetails = (data: any): DetailItem[] => {\r\n const conditions = data.conditions || [];\r\n if (conditions.length === 0) {\r\n return [{ type: \"warning\", label: \"条件分支\", value: \"未配置\" }];\r\n }\r\n return conditions.map((cond: any, i: number) => {\r\n const op = OPERATOR_LABELS[cond.operator] || cond.operator;\r\n return {\r\n type: \"text\" as const,\r\n label: `分支 ${i + 1}`,\r\n value: `${cond.name || \"未命名\"} — ${cond.field || \"?\"} ${op} ${cond.value || \"?\"}`,\r\n };\r\n });\r\n };\r\n\r\n /** 节点详情提取策略 */\r\n const detailExtractors: Record<string, (data: any) => DetailItem[]> = {\r\n start: extractStartDetails,\r\n approval: extractApprovalDetails,\r\n copy: extractCopyDetails,\r\n condition: extractConditionDetails,\r\n };\r\n\r\n /** 从节点数据中提取可读的配置详情 */\r\n const extractNodeDetails = (node: WorkflowNode): DetailItem[] => {\r\n const extractor = detailExtractors[node.type || \"start\"];\r\n return extractor ? extractor(node.data) : [];\r\n };\r\n\r\n /* ─── 拓扑排序 ──────────────────────────────────────────── */\r\n /** 基于 BFS 对流程节点进行拓扑排序 */\r\n const sortNodesByFlow = (): WorkflowNode[] => {\r\n const nodeMap = new Map<string, WorkflowNode>();\r\n nodes.value.forEach((n) => nodeMap.set(n.id, n));\r\n\r\n const adjacency = new Map<string, string[]>();\r\n const inDegree = new Map<string, number>();\r\n nodes.value.forEach((n) => {\r\n adjacency.set(n.id, []);\r\n inDegree.set(n.id, 0);\r\n });\r\n\r\n edges.value.forEach((e) => {\r\n adjacency.get(e.source)?.push(e.target);\r\n inDegree.set(e.target, (inDegree.get(e.target) || 0) + 1);\r\n });\r\n\r\n /* 从入度为 0 的节点(start)开始 BFS */\r\n const queue: string[] = [];\r\n inDegree.forEach((deg, id) => {\r\n if (deg === 0) queue.push(id);\r\n });\r\n\r\n const sorted: WorkflowNode[] = [];\r\n const visited = new Set<string>();\r\n\r\n while (queue.length > 0) {\r\n const id = queue.shift()!;\r\n if (visited.has(id)) continue;\r\n visited.add(id);\r\n\r\n const node = nodeMap.get(id);\r\n if (node) sorted.push(node);\r\n\r\n const neighbors = adjacency.get(id) || [];\r\n for (const next of neighbors) {\r\n const newDeg = (inDegree.get(next) || 1) - 1;\r\n inDegree.set(next, newDeg);\r\n if (newDeg <= 0 && !visited.has(next)) {\r\n queue.push(next);\r\n }\r\n }\r\n }\r\n\r\n /* 追加未连接的孤立节点 */\r\n nodes.value.forEach((n) => {\r\n if (!visited.has(n.id)) sorted.push(n);\r\n });\r\n\r\n return sorted;\r\n };\r\n\r\n /* ─── 生成流程步骤 ──────────────────────────────────────── */\r\n /** 构建预览步骤列表 */\r\n const buildFlowSteps = (): FlowStep[] => {\r\n const sorted = sortNodesByFlow();\r\n return sorted.map((node, index) => {\r\n const meta = NODE_TYPE_META[node.type || \"start\"] || NODE_TYPE_META.start;\r\n return {\r\n order: index + 1,\r\n node,\r\n nodeTypeLabel: meta.label,\r\n icon: meta.icon,\r\n colorClass: meta.colorClass,\r\n details: extractNodeDetails(node),\r\n };\r\n });\r\n };\r\n\r\n /* ─── 统计信息 ──────────────────────────────────────────── */\r\n const computeStats = (): PreviewStats => {\r\n const stats: PreviewStats = {\r\n totalNodes: nodes.value.length,\r\n approvalNodes: 0,\r\n copyNodes: 0,\r\n conditionNodes: 0,\r\n totalEdges: edges.value.length,\r\n };\r\n nodes.value.forEach((n) => {\r\n if (n.type === \"approval\") stats.approvalNodes++;\r\n else if (n.type === \"copy\") stats.copyNodes++;\r\n else if (n.type === \"condition\") stats.conditionNodes++;\r\n });\r\n return stats;\r\n };\r\n\r\n /* ─── 公开方法 ──────────────────────────────────────────── */\r\n /** 打开预览面板 */\r\n const openPreview = (): void => {\r\n previewSteps.value = buildFlowSteps();\r\n previewStats.value = computeStats();\r\n showPreview.value = true;\r\n };\r\n\r\n /** 关闭预览面板 */\r\n const closePreview = (): void => {\r\n showPreview.value = false;\r\n };\r\n\r\n return {\r\n showPreview,\r\n previewSteps,\r\n previewStats,\r\n openPreview,\r\n closePreview,\r\n };\r\n}\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-08-01\r\n * @Description: 工作流/审批流组件(基于 Vue Flow)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"approval-workflow-container\">\r\n <!-- 浮动工具栏 -->\r\n <div class=\"floating-toolbar\">\r\n <NButton size=\"small\" type=\"primary\" @click=\"saveWorkflow\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:content-save\" :size=\"16\" />\r\n </template>\r\n 保存\r\n </NButton>\r\n <NButton size=\"small\" @click=\"previewWorkflow\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:eye\" :size=\"16\" />\r\n </template>\r\n 预览\r\n </NButton>\r\n <NButton\r\n size=\"small\"\r\n @click=\"validateCurrentWorkflow\"\r\n title=\"检查工作流配置是否正确\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:check-circle\" :size=\"16\" />\r\n </template>\r\n 验证流程\r\n </NButton>\r\n <div class=\"toolbar-divider\"></div>\r\n <NButton size=\"small\" @click=\"fitView\" title=\"适应窗口\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:fit-to-screen\" :size=\"16\" />\r\n </template>\r\n </NButton>\r\n <NButton\r\n size=\"small\"\r\n type=\"error\"\r\n @click=\"clearWorkflow\"\r\n title=\"清空画布\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:delete-sweep\" :size=\"16\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n\r\n <!-- Vue Flow 画布 -->\r\n <VueFlow\r\n ref=\"vueFlowRef\"\r\n v-model:nodes=\"nodes\"\r\n v-model:edges=\"edges\"\r\n :node-types=\"nodeTypes\"\r\n class=\"workflow-canvas\"\r\n :default-viewport=\"{ zoom: 1, x: 0, y: 0 }\"\r\n :min-zoom=\"0.5\"\r\n :max-zoom=\"2\"\r\n :fit-view-on-init=\"true\"\r\n :nodes-draggable=\"true\"\r\n :elements-selectable=\"true\"\r\n @node-click=\"onNodeClick\"\r\n @pane-click=\"closeAddMenu\"\r\n />\r\n\r\n <!-- 节点添加菜单 -->\r\n <Teleport to=\"body\">\r\n <div\r\n v-show=\"showAddMenu\"\r\n class=\"add-node-menu\"\r\n :style=\"{ left: menuPosition.x + 'px', top: menuPosition.y + 'px' }\"\r\n >\r\n <div class=\"add-menu-content\">\r\n <div\r\n v-for=\"nodeType in NODE_TYPE_OPTIONS\"\r\n :key=\"nodeType.type\"\r\n class=\"add-menu-item\"\r\n @click=\"addNode(nodeType.type)\"\r\n >\r\n <div class=\"menu-icon\" :class=\"nodeType.iconClass\">\r\n <C_Icon :name=\"nodeType.icon\" :size=\"16\" />\r\n </div>\r\n <span class=\"menu-text\">{{ nodeType.label }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n </Teleport>\r\n\r\n <!-- 节点配置弹窗 - 拆分到独立组件 -->\r\n <NodeConfigModal\r\n v-model:show=\"showNodeConfig\"\r\n :current-node=\"currentNode\"\r\n :users=\"users\"\r\n :departments=\"departments\"\r\n @save=\"handleConfigSave\"\r\n @cancel=\"showNodeConfig = false\"\r\n />\r\n\r\n <!-- 验证错误日志抽屉 -->\r\n <NDrawer v-model:show=\"showValidationErrors\" :width=\"450\" placement=\"right\">\r\n <NDrawerContent title=\"流程验证结果\" closable>\r\n <div v-if=\"validationErrors.length === 0\" class=\"validation-success\">\r\n <div class=\"success-icon\">\r\n <C_Icon name=\"mdi:check-circle\" :size=\"32\" color=\"#52c41a\" />\r\n </div>\r\n <h3>验证通过</h3>\r\n <p>工作流配置正确,所有节点都已正确设置!</p>\r\n </div>\r\n\r\n <div v-else class=\"validation-errors\">\r\n <div class=\"error-summary\">\r\n <div class=\"error-icon\">\r\n <C_Icon name=\"mdi:alert-circle\" :size=\"24\" color=\"#ff4d4f\" />\r\n </div>\r\n <h3>发现 {{ validationErrors.length }} 个问题</h3>\r\n <p>请修复以下问题后重新验证:</p>\r\n </div>\r\n\r\n <div class=\"error-list\">\r\n <div\r\n v-for=\"(error, index) in validationErrors\"\r\n :key=\"error.nodeId\"\r\n class=\"error-item\"\r\n >\r\n <div class=\"error-header\">\r\n <span class=\"error-number\">{{ index + 1 }}</span>\r\n <div class=\"error-info\">\r\n <strong class=\"error-node\">{{ error.nodeName }}</strong>\r\n <span class=\"error-field\">{{\r\n getFieldDisplayName(error.field)\r\n }}</span>\r\n </div>\r\n <div class=\"error-type\" :class=\"error.type\">\r\n {{ getErrorTypeText(error.type) }}\r\n </div>\r\n </div>\r\n <div class=\"error-message\">{{ error.message }}</div>\r\n <div class=\"error-actions\">\r\n <NButton\r\n size=\"small\"\r\n type=\"primary\"\r\n @click=\"jumpToErrorNode(error.nodeId)\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:target\" :size=\"16\" />\r\n </template>\r\n 定位节点\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"validation-tips\">\r\n <h4>💡 常见问题解决方案:</h4>\r\n <ul>\r\n <li>\r\n <strong>审批人未设置:</strong\r\n >点击审批节点,在弹窗中选择审批人员\r\n </li>\r\n <li>\r\n <strong>条件分支未配置:</strong\r\n >点击条件节点,添加至少一个条件分支\r\n </li>\r\n <li>\r\n <strong>节点连接断开:</strong> 检查节点之间的连线是否正确\r\n </li>\r\n </ul>\r\n </div>\r\n </div>\r\n\r\n <template #footer>\r\n <div class=\"validation-footer\">\r\n <NButton @click=\"showValidationErrors = false\">关闭</NButton>\r\n <NButton type=\"primary\" @click=\"validateCurrentWorkflow\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:refresh\" :size=\"16\" />\r\n </template>\r\n 重新验证\r\n </NButton>\r\n </div>\r\n </template>\r\n </NDrawerContent>\r\n </NDrawer>\r\n\r\n <!-- 流程预览抽屉 -->\r\n <NDrawer v-model:show=\"showPreview\" :width=\"520\" placement=\"right\">\r\n <NDrawerContent title=\"流程预览\" closable>\r\n <!-- 统计概览 -->\r\n <div class=\"preview-stats\">\r\n <div class=\"stat-item\">\r\n <span class=\"stat-value\">{{ previewStats.totalNodes }}</span>\r\n <span class=\"stat-label\">总节点</span>\r\n </div>\r\n <div class=\"stat-item approval\">\r\n <span class=\"stat-value\">{{ previewStats.approvalNodes }}</span>\r\n <span class=\"stat-label\">审批</span>\r\n </div>\r\n <div class=\"stat-item copy\">\r\n <span class=\"stat-value\">{{ previewStats.copyNodes }}</span>\r\n <span class=\"stat-label\">抄送</span>\r\n </div>\r\n <div class=\"stat-item condition\">\r\n <span class=\"stat-value\">{{ previewStats.conditionNodes }}</span>\r\n <span class=\"stat-label\">条件</span>\r\n </div>\r\n <div class=\"stat-item edge\">\r\n <span class=\"stat-value\">{{ previewStats.totalEdges }}</span>\r\n <span class=\"stat-label\">连线</span>\r\n </div>\r\n </div>\r\n\r\n <!-- 流程步骤时间线 -->\r\n <div class=\"preview-timeline\">\r\n <div\r\n v-for=\"(step, index) in previewSteps\"\r\n :key=\"step.node.id\"\r\n class=\"preview-step\"\r\n :class=\"step.colorClass\"\r\n >\r\n <!-- 时间线连接线 -->\r\n <div class=\"step-connector\">\r\n <div class=\"step-dot\">\r\n <C_Icon :name=\"step.icon\" :size=\"16\" />\r\n </div>\r\n <div\r\n v-if=\"index < previewSteps.length - 1\"\r\n class=\"step-line\"\r\n ></div>\r\n </div>\r\n\r\n <!-- 步骤内容 -->\r\n <div class=\"step-content\">\r\n <div class=\"step-header\">\r\n <span class=\"step-order\">步骤 {{ step.order }}</span>\r\n <span class=\"step-type-badge\">{{ step.nodeTypeLabel }}</span>\r\n </div>\r\n <div class=\"step-title\">{{ step.node.data.title }}</div>\r\n <div v-if=\"step.details.length > 0\" class=\"step-details\">\r\n <div\r\n v-for=\"(detail, dIdx) in step.details\"\r\n :key=\"dIdx\"\r\n class=\"step-detail-item\"\r\n :class=\"{ 'is-warning': detail.type === 'warning' }\"\r\n >\r\n <span class=\"detail-label\">{{ detail.label }}:</span>\r\n\r\n <!-- 审批方式 badge -->\r\n <span\r\n v-if=\"detail.type === 'mode'\"\r\n class=\"mode-badge\"\r\n :class=\"detail.modeKey\"\r\n >\r\n {{ detail.value }}\r\n </span>\r\n\r\n <!-- 人员 tags -->\r\n <template v-else-if=\"detail.type === 'users'\">\r\n <NTag\r\n v-for=\"user in detail.users\"\r\n :key=\"user.name\"\r\n size=\"small\"\r\n :bordered=\"false\"\r\n round\r\n class=\"user-tag\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:account\" :size=\"12\" />\r\n </template>\r\n {{ user.name }}\r\n </NTag>\r\n </template>\r\n\r\n <!-- 警告文案 -->\r\n <span\r\n v-else-if=\"detail.type === 'warning'\"\r\n class=\"warning-text\"\r\n >\r\n ⚠️ {{ detail.value }}\r\n </span>\r\n\r\n <!-- 普通文本 -->\r\n <span v-else>{{ detail.value }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 空状态 -->\r\n <div v-if=\"previewSteps.length === 0\" class=\"preview-empty\">\r\n <C_Icon name=\"mdi:file-document-outline\" :size=\"48\" color=\"#d1d5db\" />\r\n <p>暂无流程节点</p>\r\n </div>\r\n\r\n <template #footer>\r\n <div class=\"preview-footer\">\r\n <NButton @click=\"closePreview\">关闭</NButton>\r\n <NButton type=\"primary\" @click=\"confirmAndSave\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:content-save\" :size=\"16\" />\r\n </template>\r\n 确认并保存\r\n </NButton>\r\n </div>\r\n </template>\r\n </NDrawerContent>\r\n </NDrawer>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref } from \"vue\";\r\nimport { VueFlow } from \"@vue-flow/core\";\r\nimport { NButton, NDrawer, NDrawerContent, NTag, useMessage } from \"naive-ui\";\r\nimport { C_Icon } from \"../C_Icon\";\r\nimport type { WorkflowProps, WorkflowEmits } from \"./types\";\r\nimport { NODE_TYPE_OPTIONS } from \"./data\";\r\nimport NodeConfigModal from \"./NodeConfigModal.vue\";\r\nimport { useWorkflowNodes } from \"./composables/useWorkflowNodes\";\r\nimport { useWorkflowValidation } from \"./composables/useWorkflowValidation\";\r\nimport { useWorkflowPreview } from \"./composables/useWorkflowPreview\";\r\n\r\ndefineOptions({ name: \"C_WorkFlow\" });\r\n\r\n/* Props & Emits */\r\nconst props = withDefaults(defineProps<WorkflowProps>(), {\r\n users: () => [],\r\n roles: () => [],\r\n departments: () => [],\r\n readonly: false,\r\n theme: \"light\",\r\n});\r\n\r\nconst emit = defineEmits<WorkflowEmits>();\r\nconst message = useMessage();\r\nconst vueFlowRef = ref();\r\n\r\n/* ─── 节点管理 ──────────────────────────────────────────── */\r\nconst {\r\n nodes,\r\n edges,\r\n showAddMenu,\r\n menuPosition,\r\n showNodeConfig,\r\n currentNode,\r\n nodeTypes,\r\n workflowStats,\r\n addNode,\r\n onNodeClick,\r\n closeAddMenu,\r\n handleConfigSave,\r\n resetNodes,\r\n getCurrentWorkflowData,\r\n fitView,\r\n deleteNode,\r\n} = useWorkflowNodes(props, emit, vueFlowRef);\r\n\r\n/* ─── 流程验证 ──────────────────────────────────────────── */\r\nconst {\r\n validationErrors,\r\n showValidationErrors,\r\n validateWorkflow,\r\n validateCurrentWorkflow,\r\n jumpToErrorNode,\r\n getFieldDisplayName,\r\n getErrorTypeText,\r\n resetValidation,\r\n} = useWorkflowValidation(nodes, edges, vueFlowRef, {\r\n onShowNodeConfig: (node) => {\r\n currentNode.value = node;\r\n showNodeConfig.value = true;\r\n },\r\n onValidateError: (errors) => emit(\"validate-error\", errors),\r\n});\r\n\r\n/* ─── 流程预览 ──────────────────────────────────────────── */\r\nconst { showPreview, previewSteps, previewStats, openPreview, closePreview } =\r\n useWorkflowPreview(nodes, edges);\r\n\r\n/* ─── 编排方法(跨 composable 协作) ────────────────────── */\r\nconst saveWorkflow = (): void => {\r\n const errors = validateWorkflow();\r\n if (errors.length > 0) {\r\n message.error(`工作流验证失败: ${errors[0].message}`);\r\n showValidationErrors.value = true;\r\n return;\r\n }\r\n const data = getCurrentWorkflowData();\r\n emit(\"save\", data);\r\n message.success(\"工作流保存成功\");\r\n};\r\n\r\nconst previewWorkflow = (): void => {\r\n openPreview();\r\n};\r\n\r\nconst confirmAndSave = (): void => {\r\n closePreview();\r\n saveWorkflow();\r\n};\r\n\r\nconst clearWorkflow = (): void => {\r\n resetNodes();\r\n resetValidation();\r\n};\r\n\r\n/* ─── 暴露方法 ──────────────────────────────────────────── */\r\ndefineExpose({\r\n validateWorkflow,\r\n getCurrentWorkflowData,\r\n saveWorkflow,\r\n previewWorkflow,\r\n deleteNode,\r\n stats: workflowStats,\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-08-01\r\n * @Description: 工作流/审批流组件(基于 Vue Flow)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"approval-workflow-container\">\r\n <!-- 浮动工具栏 -->\r\n <div class=\"floating-toolbar\">\r\n <NButton size=\"small\" type=\"primary\" @click=\"saveWorkflow\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:content-save\" :size=\"16\" />\r\n </template>\r\n 保存\r\n </NButton>\r\n <NButton size=\"small\" @click=\"previewWorkflow\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:eye\" :size=\"16\" />\r\n </template>\r\n 预览\r\n </NButton>\r\n <NButton\r\n size=\"small\"\r\n @click=\"validateCurrentWorkflow\"\r\n title=\"检查工作流配置是否正确\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:check-circle\" :size=\"16\" />\r\n </template>\r\n 验证流程\r\n </NButton>\r\n <div class=\"toolbar-divider\"></div>\r\n <NButton size=\"small\" @click=\"fitView\" title=\"适应窗口\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:fit-to-screen\" :size=\"16\" />\r\n </template>\r\n </NButton>\r\n <NButton\r\n size=\"small\"\r\n type=\"error\"\r\n @click=\"clearWorkflow\"\r\n title=\"清空画布\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:delete-sweep\" :size=\"16\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n\r\n <!-- Vue Flow 画布 -->\r\n <VueFlow\r\n ref=\"vueFlowRef\"\r\n v-model:nodes=\"nodes\"\r\n v-model:edges=\"edges\"\r\n :node-types=\"nodeTypes\"\r\n class=\"workflow-canvas\"\r\n :default-viewport=\"{ zoom: 1, x: 0, y: 0 }\"\r\n :min-zoom=\"0.5\"\r\n :max-zoom=\"2\"\r\n :fit-view-on-init=\"true\"\r\n :nodes-draggable=\"true\"\r\n :elements-selectable=\"true\"\r\n @node-click=\"onNodeClick\"\r\n @pane-click=\"closeAddMenu\"\r\n />\r\n\r\n <!-- 节点添加菜单 -->\r\n <Teleport to=\"body\">\r\n <div\r\n v-show=\"showAddMenu\"\r\n class=\"add-node-menu\"\r\n :style=\"{ left: menuPosition.x + 'px', top: menuPosition.y + 'px' }\"\r\n >\r\n <div class=\"add-menu-content\">\r\n <div\r\n v-for=\"nodeType in NODE_TYPE_OPTIONS\"\r\n :key=\"nodeType.type\"\r\n class=\"add-menu-item\"\r\n @click=\"addNode(nodeType.type)\"\r\n >\r\n <div class=\"menu-icon\" :class=\"nodeType.iconClass\">\r\n <C_Icon :name=\"nodeType.icon\" :size=\"16\" />\r\n </div>\r\n <span class=\"menu-text\">{{ nodeType.label }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n </Teleport>\r\n\r\n <!-- 节点配置弹窗 - 拆分到独立组件 -->\r\n <NodeConfigModal\r\n v-model:show=\"showNodeConfig\"\r\n :current-node=\"currentNode\"\r\n :users=\"users\"\r\n :departments=\"departments\"\r\n @save=\"handleConfigSave\"\r\n @cancel=\"showNodeConfig = false\"\r\n />\r\n\r\n <!-- 验证错误日志抽屉 -->\r\n <NDrawer v-model:show=\"showValidationErrors\" :width=\"450\" placement=\"right\">\r\n <NDrawerContent title=\"流程验证结果\" closable>\r\n <div v-if=\"validationErrors.length === 0\" class=\"validation-success\">\r\n <div class=\"success-icon\">\r\n <C_Icon name=\"mdi:check-circle\" :size=\"32\" color=\"#52c41a\" />\r\n </div>\r\n <h3>验证通过</h3>\r\n <p>工作流配置正确,所有节点都已正确设置!</p>\r\n </div>\r\n\r\n <div v-else class=\"validation-errors\">\r\n <div class=\"error-summary\">\r\n <div class=\"error-icon\">\r\n <C_Icon name=\"mdi:alert-circle\" :size=\"24\" color=\"#ff4d4f\" />\r\n </div>\r\n <h3>发现 {{ validationErrors.length }} 个问题</h3>\r\n <p>请修复以下问题后重新验证:</p>\r\n </div>\r\n\r\n <div class=\"error-list\">\r\n <div\r\n v-for=\"(error, index) in validationErrors\"\r\n :key=\"error.nodeId\"\r\n class=\"error-item\"\r\n >\r\n <div class=\"error-header\">\r\n <span class=\"error-number\">{{ index + 1 }}</span>\r\n <div class=\"error-info\">\r\n <strong class=\"error-node\">{{ error.nodeName }}</strong>\r\n <span class=\"error-field\">{{\r\n getFieldDisplayName(error.field)\r\n }}</span>\r\n </div>\r\n <div class=\"error-type\" :class=\"error.type\">\r\n {{ getErrorTypeText(error.type) }}\r\n </div>\r\n </div>\r\n <div class=\"error-message\">{{ error.message }}</div>\r\n <div class=\"error-actions\">\r\n <NButton\r\n size=\"small\"\r\n type=\"primary\"\r\n @click=\"jumpToErrorNode(error.nodeId)\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:target\" :size=\"16\" />\r\n </template>\r\n 定位节点\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"validation-tips\">\r\n <h4>💡 常见问题解决方案:</h4>\r\n <ul>\r\n <li>\r\n <strong>审批人未设置:</strong\r\n >点击审批节点,在弹窗中选择审批人员\r\n </li>\r\n <li>\r\n <strong>条件分支未配置:</strong\r\n >点击条件节点,添加至少一个条件分支\r\n </li>\r\n <li>\r\n <strong>节点连接断开:</strong> 检查节点之间的连线是否正确\r\n </li>\r\n </ul>\r\n </div>\r\n </div>\r\n\r\n <template #footer>\r\n <div class=\"validation-footer\">\r\n <NButton @click=\"showValidationErrors = false\">关闭</NButton>\r\n <NButton type=\"primary\" @click=\"validateCurrentWorkflow\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:refresh\" :size=\"16\" />\r\n </template>\r\n 重新验证\r\n </NButton>\r\n </div>\r\n </template>\r\n </NDrawerContent>\r\n </NDrawer>\r\n\r\n <!-- 流程预览抽屉 -->\r\n <NDrawer v-model:show=\"showPreview\" :width=\"520\" placement=\"right\">\r\n <NDrawerContent title=\"流程预览\" closable>\r\n <!-- 统计概览 -->\r\n <div class=\"preview-stats\">\r\n <div class=\"stat-item\">\r\n <span class=\"stat-value\">{{ previewStats.totalNodes }}</span>\r\n <span class=\"stat-label\">总节点</span>\r\n </div>\r\n <div class=\"stat-item approval\">\r\n <span class=\"stat-value\">{{ previewStats.approvalNodes }}</span>\r\n <span class=\"stat-label\">审批</span>\r\n </div>\r\n <div class=\"stat-item copy\">\r\n <span class=\"stat-value\">{{ previewStats.copyNodes }}</span>\r\n <span class=\"stat-label\">抄送</span>\r\n </div>\r\n <div class=\"stat-item condition\">\r\n <span class=\"stat-value\">{{ previewStats.conditionNodes }}</span>\r\n <span class=\"stat-label\">条件</span>\r\n </div>\r\n <div class=\"stat-item edge\">\r\n <span class=\"stat-value\">{{ previewStats.totalEdges }}</span>\r\n <span class=\"stat-label\">连线</span>\r\n </div>\r\n </div>\r\n\r\n <!-- 流程步骤时间线 -->\r\n <div class=\"preview-timeline\">\r\n <div\r\n v-for=\"(step, index) in previewSteps\"\r\n :key=\"step.node.id\"\r\n class=\"preview-step\"\r\n :class=\"step.colorClass\"\r\n >\r\n <!-- 时间线连接线 -->\r\n <div class=\"step-connector\">\r\n <div class=\"step-dot\">\r\n <C_Icon :name=\"step.icon\" :size=\"16\" />\r\n </div>\r\n <div\r\n v-if=\"index < previewSteps.length - 1\"\r\n class=\"step-line\"\r\n ></div>\r\n </div>\r\n\r\n <!-- 步骤内容 -->\r\n <div class=\"step-content\">\r\n <div class=\"step-header\">\r\n <span class=\"step-order\">步骤 {{ step.order }}</span>\r\n <span class=\"step-type-badge\">{{ step.nodeTypeLabel }}</span>\r\n </div>\r\n <div class=\"step-title\">{{ step.node.data.title }}</div>\r\n <div v-if=\"step.details.length > 0\" class=\"step-details\">\r\n <div\r\n v-for=\"(detail, dIdx) in step.details\"\r\n :key=\"dIdx\"\r\n class=\"step-detail-item\"\r\n :class=\"{ 'is-warning': detail.type === 'warning' }\"\r\n >\r\n <span class=\"detail-label\">{{ detail.label }}:</span>\r\n\r\n <!-- 审批方式 badge -->\r\n <span\r\n v-if=\"detail.type === 'mode'\"\r\n class=\"mode-badge\"\r\n :class=\"detail.modeKey\"\r\n >\r\n {{ detail.value }}\r\n </span>\r\n\r\n <!-- 人员 tags -->\r\n <template v-else-if=\"detail.type === 'users'\">\r\n <NTag\r\n v-for=\"user in detail.users\"\r\n :key=\"user.name\"\r\n size=\"small\"\r\n :bordered=\"false\"\r\n round\r\n class=\"user-tag\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:account\" :size=\"12\" />\r\n </template>\r\n {{ user.name }}\r\n </NTag>\r\n </template>\r\n\r\n <!-- 警告文案 -->\r\n <span\r\n v-else-if=\"detail.type === 'warning'\"\r\n class=\"warning-text\"\r\n >\r\n ⚠️ {{ detail.value }}\r\n </span>\r\n\r\n <!-- 普通文本 -->\r\n <span v-else>{{ detail.value }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 空状态 -->\r\n <div v-if=\"previewSteps.length === 0\" class=\"preview-empty\">\r\n <C_Icon name=\"mdi:file-document-outline\" :size=\"48\" color=\"#d1d5db\" />\r\n <p>暂无流程节点</p>\r\n </div>\r\n\r\n <template #footer>\r\n <div class=\"preview-footer\">\r\n <NButton @click=\"closePreview\">关闭</NButton>\r\n <NButton type=\"primary\" @click=\"confirmAndSave\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:content-save\" :size=\"16\" />\r\n </template>\r\n 确认并保存\r\n </NButton>\r\n </div>\r\n </template>\r\n </NDrawerContent>\r\n </NDrawer>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref } from \"vue\";\r\nimport { VueFlow } from \"@vue-flow/core\";\r\nimport { NButton, NDrawer, NDrawerContent, NTag, useMessage } from \"naive-ui\";\r\nimport { C_Icon } from \"../C_Icon\";\r\nimport type { WorkflowProps, WorkflowEmits } from \"./types\";\r\nimport { NODE_TYPE_OPTIONS } from \"./data\";\r\nimport NodeConfigModal from \"./NodeConfigModal.vue\";\r\nimport { useWorkflowNodes } from \"./composables/useWorkflowNodes\";\r\nimport { useWorkflowValidation } from \"./composables/useWorkflowValidation\";\r\nimport { useWorkflowPreview } from \"./composables/useWorkflowPreview\";\r\n\r\ndefineOptions({ name: \"C_WorkFlow\" });\r\n\r\n/* Props & Emits */\r\nconst props = withDefaults(defineProps<WorkflowProps>(), {\r\n users: () => [],\r\n roles: () => [],\r\n departments: () => [],\r\n readonly: false,\r\n theme: \"light\",\r\n});\r\n\r\nconst emit = defineEmits<WorkflowEmits>();\r\nconst message = useMessage();\r\nconst vueFlowRef = ref();\r\n\r\n/* ─── 节点管理 ──────────────────────────────────────────── */\r\nconst {\r\n nodes,\r\n edges,\r\n showAddMenu,\r\n menuPosition,\r\n showNodeConfig,\r\n currentNode,\r\n nodeTypes,\r\n workflowStats,\r\n addNode,\r\n onNodeClick,\r\n closeAddMenu,\r\n handleConfigSave,\r\n resetNodes,\r\n getCurrentWorkflowData,\r\n fitView,\r\n deleteNode,\r\n} = useWorkflowNodes(props, emit, vueFlowRef);\r\n\r\n/* ─── 流程验证 ──────────────────────────────────────────── */\r\nconst {\r\n validationErrors,\r\n showValidationErrors,\r\n validateWorkflow,\r\n validateCurrentWorkflow,\r\n jumpToErrorNode,\r\n getFieldDisplayName,\r\n getErrorTypeText,\r\n resetValidation,\r\n} = useWorkflowValidation(nodes, edges, vueFlowRef, {\r\n onShowNodeConfig: (node) => {\r\n currentNode.value = node;\r\n showNodeConfig.value = true;\r\n },\r\n onValidateError: (errors) => emit(\"validate-error\", errors),\r\n});\r\n\r\n/* ─── 流程预览 ──────────────────────────────────────────── */\r\nconst { showPreview, previewSteps, previewStats, openPreview, closePreview } =\r\n useWorkflowPreview(nodes, edges);\r\n\r\n/* ─── 编排方法(跨 composable 协作) ────────────────────── */\r\nconst saveWorkflow = (): void => {\r\n const errors = validateWorkflow();\r\n if (errors.length > 0) {\r\n message.error(`工作流验证失败: ${errors[0].message}`);\r\n showValidationErrors.value = true;\r\n return;\r\n }\r\n const data = getCurrentWorkflowData();\r\n emit(\"save\", data);\r\n message.success(\"工作流保存成功\");\r\n};\r\n\r\nconst previewWorkflow = (): void => {\r\n openPreview();\r\n};\r\n\r\nconst confirmAndSave = (): void => {\r\n closePreview();\r\n saveWorkflow();\r\n};\r\n\r\nconst clearWorkflow = (): void => {\r\n resetNodes();\r\n resetValidation();\r\n};\r\n\r\n/* ─── 暴露方法 ──────────────────────────────────────────── */\r\ndefineExpose({\r\n validateWorkflow,\r\n getCurrentWorkflowData,\r\n saveWorkflow,\r\n previewWorkflow,\r\n deleteNode,\r\n stats: workflowStats,\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-08-01\r\n * @Description: 工作流/审批流组件(基于 Vue Flow)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"approval-workflow-container\">\r\n <!-- 浮动工具栏 -->\r\n <div class=\"floating-toolbar\">\r\n <NButton size=\"small\" type=\"primary\" @click=\"saveWorkflow\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:content-save\" :size=\"16\" />\r\n </template>\r\n 保存\r\n </NButton>\r\n <NButton size=\"small\" @click=\"previewWorkflow\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:eye\" :size=\"16\" />\r\n </template>\r\n 预览\r\n </NButton>\r\n <NButton\r\n size=\"small\"\r\n @click=\"validateCurrentWorkflow\"\r\n title=\"检查工作流配置是否正确\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:check-circle\" :size=\"16\" />\r\n </template>\r\n 验证流程\r\n </NButton>\r\n <div class=\"toolbar-divider\"></div>\r\n <NButton size=\"small\" @click=\"fitView\" title=\"适应窗口\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:fit-to-screen\" :size=\"16\" />\r\n </template>\r\n </NButton>\r\n <NButton\r\n size=\"small\"\r\n type=\"error\"\r\n @click=\"clearWorkflow\"\r\n title=\"清空画布\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:delete-sweep\" :size=\"16\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n\r\n <!-- Vue Flow 画布 -->\r\n <VueFlow\r\n ref=\"vueFlowRef\"\r\n v-model:nodes=\"nodes\"\r\n v-model:edges=\"edges\"\r\n :node-types=\"nodeTypes\"\r\n class=\"workflow-canvas\"\r\n :default-viewport=\"{ zoom: 1, x: 0, y: 0 }\"\r\n :min-zoom=\"0.5\"\r\n :max-zoom=\"2\"\r\n :fit-view-on-init=\"true\"\r\n :nodes-draggable=\"true\"\r\n :elements-selectable=\"true\"\r\n @node-click=\"onNodeClick\"\r\n @pane-click=\"closeAddMenu\"\r\n />\r\n\r\n <!-- 节点添加菜单 -->\r\n <Teleport to=\"body\">\r\n <div\r\n v-show=\"showAddMenu\"\r\n class=\"add-node-menu\"\r\n :style=\"{ left: menuPosition.x + 'px', top: menuPosition.y + 'px' }\"\r\n >\r\n <div class=\"add-menu-content\">\r\n <div\r\n v-for=\"nodeType in NODE_TYPE_OPTIONS\"\r\n :key=\"nodeType.type\"\r\n class=\"add-menu-item\"\r\n @click=\"addNode(nodeType.type)\"\r\n >\r\n <div class=\"menu-icon\" :class=\"nodeType.iconClass\">\r\n <C_Icon :name=\"nodeType.icon\" :size=\"16\" />\r\n </div>\r\n <span class=\"menu-text\">{{ nodeType.label }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n </Teleport>\r\n\r\n <!-- 节点配置弹窗 - 拆分到独立组件 -->\r\n <NodeConfigModal\r\n v-model:show=\"showNodeConfig\"\r\n :current-node=\"currentNode\"\r\n :users=\"users\"\r\n :departments=\"departments\"\r\n @save=\"handleConfigSave\"\r\n @cancel=\"showNodeConfig = false\"\r\n />\r\n\r\n <!-- 验证错误日志抽屉 -->\r\n <NDrawer v-model:show=\"showValidationErrors\" :width=\"450\" placement=\"right\">\r\n <NDrawerContent title=\"流程验证结果\" closable>\r\n <div v-if=\"validationErrors.length === 0\" class=\"validation-success\">\r\n <div class=\"success-icon\">\r\n <C_Icon name=\"mdi:check-circle\" :size=\"32\" color=\"#52c41a\" />\r\n </div>\r\n <h3>验证通过</h3>\r\n <p>工作流配置正确,所有节点都已正确设置!</p>\r\n </div>\r\n\r\n <div v-else class=\"validation-errors\">\r\n <div class=\"error-summary\">\r\n <div class=\"error-icon\">\r\n <C_Icon name=\"mdi:alert-circle\" :size=\"24\" color=\"#ff4d4f\" />\r\n </div>\r\n <h3>发现 {{ validationErrors.length }} 个问题</h3>\r\n <p>请修复以下问题后重新验证:</p>\r\n </div>\r\n\r\n <div class=\"error-list\">\r\n <div\r\n v-for=\"(error, index) in validationErrors\"\r\n :key=\"error.nodeId\"\r\n class=\"error-item\"\r\n >\r\n <div class=\"error-header\">\r\n <span class=\"error-number\">{{ index + 1 }}</span>\r\n <div class=\"error-info\">\r\n <strong class=\"error-node\">{{ error.nodeName }}</strong>\r\n <span class=\"error-field\">{{\r\n getFieldDisplayName(error.field)\r\n }}</span>\r\n </div>\r\n <div class=\"error-type\" :class=\"error.type\">\r\n {{ getErrorTypeText(error.type) }}\r\n </div>\r\n </div>\r\n <div class=\"error-message\">{{ error.message }}</div>\r\n <div class=\"error-actions\">\r\n <NButton\r\n size=\"small\"\r\n type=\"primary\"\r\n @click=\"jumpToErrorNode(error.nodeId)\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:target\" :size=\"16\" />\r\n </template>\r\n 定位节点\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"validation-tips\">\r\n <h4>💡 常见问题解决方案:</h4>\r\n <ul>\r\n <li>\r\n <strong>审批人未设置:</strong\r\n >点击审批节点,在弹窗中选择审批人员\r\n </li>\r\n <li>\r\n <strong>条件分支未配置:</strong\r\n >点击条件节点,添加至少一个条件分支\r\n </li>\r\n <li>\r\n <strong>节点连接断开:</strong> 检查节点之间的连线是否正确\r\n </li>\r\n </ul>\r\n </div>\r\n </div>\r\n\r\n <template #footer>\r\n <div class=\"validation-footer\">\r\n <NButton @click=\"showValidationErrors = false\">关闭</NButton>\r\n <NButton type=\"primary\" @click=\"validateCurrentWorkflow\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:refresh\" :size=\"16\" />\r\n </template>\r\n 重新验证\r\n </NButton>\r\n </div>\r\n </template>\r\n </NDrawerContent>\r\n </NDrawer>\r\n\r\n <!-- 流程预览抽屉 -->\r\n <NDrawer v-model:show=\"showPreview\" :width=\"520\" placement=\"right\">\r\n <NDrawerContent title=\"流程预览\" closable>\r\n <!-- 统计概览 -->\r\n <div class=\"preview-stats\">\r\n <div class=\"stat-item\">\r\n <span class=\"stat-value\">{{ previewStats.totalNodes }}</span>\r\n <span class=\"stat-label\">总节点</span>\r\n </div>\r\n <div class=\"stat-item approval\">\r\n <span class=\"stat-value\">{{ previewStats.approvalNodes }}</span>\r\n <span class=\"stat-label\">审批</span>\r\n </div>\r\n <div class=\"stat-item copy\">\r\n <span class=\"stat-value\">{{ previewStats.copyNodes }}</span>\r\n <span class=\"stat-label\">抄送</span>\r\n </div>\r\n <div class=\"stat-item condition\">\r\n <span class=\"stat-value\">{{ previewStats.conditionNodes }}</span>\r\n <span class=\"stat-label\">条件</span>\r\n </div>\r\n <div class=\"stat-item edge\">\r\n <span class=\"stat-value\">{{ previewStats.totalEdges }}</span>\r\n <span class=\"stat-label\">连线</span>\r\n </div>\r\n </div>\r\n\r\n <!-- 流程步骤时间线 -->\r\n <div class=\"preview-timeline\">\r\n <div\r\n v-for=\"(step, index) in previewSteps\"\r\n :key=\"step.node.id\"\r\n class=\"preview-step\"\r\n :class=\"step.colorClass\"\r\n >\r\n <!-- 时间线连接线 -->\r\n <div class=\"step-connector\">\r\n <div class=\"step-dot\">\r\n <C_Icon :name=\"step.icon\" :size=\"16\" />\r\n </div>\r\n <div\r\n v-if=\"index < previewSteps.length - 1\"\r\n class=\"step-line\"\r\n ></div>\r\n </div>\r\n\r\n <!-- 步骤内容 -->\r\n <div class=\"step-content\">\r\n <div class=\"step-header\">\r\n <span class=\"step-order\">步骤 {{ step.order }}</span>\r\n <span class=\"step-type-badge\">{{ step.nodeTypeLabel }}</span>\r\n </div>\r\n <div class=\"step-title\">{{ step.node.data.title }}</div>\r\n <div v-if=\"step.details.length > 0\" class=\"step-details\">\r\n <div\r\n v-for=\"(detail, dIdx) in step.details\"\r\n :key=\"dIdx\"\r\n class=\"step-detail-item\"\r\n :class=\"{ 'is-warning': detail.type === 'warning' }\"\r\n >\r\n <span class=\"detail-label\">{{ detail.label }}:</span>\r\n\r\n <!-- 审批方式 badge -->\r\n <span\r\n v-if=\"detail.type === 'mode'\"\r\n class=\"mode-badge\"\r\n :class=\"detail.modeKey\"\r\n >\r\n {{ detail.value }}\r\n </span>\r\n\r\n <!-- 人员 tags -->\r\n <template v-else-if=\"detail.type === 'users'\">\r\n <NTag\r\n v-for=\"user in detail.users\"\r\n :key=\"user.name\"\r\n size=\"small\"\r\n :bordered=\"false\"\r\n round\r\n class=\"user-tag\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:account\" :size=\"12\" />\r\n </template>\r\n {{ user.name }}\r\n </NTag>\r\n </template>\r\n\r\n <!-- 警告文案 -->\r\n <span\r\n v-else-if=\"detail.type === 'warning'\"\r\n class=\"warning-text\"\r\n >\r\n ⚠️ {{ detail.value }}\r\n </span>\r\n\r\n <!-- 普通文本 -->\r\n <span v-else>{{ detail.value }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 空状态 -->\r\n <div v-if=\"previewSteps.length === 0\" class=\"preview-empty\">\r\n <C_Icon name=\"mdi:file-document-outline\" :size=\"48\" color=\"#d1d5db\" />\r\n <p>暂无流程节点</p>\r\n </div>\r\n\r\n <template #footer>\r\n <div class=\"preview-footer\">\r\n <NButton @click=\"closePreview\">关闭</NButton>\r\n <NButton type=\"primary\" @click=\"confirmAndSave\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:content-save\" :size=\"16\" />\r\n </template>\r\n 确认并保存\r\n </NButton>\r\n </div>\r\n </template>\r\n </NDrawerContent>\r\n </NDrawer>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref } from \"vue\";\r\nimport { VueFlow } from \"@vue-flow/core\";\r\nimport { NButton, NDrawer, NDrawerContent, NTag, useMessage } from \"naive-ui\";\r\nimport { C_Icon } from \"../C_Icon\";\r\nimport type { WorkflowProps, WorkflowEmits } from \"./types\";\r\nimport { NODE_TYPE_OPTIONS } from \"./data\";\r\nimport NodeConfigModal from \"./NodeConfigModal.vue\";\r\nimport { useWorkflowNodes } from \"./composables/useWorkflowNodes\";\r\nimport { useWorkflowValidation } from \"./composables/useWorkflowValidation\";\r\nimport { useWorkflowPreview } from \"./composables/useWorkflowPreview\";\r\n\r\ndefineOptions({ name: \"C_WorkFlow\" });\r\n\r\n/* Props & Emits */\r\nconst props = withDefaults(defineProps<WorkflowProps>(), {\r\n users: () => [],\r\n roles: () => [],\r\n departments: () => [],\r\n readonly: false,\r\n theme: \"light\",\r\n});\r\n\r\nconst emit = defineEmits<WorkflowEmits>();\r\nconst message = useMessage();\r\nconst vueFlowRef = ref();\r\n\r\n/* ─── 节点管理 ──────────────────────────────────────────── */\r\nconst {\r\n nodes,\r\n edges,\r\n showAddMenu,\r\n menuPosition,\r\n showNodeConfig,\r\n currentNode,\r\n nodeTypes,\r\n workflowStats,\r\n addNode,\r\n onNodeClick,\r\n closeAddMenu,\r\n handleConfigSave,\r\n resetNodes,\r\n getCurrentWorkflowData,\r\n fitView,\r\n deleteNode,\r\n} = useWorkflowNodes(props, emit, vueFlowRef);\r\n\r\n/* ─── 流程验证 ──────────────────────────────────────────── */\r\nconst {\r\n validationErrors,\r\n showValidationErrors,\r\n validateWorkflow,\r\n validateCurrentWorkflow,\r\n jumpToErrorNode,\r\n getFieldDisplayName,\r\n getErrorTypeText,\r\n resetValidation,\r\n} = useWorkflowValidation(nodes, edges, vueFlowRef, {\r\n onShowNodeConfig: (node) => {\r\n currentNode.value = node;\r\n showNodeConfig.value = true;\r\n },\r\n onValidateError: (errors) => emit(\"validate-error\", errors),\r\n});\r\n\r\n/* ─── 流程预览 ──────────────────────────────────────────── */\r\nconst { showPreview, previewSteps, previewStats, openPreview, closePreview } =\r\n useWorkflowPreview(nodes, edges);\r\n\r\n/* ─── 编排方法(跨 composable 协作) ────────────────────── */\r\nconst saveWorkflow = (): void => {\r\n const errors = validateWorkflow();\r\n if (errors.length > 0) {\r\n message.error(`工作流验证失败: ${errors[0].message}`);\r\n showValidationErrors.value = true;\r\n return;\r\n }\r\n const data = getCurrentWorkflowData();\r\n emit(\"save\", data);\r\n message.success(\"工作流保存成功\");\r\n};\r\n\r\nconst previewWorkflow = (): void => {\r\n openPreview();\r\n};\r\n\r\nconst confirmAndSave = (): void => {\r\n closePreview();\r\n saveWorkflow();\r\n};\r\n\r\nconst clearWorkflow = (): void => {\r\n resetNodes();\r\n resetValidation();\r\n};\r\n\r\n/* ─── 暴露方法 ──────────────────────────────────────────── */\r\ndefineExpose({\r\n validateWorkflow,\r\n getCurrentWorkflowData,\r\n saveWorkflow,\r\n previewWorkflow,\r\n deleteNode,\r\n stats: workflowStats,\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n@use \"./index.scss\";\r\n</style>\r\n"],"mappings":"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"}
|