task-review.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. <template>
  2. <hc-new-dialog v-model="isShow" is-table widths="96%" title="任务审核" @close="cancelClick">
  3. <template #header="{ titleId, titleClass }">
  4. <div class="hc-card-header flex items-center">
  5. <div :id="titleId" :class="titleClass">任务审核 【已开启电签】</div>
  6. </div>
  7. </template>
  8. <div v-loading="isLoading" class="relative h-full">
  9. <div class="hc-task-name relative">{{ rowInfo.taskName }} 审批信息</div>
  10. <div class="hc-task-body relative flex">
  11. <div class="hc-task-time">
  12. <hc-body class="hc-task-body-card" padding="10px" scrollbar>
  13. <el-timeline v-if="rowInfo.fixedFlowId == null" class="hc-time-line">
  14. <template v-for="(item, index) in flowList" :key="index">
  15. <el-timeline-item :class="item.status === '2' ? 'success' : 'primary'" size="large">
  16. <div class="timeline-item-icon">
  17. <hc-icon v-if="item.status === '2'" class="check-icon" name="check" />
  18. </div>
  19. <div class="reply-name">{{ item.name }}</div>
  20. <div class="reply-time">{{ item.date }}</div>
  21. <div class="reply-content" v-html="item.flowValue" />
  22. </el-timeline-item>
  23. </template>
  24. </el-timeline>
  25. <el-timeline v-else class="hc-time-line">
  26. <template v-for="(item, index) in flowListTask" :key="index">
  27. <el-timeline-item :class="item.status == '2' ? 'success' : 'primary'" size="large">
  28. <div class="timeline-item-icon">
  29. <hc-icon v-if="item.status == '2'" class="check-icon" name="check" />
  30. </div>
  31. <div v-if="!item.isTask" class="reply-name">{{ item.name }}</div>
  32. <div v-if="item.isTask">
  33. <div class="reply-name">
  34. {{ item.name }}
  35. <hc-icon v-if="item.type == 2" name="links" class="ml-2" />
  36. <hc-icon v-if="item.type == 1" name="exchange-2" class="ml-2" />
  37. <br>
  38. <el-tooltip placement="right" effect="light" :visible="item.taskDetailvisible">
  39. <template #content>
  40. <el-timeline class="hc-time-line">
  41. <template v-for="(item1, index1) in item.userList" :key="index1">
  42. <el-timeline-item :class="item1.status === '2' ? 'success' : 'primary'" size="large">
  43. <div class="timeline-item-icon">
  44. <hc-icon v-if="item1.status === '2'" class="check-icon" name="check" />
  45. </div>
  46. <div class="reply-name">{{ item1.name }}</div>
  47. <div class="reply-time">{{ item1.date }}</div>
  48. <div class="reply-content" v-html="item1.flowValue" />
  49. </el-timeline-item>
  50. </template>
  51. </el-timeline>
  52. </template>
  53. <el-link @click="getTaskDetail" @mouseenter="item.taskDetailvisible = true" @mouseleave="item.taskDetailvisible = false">点击查看详情</el-link>
  54. </el-tooltip>
  55. </div>
  56. </div>
  57. <div class="reply-time">{{ item.date }}</div>
  58. <div class="reply-content" v-html="item.flowValue" />
  59. </el-timeline-item>
  60. </template>
  61. </el-timeline>
  62. </hc-body>
  63. </div>
  64. <div :id="`hc_task_table_${uuid}`" class="hc-task-table">
  65. <hc-body class="hc-task-body-card" padding="10px">
  66. <div class="hc-task-body-table">
  67. <hc-table
  68. ref="tableRef" :column="tableColumn" :datas="tableData" :is-stripe="false"
  69. is-new :index-style="{ width: 60 }" is-current-row @row-click="tableRowClick"
  70. >
  71. <template #action="{ row }">
  72. <div class="hc-task-table-action" :class="row.isComment === 1 ? 'is-cur' : ''" @click="rowRemarkClick(row)">
  73. <i class="i-iconoir-star-solid" />
  74. </div>
  75. </template>
  76. <template #state="{ row }">
  77. <div class="hc-task-table-state">
  78. <i v-if="row.status === 1" class="i-iconoir-check-circle-solid is-success" />
  79. <i v-else-if="row.status === 2" class="i-iconoir-xmark-circle-solid is-danger" />
  80. <span v-else-if="row.status === 3">审批结束</span>
  81. <i v-else class="i-iconoir-help-circle-solid" />
  82. </div>
  83. </template>
  84. </hc-table>
  85. </div>
  86. <div class="hc-task-body-tip hc-flex h-30px">
  87. <span class="mr-14px">上报总金额:{{ reportAllMoney }}元</span>
  88. <span>本期审核进度款:{{ progressMoney }}元</span>
  89. </div>
  90. </hc-body>
  91. </div>
  92. <div :id="`hc_task_form_${uuid}`" class="hc-task-form" :class="`is-tab-${taskTabsKey}`">
  93. <hc-body class="hc-task-body-card" padding="10px" scrollbar>
  94. <HcTaskForm :table="tableInfo" :info="rowInfo" :detail="detailInfo" :is-edit="tabsKey === 1" @finish="taskFormFinish" @tab-tap="taskTabsClick" />
  95. </hc-body>
  96. </div>
  97. </div>
  98. </div>
  99. <template #footer>
  100. <div class="hc-task-dialog-footer">
  101. <el-button :disabled="tabsKey !== 1" @click="rejectionClick">驳回审批</el-button>
  102. <el-button type="primary" :loading="confirmLoading" :disabled="tabsKey !== 1" @click="confirmClick">同意审批</el-button>
  103. </div>
  104. </template>
  105. </hc-new-dialog>
  106. <!-- 批注 -->
  107. <HcTaskNotes v-model="isNotesShow" :table="tableNoteInfo" :info="rowInfo" :is-edit="tabsKey === 1" @finish="taskNotesFinish" />
  108. <!-- 驳回 -->
  109. <HcRepealForm v-model="isRepealShow" :info="rowInfo" @finish="taskRepealFinish" />
  110. <!-- 短信认证 -->
  111. <HcSmsAuth :loading="SMSAuthLoading" :show="SMSAuthShow" @cancel="SMSAuthCancel" @confirm="SMSAuthConfirm" />
  112. </template>
  113. <script setup>
  114. import { nextTick, ref, watch } from 'vue'
  115. import { useAppStore } from '~src/store'
  116. import HcTaskForm from './task-form.vue'
  117. import HcTaskNotes from './task-notes.vue'
  118. import HcRepealForm from './repeal-form.vue'
  119. import { arrUnion, getArrValue, getObjValue, getRandom, isNullES } from 'js-fast-way'
  120. import mainApi from '~api/tasks/hc-data'
  121. import dayjs from 'dayjs'
  122. const props = defineProps({
  123. tabs: {
  124. type: [String, Number],
  125. default: '',
  126. },
  127. row: {
  128. type: Object,
  129. default: () => ({}),
  130. },
  131. })
  132. //事件
  133. const emit = defineEmits(['finish', 'close'])
  134. const uuid = getRandom(4)
  135. const useAppState = useAppStore()
  136. const projectId = ref(useAppState.getProjectId || '')
  137. const contractId = ref(useAppState.getContractId || '')
  138. //双向绑定
  139. // eslint-disable-next-line no-undef
  140. const isShow = defineModel('modelValue', {
  141. default: false,
  142. })
  143. //监听
  144. const tableRef = ref(null)
  145. const tabsKey = ref(Number(props.tabs))
  146. const rowInfo = ref(props.row)
  147. watch(() => [props.tabs, props.row], ([key, row]) => {
  148. tabsKey.value = Number(key)
  149. rowInfo.value = row
  150. }, {
  151. immediate: true,
  152. deep: true,
  153. })
  154. //监听显示
  155. watch(isShow, (val) => {
  156. if (val) {
  157. checkSmsCode()
  158. setTaskInfo()
  159. setSplitRef()
  160. }
  161. })
  162. //初始化设置拖动分割线
  163. const setSplitRef = () => {
  164. //配置参考: https://split.js.org/#/?direction=vertical&snapOffset=0
  165. nextTick(() => {
  166. window.$split(['#hc_task_table_' + uuid, '#hc_task_form_' + uuid], {
  167. sizes: [50, 50],
  168. snapOffset: 0,
  169. minSize: [50, 500],
  170. })
  171. })
  172. }
  173. //设置任务信息
  174. const setTaskInfo = () => {
  175. //meterType:1中间,2材料,3开工,4变更令
  176. const { meterType } = rowInfo.value
  177. if (meterType === 1) {
  178. tableColumn.value = middlepayTableColumn.value
  179. } else if (meterType === 2) {
  180. tableColumn.value = materialTableColumn.value
  181. } else if (meterType === 3) {
  182. tableColumn.value = startWorkTableColumn.value
  183. } else if (meterType === 4) {
  184. tableColumn.value = alterTableColumn.value
  185. } else {
  186. tableColumn.value = []
  187. }
  188. getTableDetail()
  189. }
  190. //获取数据详情
  191. const detailInfo = ref({})
  192. const reportAllMoney = ref('0')
  193. const progressMoney = ref('0')
  194. const isLoading = ref(false)
  195. const getTableDetail = async () => {
  196. isLoading.value = true
  197. confirmLoading.value = true
  198. //获取数据
  199. const { data } = await mainApi.getDetail(rowInfo.value.id)
  200. const infoData = getObjValue(data)
  201. const { taskProcessInfo, taskCenterDataInfo } = infoData
  202. tableData.value = getArrValue(taskCenterDataInfo)
  203. flowList.value = getArrValue(taskProcessInfo)
  204. reportAllMoney.value = infoData.reportAllMoney
  205. progressMoney.value = infoData.progressMoney
  206. detailInfo.value = infoData
  207. if (rowInfo.value?.fixedFlowId) {
  208. const list = [...flowList.value]
  209. let firstarr = list.slice(0, 1)
  210. let taskList = list.slice(1, list.length)
  211. taskList.forEach((ele)=>{
  212. ele.name = ele.taskBranchName
  213. ele.status = ele.taskBranchStatus
  214. ele.type = ele.taskBranchType
  215. ele.isTask = true
  216. })
  217. flowListTask.value = arrUnion(firstarr, taskList)
  218. }
  219. //默认选中第一行
  220. let info = {}
  221. if (tableData.value.length > 0) {
  222. info = tableData.value[0]
  223. }
  224. await nextTick(() => {
  225. tableInfo.value = info
  226. tableRef.value?.tableRef?.setCurrentRow(info)
  227. })
  228. //关闭加载状态
  229. isLoading.value = false
  230. confirmLoading.value = false
  231. }
  232. //流程信息,1待审批,2已审批
  233. const flowList = ref([])
  234. //type为1流程审批,type为2是平行审批
  235. const flowListTask = ref([
  236. { name: 'PCT', date: '2024-03-01 09:27:17', status: '2', flowValue: '上报', isTask:false },
  237. { name: '"222"', date: '', status: '2', flowValue: '', type:1, isTask:true },
  238. { name: '"111"', date: '', status: '1', flowValue: '', type:2, isTask:true },
  239. ])
  240. const taskDetailList = ref([])
  241. //中间计量单的表格数据
  242. const middlepayTableColumn = ref([
  243. { key: 'action', name: '批注', width: 45, align: 'center' },
  244. { key: 'meterNumber', name: '计量单编号' },
  245. { key: 'meterMoney', name: '计量金额', width: 100 },
  246. { key: 'engineerDivide', name: '工程划分' },
  247. { key: 'state', name: '审批状态', fixed: 'right', width: 70, align: 'center' },
  248. ])
  249. //开工预付款计量单的表格数据
  250. const startWorkTableColumn = ref([
  251. { key: 'action', name: '批注', width: 45, align: 'center' },
  252. { key: 'periodName', name: '计量期', minWidth: 100, align: 'center' },
  253. { key: 'businessDate', name: '业务日期', width: 160, align: 'center' },
  254. { key: 'meterMoney', name: '计量金额', width: 100, align: 'center' },
  255. { key: 'state', name: '审批状态', fixed: 'right', width: 70, align: 'center' },
  256. ])
  257. //变更令的表格数据
  258. const alterTableColumn = ref([
  259. { key: 'action', name: '批注', width: 45, align: 'center' },
  260. { key: 'changeNumber', name: '变更编号', minWidth: 120, align: 'center' },
  261. { key: 'changeName', name: '变更名称', minWidth: 120, align: 'center' },
  262. { key: 'changeMoney', name: '变更金额', width: 100, align: 'center' },
  263. { key: 'changeApprovalDate', name: '变更批复日期', width: 160, align: 'center' },
  264. { key: 'state', name: '审批状态', fixed: 'right', width: 70, align: 'center' },
  265. ])
  266. //材料计量单的表格数据
  267. const materialTableColumn = ref([
  268. { key: 'action', name: '批注', width: 45, align: 'center' },
  269. { key: 'periodName', name: '计量期', minWidth: 100, align: 'center' },
  270. { key: 'contractMaterialName', name: '合同材料', minWidth: 120, align: 'center' },
  271. { key: 'materialArriveNumber', name: '材料到场编号', width: 120, align: 'center' },
  272. { key: 'meterMoney', name: '计量金额', width: 100, align: 'center' },
  273. { key: 'state', name: '审批状态', fixed: 'right', width: 70, align: 'center' },
  274. ])
  275. //表格数据
  276. const tableColumn = ref([])
  277. const tableData = ref([])
  278. //表格行被点击
  279. const tableInfo = ref({})
  280. const tableRowClick = ({ row }) => {
  281. tableInfo.value = row
  282. }
  283. //批注, isComment 是否已批注,1=是,0=否
  284. const isNotesShow = ref(false)
  285. const tableNoteInfo = ref({})
  286. const rowRemarkClick = (row) => {
  287. tableNoteInfo.value = row
  288. nextTick(() => {
  289. isNotesShow.value = true
  290. })
  291. }
  292. //批注完成
  293. const taskNotesFinish = () => {
  294. getTableDetail()
  295. }
  296. //单条审批
  297. const taskFormFinish = () => {
  298. getTableDetail()
  299. }
  300. //确认审批
  301. const confirmLoading = ref(false)
  302. const confirmClick = () => {
  303. const ShowAuth = isCheckSmsCodeTime()
  304. SMSAuthShow.value = ShowAuth
  305. //免短信验证
  306. if (!ShowAuth) SMSAuthConfirm()
  307. }
  308. //驳回审批
  309. const isRepealShow = ref(false)
  310. const rejectionClick = async () => {
  311. isRepealShow.value = true
  312. }
  313. //驳回完成
  314. const taskRepealFinish = () => {
  315. emit('finish')
  316. cancelClick()
  317. }
  318. //取消审批
  319. const cancelClick = () => {
  320. isShow.value = false
  321. isLoading.value = false
  322. confirmLoading.value = false
  323. tableColumn.value = []
  324. tableData.value = []
  325. tableInfo.value = {}
  326. emit('close')
  327. }
  328. const taskDetailvisible = ref(false)
  329. const getTaskDetail = ()=>{
  330. taskDetailvisible.value = true
  331. }
  332. //短信验证有效期
  333. const smsCodeTime = ref('')
  334. const checkSmsCode = async () => {
  335. const { data } = await mainApi.checkSmsCode()
  336. smsCodeTime.value = data ? data : ''
  337. }
  338. //验证短信有效期
  339. const isCheckSmsCodeTime = () => {
  340. const smsTime = smsCodeTime.value
  341. if (isNullES(smsTime)) {
  342. return true
  343. } else {
  344. const toDayTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss')
  345. return dayjs(smsTime).isBefore(toDayTime)
  346. }
  347. }
  348. //短信验证
  349. const SMSAuthLoading = ref(false)
  350. const SMSAuthShow = ref(false)
  351. const SMSAuthConfirm = async () => {
  352. confirmLoading.value = true
  353. const { error, code, msg } = await mainApi.taskApprove({
  354. taskId: rowInfo.value.id,
  355. projectId: projectId.value,
  356. contractId: contractId.value,
  357. })
  358. if (!error && code === 200) {
  359. window.$message.success('审批成功')
  360. await checkSmsCode()
  361. confirmLoading.value = false
  362. emit('finish')
  363. SMSAuthCancel()
  364. cancelClick()
  365. } else {
  366. confirmLoading.value = false
  367. window.$message.error(msg ?? '审批失败')
  368. }
  369. }
  370. const SMSAuthCancel = () => {
  371. SMSAuthShow.value = false
  372. }
  373. //选项卡被切换
  374. const taskTabsKey = ref('key1')
  375. const taskTabsClick = (key) => {
  376. taskTabsKey.value = key
  377. }
  378. </script>
  379. <style lang="scss" scoped>
  380. .hc-task-name {
  381. font-weight: bold;
  382. color: #1A1a1a;
  383. padding-bottom: 10px;
  384. border-bottom: 1px solid #f5f5f5;
  385. }
  386. .hc-task-body {
  387. height: calc(100% - 27px);
  388. .hc-task-time {
  389. position: relative;
  390. height: 100%;
  391. flex-shrink: 0;
  392. width: 170px;
  393. }
  394. .hc-task-table, .hc-task-form {
  395. position: relative;
  396. height: 100%;
  397. flex: 1;
  398. flex-basis: auto;
  399. }
  400. .hc-task-table {
  401. border-left: 1px solid #e5e5e5;
  402. }
  403. }
  404. //表格图标
  405. .hc-task-table-action, .hc-task-table-state {
  406. position: relative;
  407. display: flex;
  408. justify-content: center;
  409. align-items: center;
  410. cursor: pointer;
  411. font-size: 20px;
  412. color: #929293;
  413. i {
  414. display: inline-flex;
  415. }
  416. }
  417. //表格批注
  418. .hc-task-table-action.is-cur {
  419. color: #F2B90B;
  420. }
  421. //表格状态
  422. .hc-task-table-state {
  423. .is-success {
  424. color: #25a62d;
  425. }
  426. .is-danger {
  427. color: #F5221D;
  428. }
  429. span {
  430. color: #1A1a1a;
  431. }
  432. }
  433. //弹窗底部
  434. .hc-task-dialog-footer {
  435. position: relative;
  436. text-align: center;
  437. }
  438. </style>
  439. <style lang="scss">
  440. .hc-task-body-card {
  441. background: #f7f7f7;
  442. .el-scrollbar__bar.is-vertical {
  443. right: -8px;
  444. }
  445. .hc-task-body-table {
  446. position: relative;
  447. height: calc(100% - 30px);
  448. }
  449. .hc-task-body-tip {
  450. color: red;
  451. }
  452. }
  453. //html表单模式
  454. .hc-task-body .hc-task-form.is-tab-key3 .hc-task-body-card {
  455. .el-scrollbar__view {
  456. height: 100%;
  457. }
  458. .hc-task-form-body {
  459. height: 100%;
  460. .el-tabs {
  461. height: 100%;
  462. .el-tabs__content {
  463. height: calc(100% - 39px);
  464. #pane-key3 {
  465. height: 100%;
  466. }
  467. }
  468. }
  469. }
  470. .hc-task-html-form-body {
  471. height: 100%;
  472. .hc-table-form-data-item .el-scrollbar__view {
  473. height: auto;
  474. }
  475. }
  476. }
  477. </style>