detail.vue 14 KB

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