approvedContent.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. <template>
  2. <div class="flow-detail-content">
  3. <div class="flow-detail-container">
  4. <!-- 值为空 -->
  5. <div v-if="!currentTaskRow.instanceId" class="flow-empty-detail-box">
  6. <el-empty description="暂无数据" />
  7. </div>
  8. <!-- 值不为空 -->
  9. <template v-if="currentTaskRow.instanceId">
  10. <!-- 1、头部信息 -->
  11. <div class="flow-status-stamp">
  12. <div class="flow-stamp-container">
  13. <FlowStatusStamp :status="0" />
  14. </div>
  15. </div>
  16. <div class="flow-header-box">
  17. <div class="flow-no">编号:{{ currentTaskRow.taskId }}</div>
  18. <div class="action-area">
  19. <div class="action-item"></div>
  20. </div>
  21. </div>
  22. <!-- 2、内容体 -->
  23. <div class="flow-detail-box">
  24. <!--头部-->
  25. <div class="header-box">
  26. <div class="summary-info">
  27. <div class="title">{{ currentTaskRow.processName }}</div>
  28. <FlowStatusTag :status="0" />
  29. </div>
  30. <div class="initiator-info">
  31. <FlowNodeAvatar id="1" />
  32. <div class="begin-time">{{ currentTaskRow.createTime }} 提交</div>
  33. </div>
  34. </div>
  35. <div class="area-divider"></div>
  36. <div class="scroll-wrap">
  37. <!-- 表单 -->
  38. <div v-loading="validateForm.loading" class="form-wrap">
  39. <FormCreate v-show="validateForm.rule.length" v-model:api="validateForm.api" :option="validateForm.option" :rule="validateForm.rule" />
  40. <LeNoData v-if="!validateForm.rule.length" message="表单无数据" />
  41. </div>
  42. <div class="area-divider"></div>
  43. <!--审批流-->
  44. <el-timeline style="margin-left: 50px">
  45. <el-timeline-item v-for="active in activeData" :key="active.id" hollow :timestamp="active.taskName" placement="top">
  46. <template #dot>
  47. <FlowTypeDot :status="active.taskState" :type="active.taskType" />
  48. </template>
  49. <div class="timeline-box flex-1">
  50. <div class="flex flex-align-center">
  51. <div class="timeline-box-user flex-1">
  52. <FlowNodeAvatar id="1" />
  53. <div class="comment">
  54. <div class="comment-content">{{ active.duration }}</div>
  55. </div>
  56. </div>
  57. <span class="timeline-box-date">{{ formatTimestamp(active.finishTime) }}</span>
  58. </div>
  59. </div>
  60. </el-timeline-item>
  61. </el-timeline>
  62. </div>
  63. </div>
  64. <!-- 3、底部操作按钮 -->
  65. <!--
  66. 1、已审批的任务不显示操作按钮
  67. 2、我的申请显示撤回按钮
  68. 3、认领任务显示认领按钮
  69. 4、我收到的任务显示评论
  70. -->
  71. <div class="flow-actions">
  72. <el-button :icon="ChatLineSquare" @click="openComment('reviewVisible', 'review')">评论</el-button>
  73. <template v-if="currentTaskType !== 'myReceived' || currentTaskType !== 'approved'">
  74. <el-button
  75. v-if="currentTaskType === 'pendingApproval'"
  76. :icon="Check"
  77. type="primary"
  78. @click="openComment('consentOrRefuseVisible', 'agree')"
  79. >同意</el-button
  80. >
  81. <el-button
  82. v-if="currentTaskType === 'pendingApproval'"
  83. :icon="Close"
  84. type="danger"
  85. @click="openComment('consentOrRefuseVisible', 'reject')"
  86. >拒绝</el-button
  87. >
  88. <el-button v-if="currentTaskType === 'myApplication'" :icon="Close" @click="openComment('reviewVisible', 'revoke')">撤回</el-button>
  89. <el-button v-if="currentTaskType === 'pendingClaim'" :icon="Close" @click="claimTaskEv">认领</el-button>
  90. <el-dropdown v-if="currentTaskType === 'pendingApproval'" style="margin-left: 12px">
  91. <el-button :icon="More">更多</el-button>
  92. <template #dropdown>
  93. <el-dropdown-menu>
  94. <el-dropdown-item @click.native="openComment('deliverToReviewVisible')">
  95. <el-icon><DArrowLeft /></el-icon>
  96. 转交
  97. </el-dropdown-item>
  98. <el-dropdown-item @click.native="openComment('rollbackVisible')">
  99. <el-icon><Switch /></el-icon>
  100. 回退
  101. </el-dropdown-item>
  102. <el-dropdown-item @click.native="openComment('addSignVisible')">
  103. <el-icon><Plus /></el-icon>
  104. 加签
  105. </el-dropdown-item>
  106. <el-dropdown-item @click.native="openComment('loseSignVisible')">
  107. <el-icon><Minus /></el-icon>
  108. 减签
  109. </el-dropdown-item>
  110. </el-dropdown-menu>
  111. </template>
  112. </el-dropdown>
  113. </template>
  114. </div>
  115. </template>
  116. </div>
  117. <!-- 评论弹窗-->
  118. <review-dialog
  119. v-if="reviewVisible"
  120. v-model="reviewVisible"
  121. :instance-id="currentTaskRow.instanceId"
  122. :task-id="taskId"
  123. :current-type="currentType"
  124. @success-cb="closeDetailEv"
  125. ></review-dialog>
  126. <!-- 加签弹窗 -->
  127. <add-sign-dialog v-if="addSignVisible" v-model="addSignVisible" :task-id="taskId" @success-cb="closeDetailEv"></add-sign-dialog>
  128. <!-- 同意或拒绝弹窗 -->
  129. <consent-or-refuse-dialog
  130. v-if="consentOrRefuseVisible"
  131. v-model="consentOrRefuseVisible"
  132. :task-id="taskId"
  133. :current-type="currentType"
  134. :form-data="currentFormData"
  135. @success-cb="closeDetailEv"
  136. ></consent-or-refuse-dialog>
  137. <!-- 转交审批弹窗 -->
  138. <deliver-to-review-dialog
  139. v-if="deliverToReviewVisible"
  140. v-model="deliverToReviewVisible"
  141. :task-id="taskId"
  142. @success-cb="closeDetailEv"
  143. ></deliver-to-review-dialog>
  144. <!-- 减签弹窗 -->
  145. <lose-sign-dialog v-if="loseSignVisible" v-model="loseSignVisible" :task-id="taskId" @success-cb="closeDetailEv"></lose-sign-dialog>
  146. <!-- 回退弹窗 -->
  147. <rollback-dialog v-if="rollbackVisible" v-model="rollbackVisible" :task-id="taskId" @success-cb="closeDetailEv"></rollback-dialog>
  148. </div>
  149. </template>
  150. <script setup>
  151. import useTaskProcessStore from '@/store/modules/taskProcess'
  152. import { computed, ref, onMounted, nextTick, watch } from 'vue'
  153. import FlowStatusStamp from '@/components/Flow/FlowStatusStamp.vue'
  154. import FlowStatusTag from '@/components/Flow/FlowStatusTag.vue'
  155. import FlowNodeAvatar from '@/components/Flow/FlowNodeAvatar.vue'
  156. import FlowTypeDot from '@/components/Flow/FlowTypeDot.vue'
  157. import { ChatLineSquare, Check, Close, Switch, DArrowLeft, Plus, Minus, More } from '@element-plus/icons-vue'
  158. import { processTaskApprovalInfo, processClaimTaskApi } from '@/api/flow/processTask'
  159. import { formatTimestamp } from '@/utils/datetime'
  160. import ReviewDialog from './reviewDialog'
  161. import AddSignDialog from './addSignDialog'
  162. import ConsentOrRefuseDialog from './consentOrRefuseDialog'
  163. import DeliverToReviewDialog from './deliverToReviewDialog'
  164. import LoseSignDialog from './loseSignDialog'
  165. import RollbackDialog from './rollbackDialog'
  166. import viewForm from '@/utils/form'
  167. import { storeToRefs } from 'pinia'
  168. const props = defineProps({
  169. /**
  170. * pendingApproval 待审批
  171. * myApplication 我的申请
  172. * myReceived 我收到的
  173. * pendingClaim 认领任务
  174. * approved 已审批
  175. */
  176. currentTaskType: {
  177. type: String,
  178. default: ''
  179. }
  180. })
  181. // store值
  182. const taskProcessInfo = useTaskProcessStore()
  183. const { currentTaskRow } = storeToRefs(taskProcessInfo)
  184. // 各种操作弹窗显示隐藏 start
  185. const reviewVisible = ref(false)
  186. const addSignVisible = ref(false)
  187. const consentOrRefuseVisible = ref(false)
  188. const deliverToReviewVisible = ref(false)
  189. const loseSignVisible = ref(false)
  190. const rollbackVisible = ref(false)
  191. // 各种操作弹窗显示隐藏 end
  192. const activeData = ref([])
  193. const currentType = ref(null)
  194. const currentFormData = ref({})
  195. // 当前form 表单数据字符串
  196. let cur_processForm_str = '[]'
  197. const taskId = computed(() => {
  198. return currentTaskRow.value.taskId || ''
  199. })
  200. const FormCreate = viewForm.$form()
  201. const validateForm = ref({
  202. api: {},
  203. option: {
  204. submitBtn: false
  205. },
  206. rule: [],
  207. loading: false
  208. })
  209. /**
  210. * 拿到当前关闭详情弹窗
  211. */
  212. const closeDetailEv = () => {
  213. // 存储表单 todo.....
  214. // 如果这里有表单,是否要把所有表单的内容进行存储,存储完毕后,才能关闭这个详情,刷新左侧的列表 todo
  215. taskProcessInfo.refresh = true
  216. taskProcessInfo.setCurrentTaskRow({})
  217. }
  218. /**
  219. * 详情按钮各个操作弹窗
  220. * @param visibleType 评论 拒绝 同意等
  221. */
  222. const openComment = async (visibleType, item) => {
  223. switch (visibleType) {
  224. case 'reviewVisible':
  225. currentType.value = item
  226. reviewVisible.value = !reviewVisible.value
  227. break
  228. case 'addSignVisible':
  229. addSignVisible.value = !addSignVisible.value
  230. break
  231. case 'consentOrRefuseVisible':
  232. currentFormData.value = {}
  233. // 点击同意
  234. let bool = true
  235. if (item === 'agree') {
  236. const api = validateForm.value.api
  237. bool = await api.validate((valid, fail) => {
  238. if (valid) {
  239. // 表单验证通过
  240. const values = api.formData()
  241. const processForm = JSON.parse(cur_processForm_str)
  242. processForm.forEach(v => {
  243. // 填写的数据存储(local_: 本地数据处理标识)
  244. v.local_value = values[v.field]
  245. })
  246. console.warn(processForm, 'processForm')
  247. // 流程表单JSON内容 & local_value 保存
  248. currentFormData.value = { processForm: JSON.stringify(processForm) }
  249. }
  250. })
  251. }
  252. if (!bool) return
  253. currentType.value = item
  254. consentOrRefuseVisible.value = !consentOrRefuseVisible.value
  255. break
  256. case 'deliverToReviewVisible':
  257. deliverToReviewVisible.value = !deliverToReviewVisible.value
  258. break
  259. case 'loseSignVisible':
  260. loseSignVisible.value = !loseSignVisible.value
  261. break
  262. case 'rollbackVisible':
  263. rollbackVisible.value = !rollbackVisible.value
  264. break
  265. }
  266. }
  267. /**
  268. * 获取taskId对应的详情
  269. */
  270. const getTaskDetail = () => {
  271. const cur = currentTaskRow.value || {}
  272. // 提交的表单 数据展示
  273. validateForm.value.loading = true
  274. processTaskApprovalInfo({
  275. taskId: cur.taskId,
  276. instanceId: cur.instanceId
  277. })
  278. .then(data => {
  279. activeData.value = data
  280. console.log(data, 'data.......')
  281. // validateForm.value.origin = data
  282. try {
  283. /*descItemsData.value.list = JSON.parse(data.formContent).map(item => {
  284. const showLabel = item.title
  285. let showValue = item.local_value
  286. const options = item.options
  287. if (Array.isArray(options) && showValue !== undefined) {
  288. if (Array.isArray(showValue)) {
  289. showValue = showValue.reduce(val => {
  290. const cur = options.find(option => option.value === val)
  291. return cur?.label || val
  292. }, [])
  293. } else {
  294. const cur = options.find(option => option.value === showValue)
  295. showValue = cur?.label || showValue
  296. }
  297. }
  298. return {
  299. showLabel,
  300. showValue
  301. }
  302. })*/
  303. const forms = JSON.parse(data.formContent)
  304. cur_processForm_str = data.formContent
  305. if (Array.isArray(forms)) {
  306. validateForm.value.rule = forms
  307. const api = validateForm.value.api
  308. api.setValue(
  309. forms.reduce((obj, item) => {
  310. obj[item.field] = item.local_value
  311. return obj
  312. }, {})
  313. )
  314. api.nextTick(() => {
  315. // 是否有编辑权限 操作
  316. api.disabled(false /*true*/)
  317. })
  318. }
  319. } catch (e) {
  320. console.error('解析 descItems 数据出现问题', e)
  321. // descItemsData.value.list = []
  322. validateForm.value.rule = []
  323. }
  324. })
  325. .finally(() => {
  326. validateForm.value.loading = false
  327. })
  328. }
  329. /** 认领任务 **/
  330. const claimTaskEv = async () => {
  331. const res = await processClaimTaskApi(currentTaskRow.value.taskId)
  332. console.log(res, 'res====')
  333. }
  334. /**
  335. * 监听同级子组件的instanceId的值变化 这里可能也有实例Id
  336. * 1、监听instanceId的值变化,如果值有变化,则重新获取审批详情
  337. * 2、如果instanceId没有值,则不请求接口,暂时暂无数据img
  338. */
  339. watch(
  340. () => currentTaskRow.value.instanceId,
  341. (nValue, oValue) => {
  342. if (!nValue) return
  343. nextTick(() => {
  344. getTaskDetail()
  345. })
  346. }
  347. )
  348. </script>
  349. <style scoped lang="scss">
  350. .flow-detail-content {
  351. height: 100%;
  352. overflow: hidden;
  353. background: #fff;
  354. border-radius: 6px;
  355. flex: auto;
  356. .flow-detail-container {
  357. display: flex;
  358. flex-direction: column;
  359. position: relative;
  360. height: 100%;
  361. overflow: hidden;
  362. }
  363. // 通过、不通过样式
  364. .flow-status-stamp {
  365. position: absolute;
  366. right: 10px;
  367. top: 10px;
  368. z-index: 999;
  369. }
  370. // 头部
  371. .flow-header-box {
  372. font-weight: 400;
  373. font-size: 13px;
  374. border-bottom: 1px solid #e5e6ec;
  375. padding: 0 20px;
  376. height: 39px;
  377. display: flex;
  378. align-items: center;
  379. justify-content: space-between;
  380. color: #86909c;
  381. .action-area {
  382. display: flex;
  383. gap: 4px;
  384. .action-item {
  385. cursor: pointer;
  386. padding: 4px;
  387. border-radius: 6px;
  388. width: fit-content;
  389. height: fit-content;
  390. }
  391. }
  392. }
  393. // 内容体
  394. .flow-detail-box {
  395. //height: calc(100% - 92px);
  396. //overflow: hidden;
  397. //overflow-y: auto;
  398. flex: 1;
  399. min-height: 0;
  400. padding: 0 20px;
  401. display: flex;
  402. flex-direction: column;
  403. height: 100%;
  404. .header-box {
  405. display: flex;
  406. flex-direction: column;
  407. justify-content: center;
  408. // padding-top: 20px;
  409. .summary-info {
  410. display: flex;
  411. align-items: center;
  412. padding-top: 10px;
  413. font-size: 24px;
  414. .title {
  415. font-family: PingFangSC-Semibold, PingFang SC;
  416. color: #1d2129;
  417. }
  418. }
  419. .initiator-info {
  420. display: flex;
  421. align-items: center;
  422. margin-top: 16px;
  423. .begin-time {
  424. margin-left: 16px;
  425. font-weight: 350;
  426. color: #86909c;
  427. font-size: 13px;
  428. -webkit-user-select: none;
  429. user-select: none;
  430. }
  431. }
  432. }
  433. .area-divider {
  434. border-bottom: 1px solid rgba(229, 230, 235, 1);
  435. margin: 20px 0;
  436. //position: relative;
  437. }
  438. .scroll-wrap {
  439. overflow: hidden;
  440. overflow-y: auto;
  441. flex: 1;
  442. }
  443. }
  444. // 底部
  445. .flow-actions {
  446. display: flex;
  447. align-items: center;
  448. justify-content: flex-end;
  449. height: 52px;
  450. //border-top: 1px solid var(--color-neutral-3);
  451. border-top: 1px solid #e5e6ec;
  452. padding: 0 20px;
  453. }
  454. }
  455. </style>