index.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. <!-- 流程详情 -->
  2. <template>
  3. <div class="flow-detail-content">
  4. <div v-loading="validateForm.loading" class="flow-detail-container">
  5. <!-- 值为空 -->
  6. <div v-if="!currentTaskRow.instanceId" class="flow-empty-detail-box">
  7. <el-empty description="暂无数据" />
  8. </div>
  9. <!-- 值不为空 -->
  10. <div class="column-page-wrap" v-show="currentTaskRow.instanceId">
  11. <!-- 1、头部信息 -->
  12. <div class="flow-status-stamp">
  13. <div class="flow-stamp-container">
  14. <FlowStatusStamp :status="currentTaskRow.instanceState" />
  15. </div>
  16. </div>
  17. <div class="flow-header-box">
  18. <div class="flow-no">编号:{{ currentTaskRow.instanceId }}</div>
  19. <div class="action-area">
  20. <div class="action-item" @click="openPrintModal">
  21. <el-tooltip effect="dark" content="打印" placement="bottom">
  22. <el-icon :style="{ color: 'var(--el-color-primary)' }" :size="16"><Printer /> </el-icon>
  23. </el-tooltip>
  24. </div>
  25. </div>
  26. </div>
  27. <!-- 2、内容体 -->
  28. <div class="flow-detail-box">
  29. <!--头部-->
  30. <div class="header-box">
  31. <div class="summary-info">
  32. <div class="title">{{ currentTaskRow.modelContent?.name }}</div>
  33. <FlowStatusTag :status="currentTaskRow.instanceState" />
  34. </div>
  35. <div class="initiator-info">
  36. <FlowNodeAvatar :name="currentTaskRow.createBy" />
  37. <div class="begin-time">{{ formatTimestamp(currentTaskRow.createTime) }} 提交</div>
  38. </div>
  39. </div>
  40. <el-tabs v-model="tabName">
  41. <el-tab-pane v-for="v of tabList" :key="v.value" :label="v.label" :name="v.value" />
  42. </el-tabs>
  43. <!-- 审批信息 -->
  44. <div v-show="tabName === 'ApprovalInfo'" class="scroll-wrap">
  45. <!-- 系统表单-->
  46. <template v-if="currentTaskRow.processType === 'business' && currentObj?.formTemplate.type === 1">
  47. <component :is="dyVueComponent" ref="dyVueComponentRef" @form-valid="openComment('consentOrRefuseVisible', 'agree')"></component>
  48. </template>
  49. <!-- 表单设计 -->
  50. <template v-else>
  51. <div class="form-wrap self-Everright-formEditor">
  52. <er-form-preview
  53. v-show="validateForm.rule.length"
  54. ref="EReditorRef"
  55. :file-upload-u-r-i="uploadFileApi"
  56. :is-show-complete-button="false"
  57. />
  58. <LeNoData v-if="!validateForm.rule.length" message="表单无数据" />
  59. </div>
  60. </template>
  61. <div class="area-divider"></div>
  62. <!--审批流-->
  63. <el-timeline style="margin-left: 50px">
  64. <el-timeline-item v-for="active in activeData" :key="active.id" hollow :timestamp="active.local_timestamp">
  65. <template #dot>
  66. <FlowTypeDot :status="active.id ? 0 : 1" :type="active.type" :name="active.createBy" />
  67. </template>
  68. <div v-show="active.type === 0" class="timeline-box flex-1">
  69. <span style="color: #86909c; display: block; margin-bottom: 3px; padding-left: 4px">评论</span>
  70. <div class="flex flex-align-center">
  71. <div class="timeline-box-user flex-1">
  72. <span style="padding-left: 4px">{{ active.createBy }}</span>
  73. <div v-if="active.local_content" class="comment">
  74. <div class="comment-content">{{ active.local_content }}</div>
  75. </div>
  76. </div>
  77. <span class="timeline-box-date">{{ formatTimestamp(active.finishTime) }}</span>
  78. </div>
  79. </div>
  80. <div v-show="active.type !== 0" class="timeline-box flex-1">
  81. <span style="display: block" class="pl-1.5 mb-0">{{ active.taskName }}</span>
  82. <div class="flex flex-align-center">
  83. <div class="timeline-box-user flex-1">
  84. <span v-if="active.id" class="text-gray-500 pl-1.5">
  85. {{ active.createBy }}
  86. </span>
  87. <span v-if="active.type === 22">
  88. 发起的子流程<el-tooltip content="点击查看详情">
  89. <a class="el-button el-button--primary is-link" style="padding-top: 0" @click="lookSubProcess(active)">
  90. ({{ active.content.callProcess.split(':')[1] }})
  91. </a>
  92. </el-tooltip>
  93. </span>
  94. <div class="mt-[3px] flex flex-wrap gap-[6px]">
  95. <FlowNodeAvatar v-for="nodeUser in active.local_nodeUserList" :key="nodeUser.id" :name="nodeUser.name" />
  96. <FlowNodeAvatar v-for="nodeRole in active.local_nodeRoleList" :key="nodeRole.id" :name="nodeRole.name">
  97. <template #avatar>
  98. <svg-icon icon-class="flow-group" color="#fff" />
  99. </template>
  100. </FlowNodeAvatar>
  101. </div>
  102. <div v-if="active.local_content" class="comment">
  103. <div class="comment-content">{{ active.local_content }}</div>
  104. </div>
  105. </div>
  106. <span class="timeline-box-date">{{ formatTimestamp(active.finishTime) }}</span>
  107. </div>
  108. </div>
  109. </el-timeline-item>
  110. </el-timeline>
  111. </div>
  112. <!-- 流程图 -->
  113. <div v-show="tabName === 'FlowChart'" class="scroll-wrap">
  114. <div class="tags-desc">
  115. <!--0: 已执行 1正在执行-->
  116. <el-tag style="margin-left: 4px" type="success" size="small">已执行</el-tag>
  117. <el-tag style="margin-left: 4px" type="warning" size="small">执行中</el-tag>
  118. <el-tag style="margin-left: 4px" type="info" size="small">未执行</el-tag>
  119. </div>
  120. <ScWorkflow style="overflow-x: auto" :model-value="modelContentConfig" disabled />
  121. </div>
  122. </div>
  123. </div>
  124. </div>
  125. <!-- 打印审批流 -->
  126. <printer-dialog v-if="printerVisible" v-model="printerVisible" :currentTaskRow="currentTaskRow" :opts="printOpts"></printer-dialog>
  127. </div>
  128. </template>
  129. <script setup>
  130. import path from 'path'
  131. import useTaskProcessStore from '@/store/modules/taskProcess'
  132. import { computed, ref, nextTick, watch, markRaw, defineAsyncComponent, provide, onMounted } from 'vue'
  133. import FlowStatusStamp from '@/components/Flow/FlowStatusStamp.vue'
  134. import FlowStatusTag from '@/components/Flow/FlowStatusTag.vue'
  135. import FlowNodeAvatar from '@/components/Flow/FlowNodeAvatar.vue'
  136. import FlowTypeDot from '@/components/Flow/FlowTypeDot.vue'
  137. import { ChatLineSquare, Check, Close, Switch, DArrowLeft, Plus, Minus, More, Printer } from '@element-plus/icons-vue'
  138. import { processTaskApprovalInfo, processClaimTaskApi } from '@/api/flow/processTask'
  139. import { formatTimestamp } from '@/utils/datetime'
  140. // import ReviewDialog from './reviewDialog'
  141. // import AddSignDialog from './addSignDialog'
  142. // import ConsentOrRefuseDialog from './consentOrRefuseDialog'
  143. // import DeliverToReviewDialog from './deliverToReviewDialog'
  144. // import LoseSignDialog from './loseSignDialog'
  145. // import RollbackDialog from './rollbackDialog'
  146. // import ViewProcessDialog from './ViewProcessDialog.vue'
  147. import PrinterDialog from '@/views/approve/components/printer.vue'
  148. // import PrinterDialog from './printer.vue'
  149. import { storeToRefs } from 'pinia'
  150. import { ElMessage, ElMessageBox } from 'element-plus'
  151. import { erFormPreview } from '@ER/formEditor'
  152. const modules = import.meta.glob('@/views/**/*.vue')
  153. const { VITE_APP_BASE_API } = import.meta.env
  154. const uploadFileApi = ref(`${VITE_APP_BASE_API}/v1/oss/upload`)
  155. import { package_modelContentConfig } from '../components/config.ts'
  156. import { useRoute } from 'vue-router'
  157. const route = useRoute()
  158. const props = defineProps({
  159. /**
  160. * pendingApproval 待审批
  161. * myApplication 我的申请
  162. * myReceived 我收到的
  163. * pendingClaim 认领任务
  164. * approved 已审批
  165. */
  166. currentTaskType: {
  167. type: String,
  168. default: ''
  169. }
  170. })
  171. const tabName = ref('ApprovalInfo')
  172. const tabList = computed(() => {
  173. const list = [
  174. {
  175. label: '审批信息',
  176. value: 'ApprovalInfo'
  177. },
  178. {
  179. label: '流程图',
  180. value: 'FlowChart'
  181. }
  182. ]
  183. /*// 待审批 去掉流程图 todo?
  184. if (props.currentTaskType === 'pendingApproval') {
  185. list.splice(1, 1)
  186. }*/
  187. return list
  188. })
  189. const currentTaskRow = ref({})
  190. // store值
  191. // const taskProcessInfo = useTaskProcessStore()
  192. // const { currentTaskRow } = storeToRefs(taskProcessInfo)
  193. // 各种操作弹窗显示隐藏 start
  194. const reviewVisible = ref(false)
  195. const addSignVisible = ref(false)
  196. const consentOrRefuseVisible = ref(false)
  197. const deliverToReviewVisible = ref(false)
  198. const loseSignVisible = ref(false)
  199. const rollbackVisible = ref(false)
  200. const printerVisible = ref(false)
  201. const printOpts = ref({})
  202. const allowReject = ref(false)
  203. const allowRevocation = ref(false)
  204. // 各种操作弹窗显示隐藏 end
  205. const activeData = ref([])
  206. const currentType = ref(null)
  207. const currentFormData = ref({})
  208. // 当前form 表单数据字符串
  209. let cur_processForm_str = ''
  210. let cur_formStructure = {}
  211. let cur_formData = {}
  212. const openPrintModal = () => {
  213. // innerTable.value = document.querySelector('.self-Everright-formEditor').innerHTML
  214. printOpts.value = {
  215. formStructure: cur_formStructure,
  216. formData: cur_formData
  217. }
  218. printerVisible.value = true
  219. }
  220. const EReditorRef = ref()
  221. const taskId = computed(() => {
  222. return currentTaskRow.value.taskId || ''
  223. })
  224. const validateForm = ref({
  225. rule: [],
  226. loading: false
  227. })
  228. // 允许转交
  229. const allowTransfer = ref(true)
  230. // 允许加减签
  231. const allowAppendNode = ref(true)
  232. // 允许回退
  233. const allowRollback = ref(true)
  234. const modelContentConfig = ref({})
  235. // 系统表单
  236. const dyVueComponent = ref(undefined)
  237. const dyVueComponentRef = ref()
  238. const dyVueForm = ref({})
  239. const currentObj = ref()
  240. provide('dyVueForm', dyVueForm) // 这里主要是存放动态的form的属性值
  241. /**
  242. * 详情按钮各个操作弹窗
  243. * @param visibleType 评论 拒绝 同意等
  244. */
  245. const openComment = async (visibleType, item) => {
  246. switch (visibleType) {
  247. case 'reviewVisible':
  248. currentType.value = item
  249. reviewVisible.value = !reviewVisible.value
  250. break
  251. case 'addSignVisible':
  252. addSignVisible.value = !addSignVisible.value
  253. break
  254. case 'consentOrRefuseVisible':
  255. currentFormData.value = {}
  256. if (item === 'agree') {
  257. const { processType } = currentTaskRow.value
  258. const flag = processType === 'business' && currentObj.value.formTemplate.type === 1 // 系统表单
  259. if (flag) {
  260. const formData = dyVueComponentRef.value.getComponentData()
  261. const saveData = {
  262. // 这里要调整真实的URL
  263. // formStructure: currentObj.value.formTemplate.pcUrl,
  264. formStructure: '@/views/flow/test/test1.vue',
  265. formData
  266. }
  267. currentFormData.value = { processForm: JSON.stringify(saveData) }
  268. } else {
  269. const formData = EReditorRef.value.getData()
  270. let processForm = JSON.parse(cur_processForm_str)
  271. processForm = { ...processForm, formData }
  272. currentFormData.value = { processForm: JSON.stringify(processForm) }
  273. }
  274. }
  275. currentType.value = item
  276. consentOrRefuseVisible.value = !consentOrRefuseVisible.value
  277. break
  278. case 'deliverToReviewVisible':
  279. deliverToReviewVisible.value = !deliverToReviewVisible.value
  280. break
  281. case 'loseSignVisible':
  282. loseSignVisible.value = !loseSignVisible.value
  283. break
  284. case 'rollbackVisible':
  285. rollbackVisible.value = !rollbackVisible.value
  286. break
  287. }
  288. }
  289. /**
  290. * 获取taskId对应的详情
  291. * @param taskId
  292. * 1、获取流程数据
  293. * 2、获取表单数据
  294. * 2.1 主流程、业务流程(type=0 表单设计)、子流程 使用EverightForm表单来渲染表单值
  295. * 2.2 业务流程(type=1 使用系统表单来渲染表单值)
  296. */
  297. const getTaskDetail = () => {
  298. // const cur = currentTaskRow.value || {}
  299. const { id } = route.query
  300. validateForm.value.loading = true
  301. processTaskApprovalInfo({
  302. // taskId: cur.taskId,
  303. // instanceId: cur.instanceId,
  304. // instanceState: cur.instanceState
  305. instanceId: id,
  306. })
  307. .then(data => {
  308. const activeList = data.processApprovals
  309. activeList.forEach(v => {
  310. v.local_timestamp = v.id && formatTimestamp(v.createTime)
  311. const _content = v.content
  312. v.local_nodeUserList = _content?.nodeUserList || []
  313. v.local_nodeRoleList = _content?.nodeRoleList || []
  314. v.local_content = _content?.opinion
  315. })
  316. activeData.value = activeList
  317. try {
  318. const modelContent = JSON.parse(data.modelContent || '{}')
  319. data.modelContent = modelContent
  320. const modelContent_config = modelContent.nodeConfig // ?? modelContent.childNode
  321. if (modelContent_config) {
  322. package_modelContentConfig(modelContent_config, data.renderNodes || {})
  323. }
  324. modelContentConfig.value = modelContent_config || {}
  325. window.modelContentConfig = modelContentConfig
  326. /* 允许转交 允许加减签 允许回退 start */
  327. if (props.currentTaskType === 'pendingApproval') {
  328. allowTransfer.value = data?.allowTransfer
  329. allowAppendNode.value = data?.allowAppendNode
  330. allowRollback.value = data?.allowRollback
  331. }
  332. if (data?.taskType !== 0) {
  333. allowReject.value = true
  334. }
  335. if (props.currentTaskType === 'myApplication') {
  336. allowRevocation.value = data.processSetting.allowRevocation
  337. }
  338. /* 允许转交 允许加减签 允许回退 end */
  339. /* 这里主要是表单设计的逻辑 start */
  340. let formContent = null
  341. const { processType } = currentTaskRow.value
  342. if (processType === 'business') {
  343. currentObj.value = data
  344. if (data?.formTemplate.type === 1) {
  345. dyVueForm.value = {
  346. dy: {
  347. name: '测试名字',
  348. region: 1,
  349. delivery: true,
  350. type: ['1'],
  351. resource: 'Sponsor',
  352. desc: '啦啦啦'
  353. }
  354. }
  355. // dyVueComponent.value = markRaw(defineAsyncComponent(() => import('@/views/flow/test/test1.vue')))
  356. if (data.formTemplate.pcUrl && modules[data.formTemplate.pcUrl]) {
  357. dyVueComponent.value = defineAsyncComponent(async () => await /* @vite-ignore */ modules[data.formTemplate.pcUrl]())
  358. }
  359. return
  360. } else {
  361. formContent = `{"formStructure":${data.formTemplate.content}}` || '{}'
  362. }
  363. } else {
  364. formContent = data.formContent
  365. }
  366. cur_processForm_str = formContent
  367. const { formStructure, formData } = JSON.parse(cur_processForm_str)
  368. const formConfig = data.formConfig || []
  369. cur_formStructure = formStructure // 存储一份
  370. let newFields = cur_formStructure.fields
  371. let newFieldsList = cur_formStructure.list
  372. // pendingApproval, 除了[待审批]这个状态, 其余只能读取
  373. // if (props.currentTaskType !== 'pendingApproval') {
  374. newFields.map(item1 => (item1.options.disabled = true))
  375. // }
  376. /* else {
  377. // data.formConfig
  378. newFields.forEach(item => {
  379. const matchingFormItem = formConfig.find(formItem => formItem.id == item.id)
  380. // 如果找到匹配的对象,且opera为'2',则删除newFields中的对象
  381. if (matchingFormItem && matchingFormItem.opera === '2') {
  382. newFields = newFields.filter(t => t.id !== item.id)
  383. } else if (matchingFormItem) {
  384. // 否则,将opera属性添加到newFields中的对象
  385. item.opera = matchingFormItem.opera
  386. // '0': 只读 '1': 编辑 '2': 隐藏
  387. item.options.disabled = ['0', '1'].indexOf(matchingFormItem.opera) > -1 ? !Number(matchingFormItem.opera) : false
  388. }
  389. })
  390. newFieldsList.forEach(item => {
  391. const matchingFormItem = formConfig.find(formItem => formItem.id == item.id)
  392. // 如果找到匹配的对象,且opera为'2',则删除newFields中的对象
  393. if (matchingFormItem && matchingFormItem.opera === '2') {
  394. newFieldsList = newFieldsList.filter(t => t.id !== item.id)
  395. }
  396. })
  397. }*/
  398. cur_formStructure = {
  399. ...cur_formStructure,
  400. fields: newFields,
  401. list: newFieldsList
  402. }
  403. cur_formData = formData
  404. console.log(cur_formStructure, '=/////====newFormStructure')
  405. console.log(formData, '=/////====newFormStructure')
  406. EReditorRef.value.setData(cur_formStructure, formData)
  407. validateForm.value.rule = newFields
  408. /* 这里主要是表单设计的逻辑 end */
  409. } catch (e) {
  410. console.error('解析 descItems 数据出现问题', e)
  411. // descItemsData.value.list = []
  412. validateForm.value.rule = []
  413. }
  414. currentTaskRow.value = data
  415. })
  416. .finally(() => {
  417. validateForm.value.loading = false
  418. })
  419. }
  420. onMounted(getTaskDetail)
  421. const processInfo = ref({ name: '', id: '', instanceId: '', visible: false })
  422. const lookSubProcess = item => {
  423. const [id, name, instanceId] = item.content.callProcess.split(':')
  424. processInfo.value = {
  425. name,
  426. id,
  427. instanceId,
  428. visible: true
  429. }
  430. }
  431. </script>
  432. <style scoped lang="scss">
  433. .column-page-wrap {
  434. background: transparent;
  435. }
  436. .flow-detail-content {
  437. height: 100%;
  438. overflow: hidden;
  439. background: var(--el-bg-color);
  440. border-radius: 6px;
  441. flex: 1;
  442. .flow-detail-container {
  443. display: flex;
  444. flex-direction: column;
  445. position: relative;
  446. z-index: 0;
  447. height: 100%;
  448. overflow: hidden;
  449. }
  450. // 通过、不通过样式
  451. .flow-status-stamp {
  452. position: absolute;
  453. right: 60px;
  454. top: 10px;
  455. z-index: 99;
  456. }
  457. // 头部
  458. .flow-header-box {
  459. font-weight: 400;
  460. font-size: 13px;
  461. border-bottom: 1px solid var(--el-border-color);
  462. padding: 0 20px;
  463. height: 39px;
  464. display: flex;
  465. align-items: center;
  466. justify-content: space-between;
  467. color: var(--el-color-info);
  468. .action-area {
  469. display: flex;
  470. gap: 4px;
  471. .action-item {
  472. cursor: pointer;
  473. display: flex;
  474. align-items: center;
  475. }
  476. }
  477. }
  478. // 内容体
  479. .flow-detail-box {
  480. //height: calc(100% - 92px);
  481. //overflow: hidden;
  482. //overflow-y: auto;
  483. flex: 1;
  484. min-height: 0;
  485. padding: 0 20px;
  486. display: flex;
  487. flex-direction: column;
  488. height: 100%;
  489. .header-box {
  490. display: flex;
  491. flex-direction: column;
  492. justify-content: center;
  493. padding-bottom: 10px;
  494. // padding-top: 20px;
  495. .summary-info {
  496. display: flex;
  497. align-items: center;
  498. padding-top: 10px;
  499. font-size: 24px;
  500. .title {
  501. font-family: PingFangSC-Semibold, PingFang SC;
  502. color: var(--el-text-color-primary);
  503. }
  504. }
  505. .initiator-info {
  506. display: flex;
  507. align-items: center;
  508. margin-top: 16px;
  509. .begin-time {
  510. margin-left: 16px;
  511. font-weight: 350;
  512. color: var(--el-text-color-placeholder);
  513. font-size: 13px;
  514. -webkit-user-select: none;
  515. user-select: none;
  516. }
  517. }
  518. }
  519. .area-divider {
  520. border-bottom: 1px solid var(--el-border-color);
  521. margin: 20px 0;
  522. //position: relative;
  523. }
  524. .scroll-wrap {
  525. position: relative;
  526. overflow: hidden;
  527. overflow-y: auto;
  528. flex: 1;
  529. .tags-desc {
  530. position: absolute;
  531. left: 6px;
  532. top: 10px;
  533. z-index: 1;
  534. }
  535. :deep(.zoom-scale) {
  536. position: absolute;
  537. top: 16px;
  538. right: 16px;
  539. }
  540. }
  541. }
  542. // 底部
  543. .flow-actions {
  544. display: flex;
  545. align-items: center;
  546. justify-content: flex-end;
  547. height: 52px;
  548. //border-top: 1px solid var(--color-neutral-3);
  549. border-top: 1px solid var(--el-border-color-light);
  550. background: var(--el-bg-color);
  551. padding: 0 20px;
  552. }
  553. }
  554. .comment-content {
  555. user-select: none;
  556. margin-top: 4px;
  557. padding: 8px;
  558. border-radius: 4px;
  559. background-color: var(--el-bg-color-page);
  560. }
  561. .timeline-box {
  562. margin-left: 5px;
  563. }
  564. :deep(.el-timeline-item__timestamp) {
  565. margin-left: 6px;
  566. }
  567. // 修改everright 表单的样式
  568. .self-Everright-formEditor {
  569. :deep(.Everright-formEditor-selectElement) {
  570. padding: 0px 16px;
  571. }
  572. }
  573. </style>