index.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. <template>
  2. <ContentWrap>
  3. <!-- 列表 -->
  4. <XTable @register="registerTable">
  5. <template #toolbar_buttons>
  6. <!-- 操作:新增 -->
  7. <XButton
  8. type="primary"
  9. preIcon="ep:zoom-in"
  10. title="新建流程"
  11. v-hasPermi="['bpm:model:create']"
  12. @click="handleCreate"
  13. />
  14. <!-- 操作:导入 -->
  15. <XButton
  16. type="warning"
  17. preIcon="ep:upload"
  18. :title="'导入流程'"
  19. @click="handleImport"
  20. style="margin-left: 10px"
  21. />
  22. </template>
  23. <!-- 流程名称 -->
  24. <template #name_default="{ row }">
  25. <XTextButton :title="row.name" @click="handleBpmnDetail(row.id)" />
  26. </template>
  27. <!-- 流程分类 -->
  28. <template #category_default="{ row }">
  29. <DictTag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="Number(row?.category)" />
  30. </template>
  31. <!-- 表单信息 -->
  32. <template #formId_default="{ row }">
  33. <XTextButton
  34. v-if="row.formType === 10"
  35. :title="forms.find((form) => form.id === row.formId)?.name || row.formId"
  36. @click="handleFormDetail(row)"
  37. />
  38. <XTextButton v-else :title="row.formCustomCreatePath" @click="handleFormDetail(row)" />
  39. </template>
  40. <!-- 流程版本 -->
  41. <template #version_default="{ row }">
  42. <el-tag v-if="row.processDefinition">v{{ row.processDefinition.version }}</el-tag>
  43. <el-tag type="warning" v-else>未部署</el-tag>
  44. </template>
  45. <!-- 激活状态 -->
  46. <template #status_default="{ row }">
  47. <el-switch
  48. v-if="row.processDefinition"
  49. v-model="row.processDefinition.suspensionState"
  50. :active-value="1"
  51. :inactive-value="2"
  52. @change="handleChangeState(row)"
  53. />
  54. </template>
  55. <!-- 操作 -->
  56. <template #actionbtns_default="{ row }">
  57. <XTextButton
  58. preIcon="ep:edit"
  59. title="修改流程"
  60. v-hasPermi="['bpm:model:update']"
  61. @click="handleUpdate(row.id)"
  62. />
  63. <XTextButton
  64. preIcon="ep:setting"
  65. title="设计流程"
  66. v-hasPermi="['bpm:model:update']"
  67. @click="handleDesign(row)"
  68. />
  69. <XTextButton
  70. preIcon="ep:user"
  71. title="分配规则"
  72. v-hasPermi="['bpm:task-assign-rule:query']"
  73. @click="handleAssignRule(row)"
  74. />
  75. <XTextButton
  76. preIcon="ep:position"
  77. title="发布流程"
  78. v-hasPermi="['bpm:model:deploy']"
  79. @click="handleDeploy(row)"
  80. />
  81. <XTextButton
  82. preIcon="ep:aim"
  83. title="流程定义"
  84. v-hasPermi="['bpm:process-definition:query']"
  85. @click="handleDefinitionList(row)"
  86. />
  87. <!-- 操作:删除 -->
  88. <XTextButton
  89. preIcon="ep:delete"
  90. :title="t('action.del')"
  91. v-hasPermi="['bpm:model:delete']"
  92. @click="handleDelete(row.id)"
  93. />
  94. </template>
  95. </XTable>
  96. <!-- 对话框(添加 / 修改流程) -->
  97. <XModal v-model="dialogVisible" :title="dialogTitle" width="600">
  98. <el-form
  99. :loading="dialogLoading"
  100. el-form
  101. ref="saveFormRef"
  102. :model="saveForm"
  103. :rules="rules"
  104. label-width="110px"
  105. >
  106. <el-form-item label="流程标识" prop="key">
  107. <el-input
  108. v-model="saveForm.key"
  109. placeholder="请输入流标标识"
  110. style="width: 330px"
  111. :disabled="!!saveForm.id"
  112. />
  113. <el-tooltip
  114. v-if="!saveForm.id"
  115. class="item"
  116. effect="light"
  117. content="新建后,流程标识不可修改!"
  118. placement="top"
  119. >
  120. <i style="padding-left: 5px" class="el-icon-question"></i>
  121. </el-tooltip>
  122. <el-tooltip
  123. v-else
  124. class="item"
  125. effect="light"
  126. content="流程标识不可修改!"
  127. placement="top"
  128. >
  129. <i style="padding-left: 5px" class="el-icon-question"></i>
  130. </el-tooltip>
  131. </el-form-item>
  132. <el-form-item label="流程名称" prop="name">
  133. <el-input
  134. v-model="saveForm.name"
  135. placeholder="请输入流程名称"
  136. :disabled="!!saveForm.id"
  137. clearable
  138. />
  139. </el-form-item>
  140. <el-form-item v-if="saveForm.id" label="流程分类" prop="category">
  141. <el-select
  142. v-model="saveForm.category"
  143. placeholder="请选择流程分类"
  144. clearable
  145. style="width: 100%"
  146. >
  147. <el-option
  148. v-for="(dict, index) in getDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY)"
  149. :key="index"
  150. :label="dict.label"
  151. :value="dict.value"
  152. />
  153. </el-select>
  154. </el-form-item>
  155. <el-form-item label="流程描述" prop="description">
  156. <el-input type="textarea" v-model="saveForm.description" clearable />
  157. </el-form-item>
  158. <div v-if="saveForm.id">
  159. <el-form-item label="表单类型" prop="formType">
  160. <el-radio-group v-model="saveForm.formType">
  161. <el-radio
  162. v-for="dict in getDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE)"
  163. :key="parseInt(dict.value)"
  164. :label="parseInt(dict.value)"
  165. >
  166. {{ dict.label }}
  167. </el-radio>
  168. </el-radio-group>
  169. </el-form-item>
  170. <el-form-item v-if="saveForm.formType === 10" label="流程表单" prop="formId">
  171. <el-select v-model="saveForm.formId" clearable style="width: 100%">
  172. <el-option v-for="form in forms" :key="form.id" :label="form.name" :value="form.id" />
  173. </el-select>
  174. </el-form-item>
  175. <el-form-item
  176. v-if="saveForm.formType === 20"
  177. label="表单提交路由"
  178. prop="formCustomCreatePath"
  179. >
  180. <el-input
  181. v-model="saveForm.formCustomCreatePath"
  182. placeholder="请输入表单提交路由"
  183. style="width: 330px"
  184. />
  185. <el-tooltip
  186. class="item"
  187. effect="light"
  188. content="自定义表单的提交路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/create"
  189. placement="top"
  190. >
  191. <i style="padding-left: 5px" class="el-icon-question"></i>
  192. </el-tooltip>
  193. </el-form-item>
  194. <el-form-item
  195. v-if="saveForm.formType === 20"
  196. label="表单查看路由"
  197. prop="formCustomViewPath"
  198. >
  199. <el-input
  200. v-model="saveForm.formCustomViewPath"
  201. placeholder="请输入表单查看路由"
  202. style="width: 330px"
  203. />
  204. <el-tooltip
  205. class="item"
  206. effect="light"
  207. content="自定义表单的查看路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/view"
  208. placement="top"
  209. >
  210. <i style="padding-left: 5px" class="el-icon-question"></i>
  211. </el-tooltip>
  212. </el-form-item>
  213. </div>
  214. </el-form>
  215. <template #footer>
  216. <!-- 按钮:保存 -->
  217. <XButton
  218. type="primary"
  219. :loading="dialogLoading"
  220. @click="submitForm"
  221. :title="t('action.save')"
  222. />
  223. <!-- 按钮:关闭 -->
  224. <XButton
  225. :loading="dialogLoading"
  226. @click="dialogVisible = false"
  227. :title="t('dialog.close')"
  228. />
  229. </template>
  230. </XModal>
  231. <!-- 导入流程 -->
  232. <XModal v-model="importDialogVisible" width="400" title="导入流程">
  233. <div>
  234. <el-upload
  235. ref="uploadRef"
  236. :action="importUrl"
  237. :headers="uploadHeaders"
  238. :drag="true"
  239. :limit="1"
  240. :multiple="true"
  241. :show-file-list="true"
  242. :disabled="uploadDisabled"
  243. :on-exceed="handleExceed"
  244. :on-success="handleFileSuccess"
  245. :on-error="excelUploadError"
  246. :auto-upload="false"
  247. accept=".bpmn, .xml"
  248. name="bpmnFile"
  249. :data="importForm"
  250. >
  251. <Icon class="el-icon--upload" icon="ep:upload-filled" />
  252. <div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em> </div>
  253. <template #tip>
  254. <div class="el-upload__tip" style="color: red">
  255. 提示:仅允许导入“bpm”或“xml”格式文件!
  256. </div>
  257. <div>
  258. <el-form
  259. ref="importFormRef"
  260. :model="importForm"
  261. :rules="rules"
  262. label-width="120px"
  263. status-icon
  264. >
  265. <el-form-item label="流程标识" prop="key">
  266. <el-input
  267. v-model="importForm.key"
  268. placeholder="请输入流标标识"
  269. style="width: 250px"
  270. />
  271. </el-form-item>
  272. <el-form-item label="流程名称" prop="name">
  273. <el-input v-model="importForm.name" placeholder="请输入流程名称" clearable />
  274. </el-form-item>
  275. <el-form-item label="流程描述" prop="description">
  276. <el-input type="textarea" v-model="importForm.description" clearable />
  277. </el-form-item>
  278. </el-form>
  279. </div>
  280. </template>
  281. </el-upload>
  282. </div>
  283. <template #footer>
  284. <!-- 按钮:保存 -->
  285. <XButton
  286. type="warning"
  287. preIcon="ep:upload-filled"
  288. :title="t('action.save')"
  289. @click="submitFileForm"
  290. />
  291. <XButton title="取 消" @click="uploadClose" />
  292. </template>
  293. </XModal>
  294. <!-- 表单详情的弹窗 -->
  295. <XModal v-model="formDetailVisible" width="800" title="表单详情" :show-footer="false">
  296. <form-create
  297. :rule="formDetailPreview.rule"
  298. :option="formDetailPreview.option"
  299. v-if="formDetailVisible"
  300. />
  301. </XModal>
  302. <!-- 流程模型图的预览 -->
  303. <XModal title="流程图" v-model="showBpmnOpen" width="80%" height="90%">
  304. <MyProcessViewer
  305. key="designer"
  306. v-model="bpmnXML"
  307. :value="bpmnXML"
  308. v-bind="bpmnControlForm"
  309. :prefix="bpmnControlForm.prefix"
  310. />
  311. </XModal>
  312. </ContentWrap>
  313. </template>
  314. <script lang="ts" setup>
  315. // 全局相关的 import
  316. import { DICT_TYPE, getDictOptions } from '@/utils/dict'
  317. import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
  318. import { FormInstance, UploadInstance } from 'element-plus'
  319. // 业务相关的 import
  320. import { getAccessToken, getTenantId } from '@/utils/auth'
  321. import * as FormApi from '@/api/bpm/form'
  322. import * as ModelApi from '@/api/bpm/model'
  323. import { allSchemas, rules } from './model.data'
  324. import { setConfAndFields2 } from '@/utils/formCreate'
  325. defineOptions({ name: 'BpmModel' })
  326. const { t } = useI18n() // 国际化
  327. const message = useMessage() // 消息弹窗
  328. const router = useRouter() // 路由
  329. const showBpmnOpen = ref(false)
  330. const bpmnXML = ref(null)
  331. const bpmnControlForm = ref({
  332. prefix: 'flowable'
  333. })
  334. // ========== 列表相关 ==========
  335. const [registerTable, { reload }] = useXTable({
  336. allSchemas: allSchemas,
  337. getListApi: ModelApi.getModelPageApi
  338. })
  339. const forms = ref() // 流程表单的下拉框的数据
  340. // 设计流程
  341. const handleDesign = (row) => {
  342. console.log(row, '设计流程')
  343. router.push({
  344. name: 'BpmModelEditor',
  345. query: {
  346. modelId: row.id
  347. }
  348. })
  349. }
  350. // 跳转到指定流程定义列表
  351. const handleDefinitionList = (row) => {
  352. router.push({
  353. name: 'BpmProcessDefinition',
  354. query: {
  355. key: row.key
  356. }
  357. })
  358. }
  359. // 流程表单的详情按钮操作
  360. const formDetailVisible = ref(false)
  361. const formDetailPreview = ref({
  362. rule: [],
  363. option: {}
  364. })
  365. const handleFormDetail = async (row) => {
  366. if (row.formType == 10) {
  367. // 设置表单
  368. const data = await FormApi.getFormApi(row.formId)
  369. setConfAndFields2(formDetailPreview, data.conf, data.fields)
  370. // 弹窗打开
  371. formDetailVisible.value = true
  372. } else {
  373. await router.push({
  374. path: row.formCustomCreatePath
  375. })
  376. }
  377. }
  378. // 流程图的详情按钮操作
  379. const handleBpmnDetail = (row) => {
  380. // TODO 芋艿:流程组件开发中
  381. console.log(row)
  382. ModelApi.getModelApi(row).then((response) => {
  383. console.log(response, 'response')
  384. bpmnXML.value = response.bpmnXml
  385. // 弹窗打开
  386. showBpmnOpen.value = true
  387. })
  388. // message.success('流程组件开发中,预计 2 月底完成')
  389. }
  390. // 点击任务分配按钮
  391. const handleAssignRule = (row) => {
  392. router.push({
  393. name: 'BpmTaskAssignRule',
  394. query: {
  395. modelId: row.id
  396. }
  397. })
  398. }
  399. // ========== 新建/修改流程 ==========
  400. const dialogVisible = ref(false)
  401. const dialogTitle = ref('新建模型')
  402. const dialogLoading = ref(false)
  403. const saveForm = ref()
  404. const saveFormRef = ref<FormInstance>()
  405. // 设置标题
  406. const setDialogTile = async (type: string) => {
  407. dialogTitle.value = t('action.' + type)
  408. dialogVisible.value = true
  409. }
  410. // 新增操作
  411. const handleCreate = async () => {
  412. resetForm()
  413. await setDialogTile('create')
  414. }
  415. // 修改操作
  416. const handleUpdate = async (rowId: number) => {
  417. resetForm()
  418. await setDialogTile('edit')
  419. // 设置数据
  420. saveForm.value = await ModelApi.getModelApi(rowId)
  421. if (saveForm.value.category == null) {
  422. saveForm.value.category = '1'
  423. } else {
  424. saveForm.value.category = saveForm.value.category
  425. }
  426. }
  427. // 提交按钮
  428. const submitForm = async () => {
  429. // 参数校验
  430. const elForm = unref(saveFormRef)
  431. if (!elForm) return
  432. const valid = await elForm.validate()
  433. if (!valid) return
  434. // 提交请求
  435. dialogLoading.value = true
  436. try {
  437. const data = saveForm.value as ModelApi.ModelVO
  438. if (!data.id) {
  439. await ModelApi.createModelApi(data)
  440. message.success(t('common.createSuccess'))
  441. } else {
  442. await ModelApi.updateModelApi(data)
  443. message.success(t('common.updateSuccess'))
  444. }
  445. dialogVisible.value = false
  446. } finally {
  447. // 刷新列表
  448. await reload()
  449. dialogLoading.value = false
  450. }
  451. }
  452. // 重置表单
  453. const resetForm = () => {
  454. saveForm.value = {
  455. formType: 10,
  456. name: '',
  457. courseSort: '',
  458. description: '',
  459. formId: '',
  460. formCustomCreatePath: '',
  461. formCustomViewPath: ''
  462. }
  463. saveFormRef.value?.resetFields()
  464. }
  465. // ========== 删除 / 更新状态 / 发布流程 ==========
  466. // 删除流程
  467. const handleDelete = (rowId) => {
  468. message.delConfirm('是否删除该流程!!').then(async () => {
  469. await ModelApi.deleteModelApi(rowId)
  470. message.success(t('common.delSuccess'))
  471. // 刷新列表
  472. reload()
  473. })
  474. }
  475. // 更新状态操作
  476. const handleChangeState = (row) => {
  477. const id = row.id
  478. const state = row.processDefinition.suspensionState
  479. const statusState = state === 1 ? '激活' : '挂起'
  480. const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
  481. message
  482. .confirm(content)
  483. .then(async () => {
  484. await ModelApi.updateModelStateApi(id, state)
  485. message.success(t('部署成功'))
  486. // 刷新列表
  487. reload()
  488. })
  489. .catch(() => {
  490. // 取消后,进行恢复按钮
  491. row.processDefinition.suspensionState = state === 1 ? 2 : 1
  492. })
  493. }
  494. // 发布流程
  495. const handleDeploy = (row) => {
  496. message
  497. .confirm('是否部署该流程!!')
  498. .then(async () => {
  499. await ModelApi.deployModelApi(row.id)
  500. message.success(t('部署成功'))
  501. // 刷新列表
  502. reload()
  503. })
  504. .catch(() => {})
  505. }
  506. // ========== 导入流程 ==========
  507. const uploadRef = ref<UploadInstance>()
  508. let importUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/bpm/model/import'
  509. const uploadHeaders = ref()
  510. const importDialogVisible = ref(false)
  511. const uploadDisabled = ref(false)
  512. const importFormRef = ref<FormInstance>()
  513. const importForm = ref({
  514. key: '',
  515. name: '',
  516. description: ''
  517. })
  518. // 导入流程弹窗显示
  519. const handleImport = () => {
  520. importDialogVisible.value = true
  521. }
  522. // 文件数超出提示
  523. const handleExceed = (): void => {
  524. message.error('最多只能上传一个文件!')
  525. }
  526. // 上传错误提示
  527. const excelUploadError = (): void => {
  528. message.error('导入流程失败,请您重新上传!')
  529. }
  530. // 提交文件上传
  531. const submitFileForm = () => {
  532. uploadHeaders.value = {
  533. Authorization: 'Bearer ' + getAccessToken(),
  534. 'tenant-id': getTenantId()
  535. }
  536. uploadDisabled.value = true
  537. uploadRef.value!.submit()
  538. }
  539. // 文件上传成功
  540. const handleFileSuccess = async (response: any): Promise<void> => {
  541. if (response.code !== 0) {
  542. message.error(response.msg)
  543. return
  544. }
  545. // 重置表单
  546. uploadClose()
  547. // 提示,并刷新
  548. message.success('导入流程成功!请点击【设计流程】按钮,进行编辑保存后,才可以进行【发布流程】')
  549. await reload()
  550. }
  551. // 关闭文件上传
  552. const uploadClose = () => {
  553. // 关闭弹窗
  554. importDialogVisible.value = false
  555. // 重置上传状态和文件
  556. uploadDisabled.value = false
  557. uploadRef.value!.clearFiles()
  558. // 重置表单
  559. importForm.value = {
  560. key: '',
  561. name: '',
  562. description: ''
  563. }
  564. importFormRef.value?.resetFields()
  565. }
  566. // ========== 初始化 ==========
  567. onMounted(() => {
  568. // 获得流程表单的下拉框的数据
  569. FormApi.getSimpleFormsApi().then((data) => {
  570. forms.value = data
  571. })
  572. })
  573. </script>