@robot-admin/naive-ui-components 0.3.1 โ†’ 0.3.3

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.
Files changed (188) hide show
  1. package/dist/C_ActionBar-nnfbZCea.css.map +1 -0
  2. package/dist/C_ActionBar2.js +2 -2
  3. package/dist/C_ActionBar2.js.map +1 -1
  4. package/dist/C_AntV-DGjscTWa.css.map +1 -0
  5. package/dist/C_AntV2.js +6 -6
  6. package/dist/C_AntV2.js.map +1 -1
  7. package/dist/C_Barcode-DjTmDkbQ.css.map +1 -0
  8. package/dist/C_Barcode2.js +1 -1
  9. package/dist/C_Barcode2.js.map +1 -1
  10. package/dist/C_Captcha-Ccq3DMrR.css.map +1 -0
  11. package/dist/C_Captcha2.js +1 -1
  12. package/dist/C_Captcha2.js.map +1 -1
  13. package/dist/C_Cascade-IUUHIh7r.css.map +1 -0
  14. package/dist/C_Cascade2.js +2 -2
  15. package/dist/C_Cascade2.js.map +1 -1
  16. package/dist/C_City-Cv5BESaN.css.map +1 -0
  17. package/dist/C_City2.js +2 -2
  18. package/dist/C_City2.js.map +1 -1
  19. package/dist/C_Code-DPZlNSxL.css.map +1 -0
  20. package/dist/C_Code2.js +8 -7
  21. package/dist/C_Code2.js.map +1 -1
  22. package/dist/C_CollapsePanel-Fap-lv_5.css.map +1 -0
  23. package/dist/C_CollapsePanel2.js +1 -1
  24. package/dist/C_CollapsePanel2.js.map +1 -1
  25. package/dist/C_Cron-C0-8b5af.css.map +1 -0
  26. package/dist/C_Cron2.js +15 -14
  27. package/dist/C_Cron2.js.map +1 -1
  28. package/dist/C_Date2.js +1 -1
  29. package/dist/C_Date2.js.map +1 -1
  30. package/dist/C_Draggable-Bq6o0qXn.css.map +1 -0
  31. package/dist/C_Draggable2.js +1 -1
  32. package/dist/C_Draggable2.js.map +1 -1
  33. package/dist/C_Editor-OlxIF9-5.css.map +1 -0
  34. package/dist/C_Editor2.js +1 -1
  35. package/dist/C_Editor2.js.map +1 -1
  36. package/dist/C_FilePreview-B4XgTv-h.css.map +1 -0
  37. package/dist/C_FilePreview.cjs +1 -0
  38. package/dist/C_FilePreview.js +1 -0
  39. package/dist/C_FilePreview2.js +10 -9
  40. package/dist/C_FilePreview2.js.map +1 -1
  41. package/dist/C_Form-Cr9oX037.css.map +1 -0
  42. package/dist/C_Form.cjs +1 -0
  43. package/dist/C_Form.js +1 -0
  44. package/dist/C_Form2.js +69 -72
  45. package/dist/C_Form2.js.map +1 -1
  46. package/dist/C_FormSearch-DlIEoh7X.css.map +1 -0
  47. package/dist/C_FormSearch2.js +2 -2
  48. package/dist/C_FormSearch2.js.map +1 -1
  49. package/dist/C_FormulaEditor-Cm0CokN5.css.map +1 -0
  50. package/dist/C_FormulaEditor2.js +6 -6
  51. package/dist/C_FormulaEditor2.js.map +1 -1
  52. package/dist/C_FullCalendar-BULCIlVK.css.map +1 -0
  53. package/dist/C_FullCalendar2.js +2 -2
  54. package/dist/C_FullCalendar2.js.map +1 -1
  55. package/dist/C_Guide2.js +1 -1
  56. package/dist/C_Guide2.js.map +1 -1
  57. package/dist/C_Icon2.js.map +1 -1
  58. package/dist/C_ImageCropper-DrmUlaLi.css.map +1 -0
  59. package/dist/C_ImageCropper2.js +4 -4
  60. package/dist/C_ImageCropper2.js.map +1 -1
  61. package/dist/C_Language2.js +1 -1
  62. package/dist/C_Language2.js.map +1 -1
  63. package/dist/C_Map-WUMXSAfy.css.map +1 -0
  64. package/dist/C_Map2.js +2 -2
  65. package/dist/C_Map2.js.map +1 -1
  66. package/dist/C_Markdown-Dmv8yaM4.css.map +1 -0
  67. package/dist/C_Markdown2.js +4 -4
  68. package/dist/C_Markdown2.js.map +1 -1
  69. package/dist/C_NotificationCenter-DbgBiyqB.css.map +1 -0
  70. package/dist/C_NotificationCenter2.js +21 -20
  71. package/dist/C_NotificationCenter2.js.map +1 -1
  72. package/dist/C_Progress2.js +1 -1
  73. package/dist/C_Progress2.js.map +1 -1
  74. package/dist/C_QRCode-G7fiAkm4.css.map +1 -0
  75. package/dist/C_QRCode2.js +1 -1
  76. package/dist/C_QRCode2.js.map +1 -1
  77. package/dist/C_Signature-es-ZNPzr.css.map +1 -0
  78. package/dist/C_Signature2.js +2 -2
  79. package/dist/C_Signature2.js.map +1 -1
  80. package/dist/C_SplitPane-Br2eK8IG.css.map +1 -0
  81. package/dist/C_SplitPane2.js +1 -1
  82. package/dist/C_SplitPane2.js.map +1 -1
  83. package/dist/C_Steps-P9Qj9iDd.css.map +1 -0
  84. package/dist/C_Steps2.js +1 -1
  85. package/dist/C_Steps2.js.map +1 -1
  86. package/dist/C_Table-DAwAxr72.css.map +1 -0
  87. package/dist/C_Table.cjs +1 -0
  88. package/dist/C_Table.js +1 -0
  89. package/dist/C_Table2.js +3 -3
  90. package/dist/C_Table2.js.map +1 -1
  91. package/dist/C_Theme2.js +1 -1
  92. package/dist/C_Theme2.js.map +1 -1
  93. package/dist/C_Time-Bd_e1YDN.css.map +1 -0
  94. package/dist/C_Time2.js +2 -2
  95. package/dist/C_Time2.js.map +1 -1
  96. package/dist/C_Tree-DnGc_MPb.css.map +1 -0
  97. package/dist/C_Tree2.js +2 -2
  98. package/dist/C_Tree2.js.map +1 -1
  99. package/dist/C_Upload-i8LB_29U.css.map +1 -0
  100. package/dist/C_Upload2.js +5 -5
  101. package/dist/C_Upload2.js.map +1 -1
  102. package/dist/C_VideoPlayer-rm0MODUv.css.map +1 -0
  103. package/dist/C_VideoPlayer2.js +21 -20
  104. package/dist/C_VideoPlayer2.js.map +1 -1
  105. package/dist/C_VtableGantt-BpY-Rng3.css.map +1 -0
  106. package/dist/C_VtableGantt2.js +2 -2
  107. package/dist/C_VtableGantt2.js.map +1 -1
  108. package/dist/C_WaterFall-HWB-gPON.css.map +1 -0
  109. package/dist/C_WaterFall2.js +2 -2
  110. package/dist/C_WaterFall2.js.map +1 -1
  111. package/dist/C_WorkFlow-CSO86Cuc.css.map +1 -0
  112. package/dist/C_WorkFlow2.js +6 -6
  113. package/dist/C_WorkFlow2.js.map +1 -1
  114. package/dist/constants.d.ts +4 -4
  115. package/dist/constants2.d.ts +4 -4
  116. package/dist/constants3.d.ts +4 -4
  117. package/dist/constants4.d.ts +5 -5
  118. package/dist/constants5.d.ts +2 -2
  119. package/dist/data.d.ts +1 -1
  120. package/dist/index.js.map +1 -1
  121. package/dist/index.vue.d.ts +1 -1
  122. package/dist/index10.vue.d.ts +7 -7
  123. package/dist/index11.vue.d.ts +2 -2
  124. package/dist/index12.vue.d.ts +5 -5
  125. package/dist/index12.vue.d.ts.map +1 -1
  126. package/dist/index13.vue.d.ts +1 -1
  127. package/dist/index14.vue.d.ts +1 -1
  128. package/dist/index16.vue.d.ts +1 -1
  129. package/dist/index2.vue.d.ts +6 -6
  130. package/dist/index3.vue.d.ts +2 -2
  131. package/dist/index4.vue.d.ts +2 -2
  132. package/dist/index5.vue.d.ts +2 -2
  133. package/dist/index6.vue.d.ts +1 -1
  134. package/dist/index8.vue.d.ts +3 -3
  135. package/dist/resolver.js.map +1 -1
  136. package/dist/style.css +1555 -1555
  137. package/dist/useCalendarEvents.d.ts +2 -2
  138. package/dist/useCollapsePanel.d.ts +2 -2
  139. package/dist/useCropperCore.d.ts +3 -3
  140. package/dist/useDraggableLayout.d.ts +8 -8
  141. package/dist/useDynamicFormState.d.ts +111 -111
  142. package/dist/useDynamicFormState.d.ts.map +1 -1
  143. package/dist/useEdgeInteraction.d.ts +2 -2
  144. package/dist/useInfiniteScroll.d.ts +1 -1
  145. package/dist/useModalEdit.d.ts +4 -4
  146. package/dist/useModalEdit.d.ts.map +1 -1
  147. package/dist/useQRCode.d.ts +4 -4
  148. package/dist/useQRCode.d.ts.map +1 -1
  149. package/dist/useSearchState.d.ts +2 -2
  150. package/dist/useSignatureHistory.d.ts +4 -4
  151. package/dist/useSplitResize.d.ts +6 -6
  152. package/dist/useSplitResize.d.ts.map +1 -1
  153. package/dist/useTimeSelection.d.ts +2 -2
  154. package/dist/useTreeOperations.d.ts +6 -6
  155. package/dist/useWorkflowValidation.d.ts +5 -5
  156. package/package.json +2 -1
  157. package/dist/C_ActionBar-DWN-woTc.css.map +0 -1
  158. package/dist/C_AntV-AFKyK6hH.css.map +0 -1
  159. package/dist/C_Barcode-P_EFj8dC.css.map +0 -1
  160. package/dist/C_Captcha-C-ef41xw.css.map +0 -1
  161. package/dist/C_Cascade-D9kNsjsV.css.map +0 -1
  162. package/dist/C_City-BCQ4ipiK.css.map +0 -1
  163. package/dist/C_Code-C9kvvEmO.css.map +0 -1
  164. package/dist/C_CollapsePanel-BUJHuYcU.css.map +0 -1
  165. package/dist/C_Cron-yx2Ob4Jl.css.map +0 -1
  166. package/dist/C_Draggable-C483syRC.css.map +0 -1
  167. package/dist/C_Editor-Bp0SyIEw.css.map +0 -1
  168. package/dist/C_FilePreview-CPqvhoCy.css.map +0 -1
  169. package/dist/C_Form-Jx7PY3sT.css.map +0 -1
  170. package/dist/C_FormSearch-DvRgxlRn.css.map +0 -1
  171. package/dist/C_FormulaEditor-DtGkt4T_.css.map +0 -1
  172. package/dist/C_FullCalendar-BF7H0YIx.css.map +0 -1
  173. package/dist/C_ImageCropper-BVJfUufl.css.map +0 -1
  174. package/dist/C_Map-DpzeuWdX.css.map +0 -1
  175. package/dist/C_Markdown-BEjxknqd.css.map +0 -1
  176. package/dist/C_NotificationCenter-0l3TY2Gn.css.map +0 -1
  177. package/dist/C_QRCode-DbdiAIPg.css.map +0 -1
  178. package/dist/C_Signature-zhHCbra9.css.map +0 -1
  179. package/dist/C_SplitPane-C6sBsfKY.css.map +0 -1
  180. package/dist/C_Steps-CODHN5Hs.css.map +0 -1
  181. package/dist/C_Table-DSNsntmT.css.map +0 -1
  182. package/dist/C_Time-BvZLYraL.css.map +0 -1
  183. package/dist/C_Tree-0GDv--jX.css.map +0 -1
  184. package/dist/C_Upload-BXd3YYLx.css.map +0 -1
  185. package/dist/C_VideoPlayer-DYG3RL0Q.css.map +0 -1
  186. package/dist/C_VtableGantt-fhItIiHE.css.map +0 -1
  187. package/dist/C_WaterFall-8sQDFXKg.css.map +0 -1
  188. package/dist/C_WorkFlow-J-dyIuh9.css.map +0 -1
@@ -1 +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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"}
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","/* unplugin-vue-components disabled */<!--\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","/* unplugin-vue-components disabled */<!--\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","/* unplugin-vue-components disabled */<!--\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","/* unplugin-vue-components disabled */<!--\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","/* unplugin-vue-components disabled */<!--\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","/* unplugin-vue-components disabled */<!--\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","/* unplugin-vue-components disabled */<!--\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","/* unplugin-vue-components disabled */<!--\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","/* unplugin-vue-components disabled */<!--\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","/* unplugin-vue-components disabled */<!--\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","/* unplugin-vue-components disabled */<!--\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","/* unplugin-vue-components disabled */<!--\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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"}