detail.vue 15 KB

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