index.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  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 in getDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY)"
  149. :key="dict.value"
  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. <my-process-viewer
  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 setup lang="ts">
  315. // 全局相关的 import
  316. import { DICT_TYPE, getDictOptions } from '@/utils/dict'
  317. import { FormInstance, UploadInstance } from 'element-plus'
  318. // 业务相关的 import
  319. import { getAccessToken, getTenantId } from '@/utils/auth'
  320. import * as FormApi from '@/api/bpm/form'
  321. import * as ModelApi from '@/api/bpm/model'
  322. import { allSchemas, rules } from './model.data'
  323. import { setConfAndFields2 } from '@/utils/formCreate'
  324. const { t } = useI18n() // 国际化
  325. const message = useMessage() // 消息弹窗
  326. const router = useRouter() // 路由
  327. const showBpmnOpen = ref(false)
  328. const bpmnXML = ref(null)
  329. const bpmnControlForm = ref({
  330. prefix: 'flowable'
  331. })
  332. // ========== 列表相关 ==========
  333. const [registerTable, { reload }] = useXTable({
  334. allSchemas: allSchemas,
  335. getListApi: ModelApi.getModelPageApi
  336. })
  337. const forms = ref() // 流程表单的下拉框的数据
  338. // 设计流程
  339. const handleDesign = (row) => {
  340. console.log(row, '设计流程')
  341. router.push({
  342. name: 'modelEditor',
  343. query: {
  344. modelId: row.id
  345. }
  346. })
  347. }
  348. // 跳转到指定流程定义列表
  349. const handleDefinitionList = (row) => {
  350. router.push({
  351. name: 'BpmProcessDefinitionList',
  352. query: {
  353. key: row.key
  354. }
  355. })
  356. }
  357. // 流程表单的详情按钮操作
  358. const formDetailVisible = ref(false)
  359. const formDetailPreview = ref({
  360. rule: [],
  361. option: {}
  362. })
  363. const handleFormDetail = async (row) => {
  364. if (row.formType == 10) {
  365. // 设置表单
  366. const data = await FormApi.getFormApi(row.formId)
  367. setConfAndFields2(formDetailPreview, data.conf, data.fields)
  368. // 弹窗打开
  369. formDetailVisible.value = true
  370. } else {
  371. await router.push({
  372. path: row.formCustomCreatePath
  373. })
  374. }
  375. }
  376. // 流程图的详情按钮操作
  377. const handleBpmnDetail = (row) => {
  378. // TODO 芋艿:流程组件开发中
  379. console.log(row)
  380. ModelApi.getModelApi(row).then((response) => {
  381. console.log(response, 'response')
  382. bpmnXML.value = response.bpmnXml
  383. // 弹窗打开
  384. showBpmnOpen.value = true
  385. })
  386. // message.success('流程组件开发中,预计 2 月底完成')
  387. }
  388. // 点击任务分配按钮
  389. const handleAssignRule = (row) => {
  390. router.push({
  391. name: 'BpmTaskAssignRuleList',
  392. query: {
  393. modelId: row.id
  394. }
  395. })
  396. }
  397. // ========== 新建/修改流程 ==========
  398. const dialogVisible = ref(false)
  399. const dialogTitle = ref('新建模型')
  400. const dialogLoading = ref(false)
  401. const saveForm = ref()
  402. const saveFormRef = ref<FormInstance>()
  403. // 设置标题
  404. const setDialogTile = async (type: string) => {
  405. dialogTitle.value = t('action.' + type)
  406. dialogVisible.value = true
  407. }
  408. // 新增操作
  409. const handleCreate = async () => {
  410. resetForm()
  411. await setDialogTile('create')
  412. }
  413. // 修改操作
  414. const handleUpdate = async (rowId: number) => {
  415. resetForm()
  416. await setDialogTile('edit')
  417. // 设置数据
  418. saveForm.value = await ModelApi.getModelApi(rowId)
  419. if (saveForm.value.category == null) {
  420. saveForm.value.category = 1
  421. } else {
  422. saveForm.value.category = Number(saveForm.value.category)
  423. }
  424. }
  425. // 提交按钮
  426. const submitForm = async () => {
  427. // 参数校验
  428. const elForm = unref(saveFormRef)
  429. if (!elForm) return
  430. const valid = await elForm.validate()
  431. if (!valid) return
  432. // 提交请求
  433. dialogLoading.value = true
  434. try {
  435. const data = saveForm.value as ModelApi.ModelVO
  436. if (!data.id) {
  437. await ModelApi.createModelApi(data)
  438. message.success(t('common.createSuccess'))
  439. } else {
  440. await ModelApi.updateModelApi(data)
  441. message.success(t('common.updateSuccess'))
  442. }
  443. dialogVisible.value = false
  444. } finally {
  445. // 刷新列表
  446. await reload()
  447. dialogLoading.value = false
  448. }
  449. }
  450. // 重置表单
  451. const resetForm = () => {
  452. saveForm.value = {
  453. formType: 10,
  454. name: '',
  455. courseSort: '',
  456. description: '',
  457. formId: '',
  458. formCustomCreatePath: '',
  459. formCustomViewPath: ''
  460. }
  461. saveFormRef.value?.resetFields()
  462. }
  463. // ========== 删除 / 更新状态 / 发布流程 ==========
  464. // 删除流程
  465. const handleDelete = (rowId) => {
  466. message.delConfirm('是否删除该流程!!').then(async () => {
  467. await ModelApi.deleteModelApi(rowId)
  468. message.success(t('common.delSuccess'))
  469. // 刷新列表
  470. reload()
  471. })
  472. }
  473. // 更新状态操作
  474. const handleChangeState = (row) => {
  475. const id = row.id
  476. const state = row.processDefinition.suspensionState
  477. const statusState = state === 1 ? '激活' : '挂起'
  478. const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
  479. message
  480. .confirm(content)
  481. .then(async () => {
  482. await ModelApi.updateModelStateApi(id, state)
  483. message.success(t('部署成功'))
  484. // 刷新列表
  485. reload()
  486. })
  487. .catch(() => {
  488. // 取消后,进行恢复按钮
  489. row.processDefinition.suspensionState = state === 1 ? 2 : 1
  490. })
  491. }
  492. // 发布流程
  493. const handleDeploy = (row) => {
  494. message.confirm('是否部署该流程!!').then(async () => {
  495. await ModelApi.deployModelApi(row.id)
  496. message.success(t('部署成功'))
  497. // 刷新列表
  498. reload()
  499. })
  500. }
  501. // ========== 导入流程 ==========
  502. const uploadRef = ref<UploadInstance>()
  503. let importUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/bpm/model/import'
  504. const uploadHeaders = ref()
  505. const importDialogVisible = ref(false)
  506. const uploadDisabled = ref(false)
  507. const importFormRef = ref<FormInstance>()
  508. const importForm = ref({
  509. key: '',
  510. name: '',
  511. description: ''
  512. })
  513. // 导入流程弹窗显示
  514. const handleImport = () => {
  515. importDialogVisible.value = true
  516. }
  517. // 文件数超出提示
  518. const handleExceed = (): void => {
  519. message.error('最多只能上传一个文件!')
  520. }
  521. // 上传错误提示
  522. const excelUploadError = (): void => {
  523. message.error('导入流程失败,请您重新上传!')
  524. }
  525. // 提交文件上传
  526. const submitFileForm = () => {
  527. uploadHeaders.value = {
  528. Authorization: 'Bearer ' + getAccessToken(),
  529. 'tenant-id': getTenantId()
  530. }
  531. uploadDisabled.value = true
  532. uploadRef.value!.submit()
  533. }
  534. // 文件上传成功
  535. const handleFileSuccess = async (response: any): Promise<void> => {
  536. if (response.code !== 0) {
  537. message.error(response.msg)
  538. return
  539. }
  540. // 重置表单
  541. uploadClose()
  542. // 提示,并刷新
  543. message.success('导入流程成功!请点击【设计流程】按钮,进行编辑保存后,才可以进行【发布流程】')
  544. await reload()
  545. }
  546. // 关闭文件上传
  547. const uploadClose = () => {
  548. // 关闭弹窗
  549. importDialogVisible.value = false
  550. // 重置上传状态和文件
  551. uploadDisabled.value = false
  552. uploadRef.value!.clearFiles()
  553. // 重置表单
  554. importForm.value = {
  555. key: '',
  556. name: '',
  557. description: ''
  558. }
  559. importFormRef.value?.resetFields()
  560. }
  561. // ========== 初始化 ==========
  562. onMounted(() => {
  563. // 获得流程表单的下拉框的数据
  564. FormApi.getSimpleFormsApi().then((data) => {
  565. forms.value = data
  566. })
  567. })
  568. </script>