index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. <template>
  2. <el-row class="process-list" :gutter="20">
  3. <el-col :span="6" :xs="24">
  4. <div class="header" v-if="show">
  5. <el-row>
  6. <el-col :span="22">
  7. <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
  8. <el-tab-pane name="1">
  9. <template #label>
  10. <span class="custom-tabs-label">
  11. 待审<el-badge :value="count.todoCount" class="item" />
  12. </span>
  13. </template>
  14. </el-tab-pane>
  15. <el-tab-pane label="已审" name="2" />
  16. <el-tab-pane label="已发起" name="3">
  17. <template #label>
  18. <span class="custom-tabs-label">
  19. 已发起<el-badge :value="count.myCreateCount" class="item" />
  20. </span>
  21. </template>
  22. </el-tab-pane>
  23. <el-tab-pane label="抄送我的" name="4">
  24. <template #label>
  25. <span class="custom-tabs-label">
  26. 抄送我的<el-badge :value="count.copyCount" class="item" />
  27. </span>
  28. </template>
  29. </el-tab-pane>
  30. </el-tabs>
  31. </el-col>
  32. <el-col :span="2">
  33. <Icon icon="ep:search" :size="20" @click="show=false"/>
  34. </el-col>
  35. </el-row>
  36. </div>
  37. <el-form v-else :inline="true" :model="queryParams" ref="queryFormRef" class="searchContent">
  38. <el-row>
  39. <el-col :span="20">
  40. <el-input placeholder="请输入审批内容" v-model="queryParams.templateContent" @change="handleQuery"/>
  41. </el-col>
  42. <el-col :span="4">
  43. <el-button class="mt2" @click="show=true" link type="primary">取消</el-button>
  44. </el-col>
  45. </el-row>
  46. </el-form>
  47. <div
  48. v-if="selectableTaskIds.length"
  49. class="batch-select-bar"
  50. @click.stop
  51. >
  52. <el-checkbox
  53. :model-value="isAllSelectableSelected"
  54. :indeterminate="isPartialSelectableSelected"
  55. @change="onSelectAllChange"
  56. >
  57. 全选当前列表({{ selectedSelectableCount }}/{{ selectableTaskIds.length }})
  58. </el-checkbox>
  59. </div>
  60. <div v-infinite-scroll="load" class="infinite-list scrollLeft" v-loading="loading" v-if="list.length">
  61. <el-checkbox-group v-model="processInstanceIds">
  62. <el-card shadow="never" v-for="(item, index) in list" :key="index" :class="['card-item', {'selected': currItem.processInstanceId == item.processInstanceId}]" @click="handleClickCard(item)">
  63. <el-badge v-show="item.readStatus == 0 && currItem.processInstanceId != item.processInstanceId" is-dot class="badge" />
  64. <div class="title mb5 mt2">
  65. <span class="lh16 mr-1">{{ item.templateContent }}</span>
  66. <dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="item.status"/>
  67. <span class="ml2">
  68. <el-checkbox :value="item.id" v-if="item.status == 1"/>
  69. </span>
  70. </div>
  71. <div class="mb4 c6 lh14">申请类型:<span class="c3">{{ item.applicationType }}</span></div>
  72. <div class="mb4 c6 lh14">申请编号:<span class="c3">{{ item.processInstanceId }}</span></div>
  73. <div class="mb2 c6 lh14">所属组织:<span class="c3">{{ item.tenantName }}</span></div>
  74. </el-card>
  75. </el-checkbox-group>
  76. </div>
  77. <div class="no-content" v-else>
  78. <span>暂无数据</span>
  79. </div>
  80. </el-col>
  81. <el-col :span="18" :xs="24">
  82. <el-button @click="handlePass" type="primary" :loading="batchLoading">批量通过</el-button>
  83. <div class="content">
  84. <ProcessDetail v-if="obj.processInstanceId" :id="obj.processInstanceId" :taskName="obj.templateContent" ref="processDetailRef" @success="getList(false, true)"/>
  85. <div v-else class="tc">
  86. <div></div>
  87. </div>
  88. </div>
  89. </el-col>
  90. </el-row>
  91. </template>
  92. <script lang="ts" setup>
  93. import * as ProcessInstanceApi from '@/api/bpm/processInstance'
  94. import { DICT_TYPE } from '@/utils/dict'
  95. import { config } from '@/config/axios/config'
  96. import ProcessDetail from './detail.vue'
  97. import axios from '@/config/axios'
  98. import { message } from 'ant-design-vue'
  99. defineOptions({ name: 'BpmProcessList' })
  100. const { base_url } = config
  101. const activeName = ref('1')
  102. const processInstanceIds = ref<any[]>([])
  103. const route = useRoute()
  104. const loading = ref(false)
  105. const list = ref<any>([])
  106. const total = ref(0)
  107. const queryParams = reactive({
  108. pageNo: 1,
  109. pageSize: 10,
  110. queryType: '1',
  111. templateContent: ''
  112. })
  113. const queryFormRef = ref()
  114. const show = ref(true)
  115. const batchLoading = ref(false) // 批量审批按钮
  116. // 点击tab页签
  117. const handleClick = (tab) => {
  118. activeName.value = tab.paneName
  119. queryParams.queryType = tab.paneName
  120. list.value = []
  121. processInstanceIds.value = []
  122. queryParams.pageNo = 1
  123. getList()
  124. // 清空路由
  125. route.query.activeName = ''
  126. route.query.processId = ''
  127. route.query.status = ''
  128. }
  129. const currItem: any = ref({})
  130. const getList = async (flag: boolean = false, refresh=false) => { // flag 存在数据, refresh 是否重载页面数据
  131. try {
  132. loading.value = true
  133. const res = await ProcessInstanceApi.getMyPage(queryParams)
  134. if(refresh){
  135. list.value = res.list
  136. }else{
  137. list.value = list.value.concat(res.list)
  138. }
  139. total.value = res.total
  140. if(flag && res.list[0]){
  141. handleClickCard(res.list[0])
  142. currItem.value = res.list[0]
  143. }
  144. } finally {
  145. loading.value = false
  146. }
  147. }
  148. const load = () => {
  149. if(queryParams.pageNo * queryParams.pageSize < total.value){
  150. queryParams.pageNo += 1
  151. getList()
  152. }
  153. }
  154. const statusType = ref('')
  155. const businessKey = ref('')
  156. const obj: any = ref({})
  157. const handleClickCard = (item: any) => {
  158. obj.value = item
  159. currItem.value = item
  160. updateRead(item)
  161. }
  162. /** 搜索按钮操作 */
  163. const handleQuery = () => {
  164. queryParams.pageNo = 1
  165. processInstanceIds.value = []
  166. getList()
  167. }
  168. // 获取未读数量
  169. const count = ref({ copyCount: 0, todoCount: 0, myCreateCount: 0 })
  170. const getCount = async () => {
  171. const res = await ProcessInstanceApi.getProcessInstanceBpmnCount()
  172. count.value = res
  173. }
  174. // 标记已读
  175. const updateRead = async (item) => {
  176. if((queryParams.queryType != '3' && queryParams.queryType != '1') && item.messageId){
  177. await ProcessInstanceApi.updateReadById(item.messageId)
  178. item.readStatus = 1
  179. }else if(item.processInstanceId){
  180. await ProcessInstanceApi.updateMyCreateReadById(item.processInstanceId)
  181. item.readStatus = 1
  182. }
  183. }
  184. const processDetailRef = ref()
  185. /** 左侧列表中可进行批量审批的项(status == 1)对应的任务 id */
  186. const selectableTaskIds = computed(() =>
  187. list.value.filter((item) => item.status == 1).map((item) => item.id)
  188. )
  189. const selectedSelectableCount = computed(() =>
  190. selectableTaskIds.value.filter((id) => processInstanceIds.value.includes(id)).length
  191. )
  192. const isAllSelectableSelected = computed(
  193. () =>
  194. selectableTaskIds.value.length > 0 &&
  195. selectedSelectableCount.value === selectableTaskIds.value.length
  196. )
  197. const isPartialSelectableSelected = computed(
  198. () =>
  199. selectedSelectableCount.value > 0 &&
  200. selectedSelectableCount.value < selectableTaskIds.value.length
  201. )
  202. const onSelectAllChange = (checked: boolean) => {
  203. if (checked) {
  204. processInstanceIds.value = [...new Set([...processInstanceIds.value, ...selectableTaskIds.value])]
  205. } else {
  206. const drop = new Set(selectableTaskIds.value)
  207. processInstanceIds.value = processInstanceIds.value.filter((id) => !drop.has(id))
  208. }
  209. }
  210. // 批量通过
  211. const handlePass = () => {
  212. batchLoading.value = true
  213. let queue = processInstanceIds.value.map(item => {
  214. return new Promise((resolve, reject) => {
  215. axios.put({url: base_url + `/bpm/task/approve`, data: {
  216. id: item,
  217. reason: '批量通过',
  218. variables: {} // 审批通过, 把修改的字段值赋于流程实例变量
  219. }}).then((res) => {
  220. console.log(`获取的code为: ${res}`);
  221. resolve(res)
  222. }).catch((err) => {
  223. reject(err)
  224. batchLoading.value = true
  225. })
  226. })
  227. })
  228. Promise.all(queue).then(async result => {
  229. batchLoading.value = false
  230. // 判断所有值为0的时候审批成功
  231. const allApproved = result.every(item => item === true);
  232. if (allApproved) {
  233. message.success('所有审批通过成功')
  234. // 这里可以执行审批成功后的逻辑
  235. } else {
  236. console.log("存在未通过的审批!");
  237. // 这里可以执行审批失败后的逻辑
  238. message.error('审批不成功')
  239. }
  240. // 清空值
  241. processInstanceIds.value = []
  242. await getList(false, true)
  243. // 找到对应的id
  244. processDetailRef.value.refresh()
  245. getCount()
  246. })
  247. }
  248. watch(
  249. () => route.query.activeName,
  250. () => {
  251. if (route.query.activeName) {
  252. activeName.value = route.query.activeName as string
  253. businessKey.value = route.query.processId as string
  254. statusType.value = route.query.status as string
  255. }
  256. },
  257. { immediate: true }
  258. )
  259. onMounted(async () => {
  260. getCount()
  261. await getList(true)
  262. })
  263. </script>
  264. <style lang="scss" scoped>
  265. .process-list{
  266. font-size: 16px;
  267. .title{
  268. font-weight: bold;
  269. }
  270. .card-item{
  271. position: relative;
  272. margin-bottom: 5px;
  273. border-radius: 8px;
  274. border: 2px solid #e5e5e5;
  275. &:hover{
  276. background-color: #f3f4f6;
  277. }
  278. &.selected{
  279. border: 2px solid var(--el-color-primary);
  280. background-color: color-mix(in srgb, var(--el-color-primary) 10%, white);
  281. }
  282. .c6{
  283. color: #666;
  284. font-size: 14px;
  285. }
  286. .c3{
  287. color: #333;
  288. font-size: 14px;
  289. }
  290. .status-label{
  291. position: absolute;
  292. right: -15px;
  293. top: -6px;
  294. width: 40px;
  295. height: 24px;
  296. background: var(--el-color-primary);
  297. text-align: center;
  298. transform: rotate(45deg);
  299. .check{
  300. color: #fff;
  301. font-size: 12px !important;
  302. margin-top: 11px;
  303. transform: rotate(-45deg);
  304. }
  305. }
  306. .badge{
  307. position: absolute;
  308. top: 10px;
  309. right: 10px;
  310. }
  311. }
  312. .content{
  313. background-color: #fff;
  314. border-radius: 4px;
  315. }
  316. }
  317. </style>
  318. <style lang="scss">
  319. .process-list{
  320. position: relative;
  321. .searchContent{
  322. padding: 10px;
  323. border-radius: 4px;
  324. background-color: #fff;
  325. }
  326. .header{
  327. // display: flex;
  328. // align-items: flex-start;
  329. border-radius: 4px;
  330. background-color: #fff;
  331. .demo-tabs{
  332. .el-tabs__header{
  333. margin: 0;
  334. padding-bottom: 10px;
  335. }
  336. }
  337. .el-icon{
  338. padding-top: 20px;
  339. padding-left: 10px;
  340. cursor: pointer;
  341. }
  342. }
  343. .no-content{
  344. background-color: #fff;
  345. height: 77vh;
  346. margin-top: 10px;
  347. font-size: 20px;
  348. color: #ccc;
  349. display: flex;
  350. justify-content: center;
  351. align-items: center;
  352. border-radius: 8px;
  353. }
  354. .el-card{
  355. .el-card__body{
  356. padding: 10px 20px !important;
  357. }
  358. }
  359. .el-tabs__item{
  360. padding: 0 10px !important;
  361. }
  362. .el-scrollbar{
  363. height: auto;
  364. }
  365. .tc{
  366. display: flex;
  367. justify-content: center;
  368. align-items: center;
  369. height: 100%;
  370. font-size: 26px;
  371. color: #ccc;
  372. }
  373. .batch-select-bar{
  374. padding: 8px 12px;
  375. margin-top: 8px;
  376. background-color: #fff;
  377. border-radius: 8px;
  378. font-size: 14px;
  379. }
  380. .scrollLeft{
  381. padding: 10px 0;
  382. height: 78vh;
  383. overflow-y: auto;
  384. }
  385. ::-webkit-scrollbar {
  386. width: 0px; //滚动条宽度
  387. }
  388. ::-webkit-scrollbar-thumb {
  389. //上层
  390. border-radius: 10px; //滚动条圆弧半径
  391. //-webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2); //滚动条阴影
  392. background: var(--el-border-color-dark); //背景颜色
  393. }
  394. .lh14{
  395. line-height: 14px;
  396. }
  397. .lh16{
  398. line-height: 16px;
  399. }
  400. }
  401. </style>