detail.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. <template>
  2. <hc-sys class="h-full hc-app-task-detail" :isNavBar="false">
  3. <uni-section class="mt-0.5" title="当前任务名称" type="line">
  4. <template v-slot:right>
  5. <view class="hc-flex text-gray-5" @click="taskPopupClick">
  6. <text class="mr-1">切换任务</text>
  7. <uni-icons type="bottom" size="18" color="#6b7280"/>
  8. </view>
  9. </template>
  10. <view class="task-name">{{taskInfo.taskName}}</view>
  11. <view class="task-report-info">
  12. <view>上报人:{{taskInfo.taskReportUserName}}</view>
  13. <view>{{taskInfo.startTime}}</view>
  14. </view>
  15. </uni-section>
  16. <uni-section class="mt-2" :title="`当前表格 (共${taskFileInfo?.fileNum ?? 0}张)`" sub-title="点击表格名称查看PDF" type="line">
  17. <template v-slot:right>
  18. <view class="hc-flex text-gray-5" @click="tablePopupClick">
  19. <text class="mr-1">切换表格</text>
  20. <uni-icons type="bottom" size="18" color="#6b7280"/>
  21. </view>
  22. </template>
  23. <view class="task-file-name" @click="viewPdfClick">{{taskFileInfo.fileName}}</view>
  24. </uni-section>
  25. <!--底部操作栏-->
  26. <hc-tabbars v-if="isTaskAuth">
  27. <button type="primary" class="action-bar-btn" @click="approveClick">
  28. <text>审</text> <text class="ml-6">批</text>
  29. </button>
  30. </hc-tabbars>
  31. <!-- 普通弹窗 -->
  32. <uni-popup ref="popupRef" class="hc-popup" type="bottom">
  33. <view class="task-popup-content">
  34. <template v-if="popupType === 1">
  35. <view class="title">请选择审批是否通过?</view>
  36. <view class="popup-btn-bar">
  37. <button type="primary" class="popup-btn c1" @click="agreeClick">同意签字审批</button>
  38. </view>
  39. <view class="popup-btn-bar">
  40. <button type="primary" class="popup-btn c2" @click="cancelTaskClick">废除任务</button>
  41. </view>
  42. <view class="popup-btn-bar">
  43. <button type="primary" class="popup-btn c3" @click="cancelApproval">取消审批</button>
  44. </view>
  45. </template>
  46. <template v-if="popupType === 2">
  47. <view class="title">请输入验证码验证是否为本人操作授权</view>
  48. <view class="popup-code-bar">
  49. <view class="btn-tel-code-bar">
  50. <view class="btn-tel">{{userInfo.phone}}</view>
  51. <view class="btn-code">
  52. <button type="primary" :disabled="isCodeDisabled" class="popup-btn code" size="mini" @click="getCodeClick">
  53. {{ isCodeDisabled ? `倒计时${currentCodeTime}s` : '发送验证码' }}
  54. </button>
  55. </view>
  56. </view>
  57. <view class="code-input-bar">
  58. <input class="code-input" v-model="smsCode" placeholder="请输入验证码"/>
  59. </view>
  60. </view>
  61. <view class="popup-btn-bar">
  62. <button type="primary" class="popup-btn c4" @click="confirmApproval">确认审批</button>
  63. </view>
  64. <view class="popup-btn-bar">
  65. <button type="primary" class="popup-btn c3" @click="cancelClick">取消操作</button>
  66. </view>
  67. </template>
  68. <template v-if="popupType === 3">
  69. <view class="popup-argument-bar">
  70. <textarea class="argument-input" v-model="argument" :maxlength="-1" placeholder="请输入废除任务的理由"/>
  71. </view>
  72. <view class="popup-btn-bar">
  73. <button type="primary" class="popup-btn c1" @click="confirmRepeal">确认废除</button>
  74. </view>
  75. <view class="popup-btn-bar">
  76. <button type="primary" class="popup-btn c3" @click="cancelClick">取消操作</button>
  77. </view>
  78. </template>
  79. </view>
  80. </uni-popup>
  81. <!-- 任务列表 -->
  82. <uni-popup ref="popupTaskRef" type="bottom">
  83. <view class="relative bg-white h-50vh hc-p br-t">
  84. <view class="text-black mb-3 text-center">选择任务数据</view>
  85. <view class="relative hc-app-task-view">
  86. <template v-for="(item, index) in taskList" :key="index">
  87. <view class="relative mt-3" :class="item.id === taskInfo.id ? 'text-blue' :''" @click="taskClick(item)">{{item.taskName}}</view>
  88. </template>
  89. </view>
  90. </view>
  91. </uni-popup>
  92. <!-- 表格列表 -->
  93. <uni-popup ref="popupTableRef" type="bottom">
  94. <view class="relative bg-white h-50vh hc-p br-t">
  95. <view class="text-black mb-3 text-center">选择表格数据</view>
  96. <template v-for="(item, index) in taskInfo.approvalFileList" :key="index">
  97. <view class="relative mt-3" :class="item.id === taskFileInfo.id ? 'text-blue' :''" @click="taskFileClick(item)">{{item.fileName}}</view>
  98. </template>
  99. </view>
  100. </uni-popup>
  101. </hc-sys>
  102. </template>
  103. <script setup>
  104. import {ref, nextTick, getCurrentInstance} from "vue";
  105. import {onLoad, onReady} from '@dcloudio/uni-app'
  106. import {arrToKey, getArrValue, getObjValue, isString} from "js-fast-way";
  107. import {errorToast, successToast, toPdfPreview} from "@/utils/tools";
  108. import {checkFlowUserIsExistPfxFile, saveSmsTimeout, sendNotice} from "~api/other/index";
  109. import mainApi from '~api/tasks/data';
  110. import {useAppStore} from "@/store";
  111. import dayjs from "dayjs";
  112. //初始变量
  113. const store = useAppStore()
  114. const instance = getCurrentInstance().proxy
  115. const projectId = ref(store.projectId);
  116. const contractId = ref(store.contractId);
  117. const userInfo = ref(store.userInfo);
  118. const taskList = ref([])
  119. const taskInfo = ref({})
  120. const isTaskAuth = ref(false)
  121. //页面传参数据
  122. let eventChannel = null;
  123. const getEventChannel = async () => {
  124. await nextTick();
  125. eventChannel = instance.getOpenerEventChannel();
  126. }
  127. onLoad(({node}) => {
  128. const {rows, isTask} = node ? JSON.parse(decodeURIComponent(node)) : {};
  129. const res = getArrValue(rows);
  130. if (res.length > 0) {
  131. taskInfo.value = res[0]
  132. }
  133. taskList.value = res
  134. isTaskAuth.value = !!isTask
  135. getDataApi()
  136. })
  137. //渲染完成
  138. onReady(() => {
  139. uni.setNavigationBarTitle({title: '审批任务'})
  140. })
  141. //获取数据
  142. const getDataApi = async () => {
  143. uni.showLoading({title: '获取数据中...', mask: true});
  144. await getEventChannel()
  145. await getTaskInfo()
  146. checkSmsCode().then()
  147. uni.hideLoading();
  148. }
  149. //切换任务
  150. const taskClick = (item) => {
  151. taskInfo.value = item
  152. taskFileInfo.value = item.approvalFileList[0]
  153. popupTaskRef.value?.close()
  154. }
  155. //切换PDF
  156. const taskFileInfo = ref({})
  157. const taskFileClick = (item) => {
  158. taskFileInfo.value = item
  159. popupTableRef.value?.close()
  160. }
  161. //获取任务详情
  162. const getTaskInfo = async () => {
  163. const rows = taskList.value
  164. if (rows.length <= 0) return false
  165. //处理详情数据
  166. for (let i = 0; i < rows.length; i++) {
  167. const res = await queryApprovalParameter(rows[i])
  168. rows[i].approvalFileList = res.approvalFileList
  169. }
  170. taskInfo.value = getObjValue(rows[0])
  171. taskFileInfo.value = getObjValue(rows[0].approvalFileList[0])
  172. taskList.value = rows
  173. }
  174. //获取审批详情
  175. const queryApprovalParameter = async ({parallelProcessInstanceId, formDataId, approvalType}) => {
  176. const { data } = await mainApi.queryApprovalParameter({
  177. projectId: projectId.value,
  178. contractId: contractId.value,
  179. parallelProcessInstanceId: parallelProcessInstanceId ?? '',
  180. formDataId: formDataId ?? '',
  181. approvalType: approvalType ?? ''
  182. })
  183. const res = getObjValue(data)
  184. const list = getArrValue(res.approvalFileList)
  185. for (let i = 0; i < list.length; i++) {
  186. list[i].fileNum = await queryPDFnumApi(list[i]?.fileUrl)
  187. }
  188. res.approvalFileList = list
  189. return getObjValue(data)
  190. }
  191. //查询PDF数量
  192. const queryPDFnumApi = async (pdfUrl) => {
  193. const { data } = await mainApi.queryPDFnum({
  194. projectId: projectId.value,
  195. contractId: contractId.value,
  196. url: pdfUrl
  197. })
  198. return data ? data : 0
  199. }
  200. //任务列表
  201. const popupTaskRef = ref(null)
  202. const taskPopupClick = () => {
  203. popupTaskRef.value?.open()
  204. }
  205. //表格列表
  206. const popupTableRef = ref(null)
  207. const tablePopupClick = () => {
  208. popupTableRef.value?.open()
  209. }
  210. //审批弹窗
  211. const popupRef = ref(null)
  212. const popupType = ref(1)
  213. const approveClick = () => {
  214. popupType.value = 1
  215. popupRef.value?.open()
  216. }
  217. //短信验证有效期
  218. const smsCodeTime = ref('')
  219. const checkSmsCode = async () => {
  220. const {data} = await mainApi.checkSmsCode()
  221. smsCodeTime.value = isString(data) ? data : '';
  222. }
  223. //验证短信有效期
  224. const isCheckSmsCodeTime = () => {
  225. const smsTime = smsCodeTime.value;
  226. if (!smsTime) return true
  227. const toDayTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
  228. return dayjs(smsTime).isBefore(toDayTime)
  229. }
  230. //同意审批短信验证
  231. const smsCode = ref('')
  232. const agreeClick = async () => {
  233. const showAuth = isCheckSmsCodeTime()
  234. if (showAuth) {
  235. smsCode.value = ''
  236. popupType.value = 2
  237. } else {
  238. const {error, code, msg, data} = await checkFlowUserIsExistPfxFile({
  239. projectId: projectId.value,
  240. contractId: contractId.value,
  241. })
  242. if (!error && code === 200 && data === true) {
  243. const showAuth = isCheckSmsCodeTime()
  244. if (showAuth) {
  245. smsCode.value = ''
  246. popupType.value = 2
  247. } else {
  248. batchTaskApproval('ok').then()
  249. }
  250. } else {
  251. errorToast(msg)
  252. cancelApproval()
  253. }
  254. }
  255. }
  256. //验证码倒计时
  257. const totalTime = 60 //总时间,单位秒
  258. const isCodeDisabled = ref(false) //是否开启倒计时
  259. const recordingCodeTime = ref(0) //记录时间变量
  260. const currentCodeTime = ref(0) //显示时间变量
  261. //获取短信验证码
  262. const resCode = ref('')
  263. const getCodeClick = async () => {
  264. const {phone} = userInfo.value
  265. if (!phone) {
  266. errorToast('您的手机号是空的,无法获取验证码')
  267. return false
  268. }
  269. const { error, code, msg } = await sendNotice({
  270. phone: phone
  271. })
  272. //处理数据
  273. if (!error && code === 200 && msg) {
  274. resCode.value = msg
  275. //把显示时间设为总时间
  276. currentCodeTime.value = totalTime
  277. //开始倒计时
  278. isCodeDisabled.value = true
  279. //执行倒计时
  280. checkingTime()
  281. successToast('发送成功')
  282. } else {
  283. resCode.value = ''
  284. errorToast(msg)
  285. }
  286. }
  287. //倒计时
  288. const checkingTime = () => {
  289. //判断是否开启
  290. if (isCodeDisabled.value) {
  291. //判断显示时间是否已到0,判断记录时间是否已到总时间
  292. if (currentCodeTime.value > 0 && recordingCodeTime.value <= totalTime) {
  293. //记录时间增加 1
  294. recordingCodeTime.value++
  295. //显示时间,用总时间 - 记录时间
  296. currentCodeTime.value = totalTime - recordingCodeTime.value
  297. //1秒钟后,再次执行本方法
  298. setTimeout(() => {
  299. checkingTime()
  300. }, 1000)
  301. } else {
  302. //时间已完成,还原相关变量
  303. isCodeDisabled.value = false //关闭倒计时
  304. recordingCodeTime.value = 0 //记录时间为0
  305. currentCodeTime.value = totalTime //显示时间为总时间
  306. }
  307. } else {
  308. //倒计时未开启,初始化默认变量
  309. isCodeDisabled.value = false
  310. recordingCodeTime.value = 0
  311. currentCodeTime.value = totalTime
  312. }
  313. }
  314. //确认审批
  315. const confirmApproval = () => {
  316. if (!resCode.value) {
  317. errorToast('请先获取验证码')
  318. return false
  319. } else if (!smsCode.value) {
  320. errorToast('请先输入验证码')
  321. return false
  322. } else if (resCode.value !== smsCode.value) {
  323. errorToast('验证码错误')
  324. return false
  325. }
  326. //验证码过期时间
  327. saveSmsTimeout({code: resCode.value})
  328. //处理数据
  329. batchTaskApproval('ok')
  330. checkSmsCode()
  331. }
  332. //批量审批
  333. const batchTaskApproval = async (type) => {
  334. let res = {}, t = type === 'ok' ? '审批' : '废除';
  335. uni.showLoading({title: '提交审批中...', mask: true});
  336. if (type === 'ok') {
  337. res = await batchCompleteApprovalTaskApi({
  338. flag: 'OK',
  339. comment: 'OK',
  340. })
  341. } else {
  342. res = await batchCompleteApprovalTaskApi({
  343. flag: 'NO',
  344. comment: argument.value,
  345. })
  346. }
  347. //处理数据
  348. uni.hideLoading();
  349. const {error, code, msg} = res
  350. if (!error && code === 200) {
  351. successToast(`${t}成功`)
  352. cancelApproval()
  353. eventChannel.emit('finish');
  354. setTimeout(() => {
  355. uni.navigateBack()
  356. }, 3000)
  357. } else {
  358. errorToast(`${t}失败:${msg}`)
  359. }
  360. }
  361. //批量审批
  362. const batchCompleteApprovalTaskApi = async (obj = {}) => {
  363. const rows = taskList.value
  364. let taskIds = arrToKey(rows, 'taskId', ',')
  365. let approvalType = arrToKey(rows, 'approvalType', ',')
  366. let formDataId = arrToKey(rows, 'formDataId', ',')
  367. let parallelProcessInstanceIds = arrToKey(rows, 'parallelProcessInstanceId', ',')
  368. return await mainApi.batchCompleteApprovalTask({
  369. ...obj,
  370. projectId: projectId.value,
  371. contractId: contractId.value,
  372. taskIds,
  373. approvalType,
  374. formDataId,
  375. parallelProcessInstanceIds
  376. })
  377. }
  378. //废除任务并输入理由
  379. const argument = ref('')
  380. const cancelTaskClick = () => {
  381. argument.value = ''
  382. popupType.value = 3
  383. }
  384. //确认废除
  385. const confirmRepeal = () => {
  386. if (!argument.value) {
  387. errorToast('请先填写废除理由')
  388. return false
  389. }
  390. batchTaskApproval('no')
  391. }
  392. //取消审批
  393. const cancelApproval = () => {
  394. popupType.value = 1
  395. popupRef.value?.close()
  396. }
  397. //取消操作
  398. const cancelClick = () => {
  399. popupType.value = 1
  400. smsCode.value = ''
  401. argument.value = ''
  402. }
  403. //查看PDF
  404. const viewPdfClick = () => {
  405. const {fileUrl} = taskFileInfo.value
  406. if (fileUrl) {
  407. // #ifdef H5
  408. window.open(fileUrl, '_blank')
  409. // #endif
  410. // #ifdef APP-PLUS
  411. uni.navigateTo({
  412. url: '/pages/index/preview?url=' + encodeURIComponent(fileUrl)
  413. });
  414. // #endif
  415. } else {
  416. errorToast('PDF文件不存在')
  417. }
  418. }
  419. </script>
  420. <style lang="scss">
  421. @import "@/style/task/detail.scss";
  422. </style>