ItemDrawer.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. <template>
  2. <el-drawer
  3. :close-on-click-modal="false"
  4. class="local-launch_drawer-wrap"
  5. :title="record.processName"
  6. :model-value="modelValue"
  7. size="760px"
  8. @update:model-value="updateModelValue"
  9. >
  10. <div v-if="validateForm.loading" v-loading="true" class="local_loading"></div>
  11. <div class="info-wrap">
  12. <el-divider content-position="left">{{ record.processName }}表单</el-divider>
  13. <FormCreate v-if="false" v-model:api="validateForm.api" class="form-wrap" :option="validateForm.option" :rule="validateForm.rule" />
  14. <er-form-preview ref="EReditorRef" :is-show-complete-button="false" />
  15. <el-divider content-position="left">审批流程</el-divider>
  16. <el-timeline class="timeline-wrap">
  17. <el-timeline-item v-for="(v, index) in processTimelineList" :key="index">
  18. <template v-if="v.conditionNode === 1">
  19. <el-radio-group v-model="processChecked[v.name]" size="small">
  20. <el-radio-button v-for="c of v.conditionNodeList" :key="c.name" :label="c.name" />
  21. </el-radio-group>
  22. </template>
  23. <template v-else>
  24. <div style="padding-bottom: 6px">{{ v.name }}</div>
  25. <div v-if="assigneeMap[v.name]" style="display: flex; align-items: center; gap: 6px">
  26. <template v-if="assigneeMap[v.name].type === 1">
  27. <el-tooltip v-if="!assigneeMap[v.name].disabled" content="添加用户" placement="left">
  28. <el-button style="width: 32px" @click="selectHandler(v.name, 1)">
  29. <svg-icon style="font-size: 18px" icon-class="flow-user-add" />
  30. </el-button>
  31. </el-tooltip>
  32. <FlowNodeAvatar v-for="(item, index) in assigneeMap[v.name].assignees" :key="index" :name="item.name" style="margin-top: 5px" />
  33. </template>
  34. <template v-else>
  35. <el-tooltip v-if="!assigneeMap[v.name].disabled" content="添加角色" placement="left">
  36. <el-button style="width: 32px" @click="selectHandler(v.name, 2)">
  37. <svg-icon style="font-size: 18px" icon-class="flow-group-add" />
  38. </el-button>
  39. </el-tooltip>
  40. <FlowNodeAvatar v-for="(item, index) in assigneeMap[v.name].assignees" :key="index" :name="item.name" style="margin-top: 5px">
  41. <template #avatar>
  42. <svg-icon icon-class="flow-group" color="#fff" />
  43. </template>
  44. </FlowNodeAvatar>
  45. </template>
  46. </div>
  47. </template>
  48. </el-timeline-item>
  49. <el-timeline-item>
  50. <div style="padding-bottom: 6px">结束</div>
  51. </el-timeline-item>
  52. </el-timeline>
  53. <el-divider></el-divider>
  54. </div>
  55. <template #footer>
  56. <el-button @click="updateModelValue(false)">{{ $t('le.btn.cancel') }}</el-button>
  57. <el-button :disabled="validateForm.loading" type="primary" style="margin-left: 8px" @click="onSubmit">{{ $t('le.btn.confirm') }}</el-button>
  58. </template>
  59. </el-drawer>
  60. <!-- 选择人员/角色-->
  61. <use-select ref="useSelectRef" v-bind="active_selectOpts" @update:selected="updateActive_assigneeMap"></use-select>
  62. </template>
  63. <script setup lang="ts">
  64. import model from '@/api/flow/process'
  65. import viewForm from '@/utils/form'
  66. import { erFormPreview } from 'everright-formeditor'
  67. import 'Everright-formEditor/dist/style.css'
  68. import { ref, shallowRef, computed, reactive, watchEffect } from 'vue'
  69. import { ElMessage } from 'element-plus'
  70. import UseSelect from '@/components/scWorkflow/select.vue'
  71. import FlowNodeAvatar from '@/components/Flow/FlowNodeAvatar.vue'
  72. import { approveTypeOptions_config, selectModeOptions_config, setTypeOptions_config } from '@/components/scWorkflow/nodes/config'
  73. const EReditorRef = ref()
  74. type Props = {
  75. modelValue: boolean
  76. record: { processId: string; processName: string; [key: string]: any }
  77. }
  78. const props = defineProps<Props>()
  79. const emit = defineEmits<{
  80. 'update:modelValue': [bool: boolean] // 具名元组语法
  81. update: [value: string]
  82. }>()
  83. const updateModelValue = (bool: boolean) => emit('update:modelValue', bool)
  84. type Assignee = {
  85. type: 1 | 2 // 1: 用户 2: 角色
  86. assignees: { [key: string]: any /*name, id*/ }
  87. disabled?: boolean
  88. selectOpts?: any
  89. // minSelected?: number
  90. // maxSelected?: number
  91. }
  92. const useSelectRef = ref()
  93. const assigneeMap = ref<{
  94. [key: string]: Assignee
  95. }>({})
  96. const active_assigneeKey = ref<string>()
  97. const active_selectOpts = ref({})
  98. // const userMap = ref(new Map())
  99. // const active_assigneeMap = ref<Assignee>({})
  100. window.assigneeMap = assigneeMap
  101. const updateActive_assigneeMap = (assignees: any[]) => {
  102. // active_assigneeMap todo... { assignees: [], type }
  103. const _cur = assigneeMap.value[active_assigneeKey.value]
  104. if (_cur) {
  105. _cur.assignees = assignees
  106. }
  107. // assigneeMap.value[active_assigneeKey.value].assignees = assignees
  108. // Object.assign()
  109. }
  110. const selectHandler = (name: string, type: number) => {
  111. /*if (!userMap.value.get(name)) {
  112. userMap.value.set(name, { assignees: [], type })
  113. }
  114. useSelectRef.value.open(type, userMap.value.get(name).assignees)
  115. */
  116. if (!assigneeMap.value[name]) {
  117. assigneeMap.value[name] = { assignees: [], type }
  118. }
  119. // active_assigneeMap.value = assigneeMap.value[name]
  120. const config = assigneeMap.value[name]
  121. active_assigneeKey.value = name
  122. active_selectOpts.value = config.selectOpts || {}
  123. useSelectRef.value.open(type, config.assignees)
  124. }
  125. const FormCreate = viewForm.$form()
  126. const validateForm = ref({
  127. api: {},
  128. option: {
  129. submitBtn: false
  130. },
  131. rule: [],
  132. loading: false
  133. })
  134. const onSubmit = async () => {
  135. // todo 这里还需要做表单校验
  136. const formData = EReditorRef.value.getData()
  137. // console.log(formData)
  138. const api = validateForm.value.api
  139. // await api.validate()
  140. // const values = api.formData()
  141. // console.warn(values, 'values')
  142. const processId = props.record.processId
  143. validateForm.value.loading = true
  144. let processForm = JSON.parse(cur_processForm_str)
  145. processForm = { ...processForm, formData }
  146. // processForm.forEach(v => {
  147. // // 填写的数据存储(local_: 本地数据处理标识)
  148. // v.local_value = values[v.field]
  149. // })
  150. console.log(processForm, '===========')
  151. const _assigneeMap = assigneeMap.value
  152. const assigneeMap_ = Object.keys(_assigneeMap).reduce((obj, key) => {
  153. const _o = _assigneeMap[key]
  154. obj[key] = {
  155. assigneeList: _o.assignees,
  156. type: _o.type
  157. }
  158. return obj
  159. }, {})
  160. model
  161. .processLaunchApi({
  162. processId, // 流程ID
  163. processForm: JSON.stringify(processForm), // 流程表单JSON内容 & local_value 保存
  164. assigneeMap: assigneeMap_
  165. })
  166. .then(res => {
  167. ElMessage.success('提交成功')
  168. updateModelValue(false)
  169. })
  170. .finally(() => {
  171. validateForm.value.loading = false
  172. })
  173. }
  174. // 当前form 表单数据字符串
  175. let cur_processForm_str = '{}'
  176. const processChecked = reactive<{ [key: string]: any }>({
  177. /*'local_条件分支_期限': '短期'*/
  178. })
  179. const localProcessData = ref<any[]>([])
  180. let c_idx = 0
  181. const packageProcess = (data, list = []) => {
  182. return data.reduce((_list, config) => {
  183. if (config.conditionNode === 0) {
  184. console.log(config.name, 'name 普通节点名称', config, data)
  185. /* // todo...
  186. // 默认用户
  187. let type = 1
  188. let assignees = config.nodeUserList
  189. if (config.nodeRoleList) {
  190. // 存在设置角色
  191. type = 2
  192. assignees = config.nodeRoleList
  193. }
  194. // userMap.value.set(config.name, { assignees, type })
  195. // 流程名称
  196. assigneeMap.value[config.name] = { assignees, type }*/
  197. // 0,发起人 1,审批人 2,抄送人 3,条件审批 4,条件分支 5,办理子流程 6,定时器在务 7,触发器在务
  198. switch (+config.type) {
  199. case 0: {
  200. // 发起人
  201. console.error(1)
  202. break
  203. }
  204. case 1: {
  205. // 审批人
  206. const handleName = setTypeOptions_config[config.setType]
  207. // if (config.setType) {
  208. // 针对审核人 不同情况控制
  209. if (Reflect.has(config, 'setType')) {
  210. console.log(handleName, 'handleName', config)
  211. let disabled = false
  212. let selectOpts = {}
  213. switch (config.setType) {
  214. case 1:
  215. // 指定人员 (不允许重新选择) // 但展示
  216. disabled = true
  217. case 2:
  218. // 主管 (不需要选择)
  219. break
  220. case 3:
  221. // 角色 选择角色 (允许重新选择)
  222. assigneeMap.value[config.name] = { assignees: config.nodeRoleList, type: 2 }
  223. break
  224. case 4:
  225. // 发起人自选 (1: 选择一个人, 2: 选择多个人)
  226. // const isMultiple = config.selectMode === 2
  227. if (config.selectMode === 1) {
  228. selectOpts.maxSelected = 1
  229. }
  230. break
  231. case 5:
  232. // 发起人自己 (不能选择)
  233. // break
  234. case 6:
  235. // 连续多级主管 (不能选择)
  236. break
  237. default:
  238. assigneeMap.value[config.name] = { assignees: config.nodeUserList, type: 1, disabled: false, selectOpts }
  239. // 发起的时候不需要选择
  240. }
  241. }
  242. break
  243. }
  244. case 2: {
  245. // 抄送人
  246. // 选择人员 & allowSelection 控制 true 允许选择 否则 隐藏
  247. assigneeMap.value[config.name] = { assignees: config.nodeUserList, type: 1, disabled: !config.allowSelection }
  248. break
  249. }
  250. /*case 3: {
  251. // 条件审批
  252. console.error(1)
  253. break
  254. }
  255. case 4: {
  256. // 条件分支
  257. console.error(1)
  258. break
  259. }
  260. case 5: {
  261. // 办理子流程
  262. console.error(1)
  263. break
  264. }
  265. case 6: {
  266. // 定时器在务
  267. console.error(1)
  268. break
  269. }
  270. case 7: {
  271. // 触发器在务
  272. console.error(1)
  273. break
  274. }*/
  275. }
  276. _list.push(config)
  277. } else if (config.conditionNode === 1) {
  278. // 自定义标识key
  279. if (!config.name) {
  280. config.name = `local_${c_idx++}`
  281. }
  282. // console.log('条件节点', config)
  283. _list.push(config)
  284. if (Array.isArray(config.conditionNodeList) && config.conditionNodeList.length) {
  285. const _val = processChecked[config.name]
  286. let condition = config.conditionNodeList[0]
  287. if (_val) {
  288. config.conditionNodeList.some(_condition => {
  289. if (_condition.name === _val) {
  290. condition = _condition
  291. return true
  292. }
  293. })
  294. } else {
  295. // console.error('else......', condition)
  296. processChecked[config.name] = condition.name
  297. }
  298. // console.warn('条件节点', condition.name)
  299. if (Array.isArray(condition.childNode) && condition.childNode.length) {
  300. packageProcess(condition.childNode, _list)
  301. }
  302. }
  303. }
  304. return _list
  305. }, list)
  306. }
  307. const processTimelineList = computed(() => {
  308. return packageProcess(localProcessData.value)
  309. })
  310. watchEffect(() => {
  311. console.error(processTimelineList.value, 'processTimelineList todo')
  312. })
  313. validateForm.value.loading = true
  314. Promise.all([
  315. model.processListNodeMapApi(props.record.processId).then((res: any) => {
  316. localProcessData.value = res
  317. }),
  318. model.processDetailApi(props.record.processId).then(res => {
  319. /*let local_workflow = {}
  320. try {
  321. console.log(JSON.parse(res.modelContent))
  322. const modelContent = JSON.parse(res.modelContent)
  323. local_workflow = modelContent.nodeConfig ?? modelContent.childNode
  324. } catch (e) {}*/
  325. cur_processForm_str = res.processForm || '{}'
  326. // const x = JSON.parse(cur_processForm_str)
  327. // x[0].validate = [{ required: true, message: '请输入商品简介', trigger: 'blur' }]
  328. // processForm 动态表单
  329. // validateForm.value.rule = [...JSON.parse(cur_processForm_str) /*, { ...workflowItem, value: local_workflow }*/ /*, { type: 'input', field: 'test_3' }*/]
  330. const { formStructure } = JSON.parse(cur_processForm_str)
  331. EReditorRef.value.setData(formStructure)
  332. // validateForm.value.rule = JSON.parse(cur_processForm_str)
  333. })
  334. ]).finally(() => {
  335. validateForm.value.loading = false
  336. })
  337. </script>
  338. <style lang="scss">
  339. .local-launch_drawer-wrap {
  340. .el-drawer__header {
  341. display: flex;
  342. padding: 16px 24px;
  343. align-items: center;
  344. justify-content: space-between;
  345. background-color: var(--el-color-info-light-9);
  346. text-align: left;
  347. /* margin-right: 0; */
  348. margin-bottom: 0;
  349. }
  350. .el-drawer__close-btn {
  351. padding: 0;
  352. margin-right: -12px;
  353. }
  354. .el-drawer__body {
  355. position: relative;
  356. display: flex;
  357. flex-direction: column;
  358. }
  359. .el-drawer__footer {
  360. border-top: 1px solid var(--el-border-color-lighter);
  361. padding: 12px 24px;
  362. }
  363. .local_loading {
  364. position: absolute;
  365. left: 0;
  366. display: flex;
  367. align-items: center;
  368. justify-content: center;
  369. width: 100%;
  370. height: 100%;
  371. z-index: 999;
  372. background: rgba(0, 0, 0, 0.05);
  373. }
  374. }
  375. </style>
  376. <style scoped lang="scss">
  377. .info-wrap {
  378. //display: flex;
  379. .form-wrap {
  380. flex: 1;
  381. }
  382. .timeline-wrap {
  383. //margin-left: 8px;
  384. flex-shrink: 0;
  385. padding-left: 60px;
  386. //padding: 0;
  387. //width: 360px;
  388. }
  389. }
  390. </style>