node.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. import { cloneDeep } from 'lodash-es'
  2. import * as RoleApi from '@/api/system/role'
  3. import * as DeptApi from '@/api/system/dept'
  4. import * as PostApi from '@/api/system/post'
  5. import * as UserApi from '@/api/system/user'
  6. import * as UserGroupApi from '@/api/bpm/userGroup'
  7. import {
  8. SimpleFlowNode,
  9. CandidateStrategy,
  10. NodeType,
  11. ApproveMethodType,
  12. RejectHandlerType,
  13. NODE_DEFAULT_NAME,
  14. AssignStartUserHandlerType,
  15. AssignEmptyHandlerType,
  16. FieldPermissionType
  17. } from './consts'
  18. export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref<SimpleFlowNode> {
  19. const node = ref<SimpleFlowNode>(props.flowNode)
  20. watch(
  21. () => props.flowNode,
  22. (newValue) => {
  23. node.value = newValue
  24. }
  25. )
  26. return node
  27. }
  28. /**
  29. * @description 表单数据权限配置,用于发起人节点 、审批节点、抄送节点
  30. */
  31. export function useFormFieldsPermission(defaultPermission: FieldPermissionType) {
  32. // 字段权限配置. 需要有 field, title, permissioin 属性
  33. const fieldsPermissionConfig = ref<Array<Record<string, string>>>([])
  34. const formType = inject<Ref<number>>('formType') // 表单类型
  35. const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
  36. const getNodeConfigFormFields = (nodeFormFields?: Array<Record<string, string>>) => {
  37. nodeFormFields = toRaw(nodeFormFields)
  38. fieldsPermissionConfig.value =
  39. cloneDeep(nodeFormFields) || getDefaultFieldsPermission(unref(formFields))
  40. }
  41. // 默认的表单权限: 获取表单的所有字段,设置字段默认权限为只读
  42. const getDefaultFieldsPermission = (formFields?: string[]) => {
  43. const defaultFieldsPermission: Array<Record<string, string>> = []
  44. if (formFields) {
  45. formFields.forEach((fieldStr: string) => {
  46. parseFieldsSetDefaultPermission(JSON.parse(fieldStr), defaultFieldsPermission)
  47. })
  48. }
  49. return defaultFieldsPermission
  50. }
  51. // 解析字段。赋给默认权限
  52. const parseFieldsSetDefaultPermission = (
  53. rule: Record<string, any>,
  54. fieldsPermission: Array<Record<string, string>>,
  55. parentTitle: string = ''
  56. ) => {
  57. const { /**type,*/ field, title: tempTitle, children } = rule
  58. if (field && tempTitle) {
  59. let title = tempTitle
  60. if (parentTitle) {
  61. title = `${parentTitle}.${tempTitle}`
  62. }
  63. fieldsPermission.push({
  64. field,
  65. title,
  66. permission: defaultPermission
  67. })
  68. // TODO 子表单 需要处理子表单字段
  69. // if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
  70. // // 解析子表单的字段
  71. // rule.props.rule.forEach((item) => {
  72. // parseFieldsSetDefaultPermission(item, fieldsPermission, title)
  73. // })
  74. // }
  75. }
  76. if (children && Array.isArray(children)) {
  77. children.forEach((rule) => {
  78. parseFieldsSetDefaultPermission(rule, fieldsPermission)
  79. })
  80. }
  81. }
  82. return {
  83. formType,
  84. fieldsPermissionConfig,
  85. getNodeConfigFormFields
  86. }
  87. }
  88. /**
  89. * @description 获取表单的字段
  90. */
  91. export function useFormFields() {
  92. // 解析后的表单字段
  93. const formFields = inject<Ref<string[]>>('formFields') // 流程表单字段
  94. const parseFormFields = () => {
  95. const parsedFormFields: Array<Record<string, string>> = []
  96. if (formFields) {
  97. formFields.value.forEach((fieldStr: string) => {
  98. parseField(JSON.parse(fieldStr), parsedFormFields)
  99. })
  100. }
  101. return parsedFormFields
  102. }
  103. // 解析字段。
  104. const parseField = (
  105. rule: Record<string, any>,
  106. parsedFormFields: Array<Record<string, string>>,
  107. parentTitle: string = ''
  108. ) => {
  109. const { field, title: tempTitle, children, type } = rule
  110. if (field && tempTitle) {
  111. let title = tempTitle
  112. if (parentTitle) {
  113. title = `${parentTitle}.${tempTitle}`
  114. }
  115. parsedFormFields.push({
  116. field,
  117. title,
  118. type
  119. })
  120. // TODO 子表单 需要处理子表单字段
  121. // if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
  122. // // 解析子表单的字段
  123. // rule.props.rule.forEach((item) => {
  124. // parseFieldsSetDefaultPermission(item, fieldsPermission, title)
  125. // })
  126. // }
  127. }
  128. if (children && Array.isArray(children)) {
  129. children.forEach((rule) => {
  130. parseField(rule, parsedFormFields)
  131. })
  132. }
  133. }
  134. return parseFormFields()
  135. }
  136. export type UserTaskFormType = {
  137. //candidateParamArray: any[]
  138. candidateStrategy: CandidateStrategy
  139. approveMethod: ApproveMethodType
  140. roleIds?: number[] // 角色
  141. deptIds?: number[] // 部门
  142. deptLevel?: number // 部门层级
  143. userIds?: number[] // 用户
  144. userGroups?: number[] // 用户组
  145. postIds?: number[] // 岗位
  146. expression?: string // 流程表达式
  147. approveRatio?: number
  148. rejectHandlerType?: RejectHandlerType
  149. returnNodeId?: string
  150. timeoutHandlerEnable?: boolean
  151. timeoutHandlerType?: number
  152. assignEmptyHandlerType?: AssignEmptyHandlerType
  153. assignEmptyHandlerUserIds?: number[]
  154. assignStartUserHandlerType?: AssignStartUserHandlerType
  155. timeDuration?: number
  156. maxRemindCount?: number
  157. buttonsSetting: any[]
  158. }
  159. export type CopyTaskFormType = {
  160. // candidateParamArray: any[]
  161. candidateStrategy: CandidateStrategy
  162. roleIds?: number[] // 角色
  163. deptIds?: number[] // 部门
  164. deptLevel?: number // 部门层级
  165. userIds?: number[] // 用户
  166. userGroups?: number[] // 用户组
  167. postIds?: number[] // 岗位
  168. expression?: string // 流程表达式
  169. }
  170. /**
  171. * @description 节点表单数据。 用于审批节点、抄送节点
  172. */
  173. export function useNodeForm(nodeType: NodeType) {
  174. const roleOptions = inject<Ref<RoleApi.RoleVO[]>>('roleList') // 角色列表
  175. const postOptions = inject<Ref<PostApi.PostVO[]>>('postList') // 岗位列表
  176. const userOptions = inject<Ref<UserApi.UserVO[]>>('userList') // 用户列表
  177. const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList') // 部门列表
  178. const userGroupOptions = inject<Ref<UserGroupApi.UserGroupVO[]>>('userGroupList') // 用户组列表
  179. const deptTreeOptions = inject('deptTree') // 部门树
  180. const configForm = ref<UserTaskFormType | CopyTaskFormType>()
  181. if (nodeType === NodeType.USER_TASK_NODE) {
  182. configForm.value = {
  183. candidateStrategy: CandidateStrategy.USER,
  184. approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE,
  185. approveRatio: 100,
  186. rejectHandlerType: RejectHandlerType.FINISH_PROCESS,
  187. assignStartUserHandlerType: AssignStartUserHandlerType.START_USER_AUDIT,
  188. returnNodeId: '',
  189. timeoutHandlerEnable: false,
  190. timeoutHandlerType: 1,
  191. timeDuration: 6, // 默认 6小时
  192. maxRemindCount: 1, // 默认 提醒 1次
  193. buttonsSetting: []
  194. }
  195. } else {
  196. configForm.value = {
  197. candidateStrategy: CandidateStrategy.USER
  198. }
  199. }
  200. const getShowText = (): string => {
  201. let showText = ''
  202. // 指定成员
  203. if (configForm.value?.candidateStrategy === CandidateStrategy.USER) {
  204. if (configForm.value?.userIds!.length > 0) {
  205. const candidateNames: string[] = []
  206. userOptions?.value.forEach((item) => {
  207. if (configForm.value?.userIds!.includes(item.id)) {
  208. candidateNames.push(item.nickname)
  209. }
  210. })
  211. showText = `指定成员:${candidateNames.join(',')}`
  212. }
  213. }
  214. // 指定角色
  215. if (configForm.value?.candidateStrategy === CandidateStrategy.ROLE) {
  216. if (configForm.value.roleIds!.length > 0) {
  217. const candidateNames: string[] = []
  218. roleOptions?.value.forEach((item) => {
  219. if (configForm.value?.roleIds!.includes(item.id)) {
  220. candidateNames.push(item.name)
  221. }
  222. })
  223. showText = `指定角色:${candidateNames.join(',')}`
  224. }
  225. }
  226. // 指定部门
  227. if (
  228. configForm.value?.candidateStrategy === CandidateStrategy.DEPT_MEMBER ||
  229. configForm.value?.candidateStrategy === CandidateStrategy.DEPT_LEADER ||
  230. configForm.value?.candidateStrategy === CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
  231. ) {
  232. if (configForm.value?.deptIds!.length > 0) {
  233. const candidateNames: string[] = []
  234. deptOptions?.value.forEach((item) => {
  235. if (configForm.value?.deptIds!.includes(item.id!)) {
  236. candidateNames.push(item.name)
  237. }
  238. })
  239. if (configForm.value.candidateStrategy === CandidateStrategy.DEPT_MEMBER) {
  240. showText = `部门成员:${candidateNames.join(',')}`
  241. } else if (configForm.value.candidateStrategy === CandidateStrategy.DEPT_LEADER) {
  242. showText = `部门的负责人:${candidateNames.join(',')}`
  243. } else {
  244. showText = `多级部门的负责人:${candidateNames.join(',')}`
  245. }
  246. }
  247. }
  248. // 指定岗位
  249. if (configForm.value?.candidateStrategy === CandidateStrategy.POST) {
  250. if (configForm.value.postIds!.length > 0) {
  251. const candidateNames: string[] = []
  252. postOptions?.value.forEach((item) => {
  253. if (configForm.value?.postIds!.includes(item.id!)) {
  254. candidateNames.push(item.name)
  255. }
  256. })
  257. showText = `指定岗位: ${candidateNames.join(',')}`
  258. }
  259. }
  260. // 指定用户组
  261. if (configForm.value?.candidateStrategy === CandidateStrategy.USER_GROUP) {
  262. if (configForm.value?.userGroups!.length > 0) {
  263. const candidateNames: string[] = []
  264. userGroupOptions?.value.forEach((item) => {
  265. if (configForm.value?.userGroups!.includes(item.id)) {
  266. candidateNames.push(item.name)
  267. }
  268. })
  269. showText = `指定用户组: ${candidateNames.join(',')}`
  270. }
  271. }
  272. // 发起人自选
  273. if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER_SELECT) {
  274. showText = `发起人自选`
  275. }
  276. // 发起人自己
  277. if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER) {
  278. showText = `发起人自己`
  279. }
  280. // 发起人的部门负责人
  281. if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER_DEPT_LEADER) {
  282. showText = `发起人的部门负责人`
  283. }
  284. // 发起人的部门负责人
  285. if (
  286. configForm.value?.candidateStrategy === CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
  287. ) {
  288. showText = `发起人连续部门负责人`
  289. }
  290. // 流程表达式
  291. if (configForm.value?.candidateStrategy === CandidateStrategy.EXPRESSION) {
  292. showText = `流程表达式:${configForm.value.expression}`
  293. }
  294. return showText
  295. }
  296. /**
  297. * 处理候选人参数的赋值
  298. */
  299. const handleCandidateParam = () => {
  300. let candidateParam: undefined | string = undefined
  301. if (!configForm.value) {
  302. return candidateParam
  303. }
  304. switch (configForm.value.candidateStrategy) {
  305. case CandidateStrategy.USER:
  306. candidateParam = configForm.value.userIds!.join(',')
  307. break
  308. case CandidateStrategy.ROLE:
  309. candidateParam = configForm.value.roleIds!.join(',')
  310. break
  311. case CandidateStrategy.POST:
  312. candidateParam = configForm.value.postIds!.join(',')
  313. break
  314. case CandidateStrategy.USER_GROUP:
  315. candidateParam = configForm.value.userGroups!.join(',')
  316. break
  317. case CandidateStrategy.EXPRESSION:
  318. candidateParam = configForm.value.expression!
  319. break
  320. case CandidateStrategy.DEPT_MEMBER:
  321. case CandidateStrategy.DEPT_LEADER:
  322. candidateParam = configForm.value.deptIds!.join(',')
  323. break
  324. // 发起人部门负责人
  325. case CandidateStrategy.START_USER_DEPT_LEADER:
  326. case CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER:
  327. candidateParam = configForm.value.deptLevel + ''
  328. break
  329. // 指定连续多级部门的负责人
  330. case CandidateStrategy.MULTI_LEVEL_DEPT_LEADER: {
  331. // 候选人参数格式: | 分隔 。左边为部门(多个部门用 , 分隔)。 右边为部门层级
  332. const deptIds = configForm.value.deptIds!.join(',')
  333. candidateParam = deptIds.concat('|' + configForm.value.deptLevel + '')
  334. break
  335. }
  336. default:
  337. break
  338. }
  339. return candidateParam
  340. }
  341. /**
  342. * 解析候选人参数
  343. */
  344. const parseCandidateParam = (
  345. candidateStrategy: CandidateStrategy,
  346. candidateParam: string | undefined
  347. ) => {
  348. if (!configForm.value || !candidateParam) {
  349. return
  350. }
  351. switch (candidateStrategy) {
  352. case CandidateStrategy.USER: {
  353. configForm.value.userIds = candidateParam.split(',').map((item) => +item)
  354. break
  355. }
  356. case CandidateStrategy.ROLE:
  357. configForm.value.roleIds = candidateParam.split(',').map((item) => +item)
  358. break
  359. case CandidateStrategy.POST:
  360. configForm.value.postIds = candidateParam.split(',').map((item) => +item)
  361. break
  362. case CandidateStrategy.USER_GROUP:
  363. configForm.value.userGroups = candidateParam.split(',').map((item) => +item)
  364. break
  365. case CandidateStrategy.EXPRESSION:
  366. configForm.value.expression = candidateParam
  367. break
  368. case CandidateStrategy.DEPT_MEMBER:
  369. case CandidateStrategy.DEPT_LEADER:
  370. configForm.value.deptIds = candidateParam.split(',').map((item) => +item)
  371. break
  372. // 发起人部门负责人
  373. case CandidateStrategy.START_USER_DEPT_LEADER:
  374. case CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER:
  375. configForm.value.deptLevel = +candidateParam
  376. break
  377. // 指定连续多级部门的负责人
  378. case CandidateStrategy.MULTI_LEVEL_DEPT_LEADER: {
  379. // 候选人参数格式: | 分隔 。左边为部门(多个部门用 , 分隔)。 右边为部门层级
  380. const paramArray = candidateParam.split('|')
  381. configForm.value.deptIds = paramArray[0].split(',').map((item) => +item)
  382. configForm.value.deptLevel = +paramArray[1]
  383. break
  384. }
  385. default:
  386. break
  387. }
  388. }
  389. return {
  390. configForm,
  391. roleOptions,
  392. postOptions,
  393. userOptions,
  394. userGroupOptions,
  395. deptTreeOptions,
  396. handleCandidateParam,
  397. parseCandidateParam,
  398. getShowText
  399. }
  400. }
  401. /**
  402. * @description 抽屉配置
  403. */
  404. export function useDrawer() {
  405. // 抽屉配置是否可见
  406. const settingVisible = ref(false)
  407. // 关闭配置抽屉
  408. const closeDrawer = () => {
  409. settingVisible.value = false
  410. }
  411. // 打开配置抽屉
  412. const openDrawer = () => {
  413. settingVisible.value = true
  414. }
  415. return {
  416. settingVisible,
  417. closeDrawer,
  418. openDrawer
  419. }
  420. }
  421. /**
  422. * @description 节点名称配置
  423. */
  424. export function useNodeName(nodeType: NodeType) {
  425. // 节点名称
  426. const nodeName = ref<string>()
  427. // 节点名称输入框
  428. const showInput = ref(false)
  429. // 点击节点名称编辑图标
  430. const clickIcon = () => {
  431. showInput.value = true
  432. }
  433. // 节点名称输入框失去焦点
  434. const blurEvent = () => {
  435. showInput.value = false
  436. nodeName.value = nodeName.value || (NODE_DEFAULT_NAME.get(nodeType) as string)
  437. }
  438. return {
  439. nodeName,
  440. showInput,
  441. clickIcon,
  442. blurEvent
  443. }
  444. }
  445. export function useNodeName2(node: Ref<SimpleFlowNode>, nodeType: NodeType) {
  446. // 显示节点名称输入框
  447. const showInput = ref(false)
  448. // 节点名称输入框失去焦点
  449. const blurEvent = () => {
  450. showInput.value = false
  451. node.value.name = node.value.name || (NODE_DEFAULT_NAME.get(nodeType) as string)
  452. }
  453. // 点击节点标题进行输入
  454. const clickTitle = () => {
  455. showInput.value = true
  456. }
  457. return {
  458. showInput,
  459. clickTitle,
  460. blurEvent
  461. }
  462. }