reportResult.vue 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. <template>
  2. <view class="default_page" :style="{'height':h+'px', 'padding-top':mt+'px'}">
  3. <cus-header title='生成报告' bgColor="transparent"></cus-header>
  4. <div class="box adffcac">
  5. <image class="box-loading" src="https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/e6d5bea9491a14aafd3f955d332e62d08e521229/report_success.png" v-if="generation.imageKey==='success'"></image>
  6. <image class="box-loading" src="https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/e6d5bea9491a14aafd3f955d332e62d08e521229/report_fail.png" v-else></image>
  7. <div class="box-p1">{{generation.title}}</div>
  8. <div class="box-p2" v-if="generation.subtitle">{{generation.subtitle}}</div>
  9. <div class="box-p3">{{generation.state===-1 ? '失败原因:'+generation.errorMessage : generation.description}}</div>
  10. </div>
  11. <div class="form">
  12. <div class="form-item adfacjb">
  13. <div class="form-item-left">问卷名称</div>
  14. <div class="form-item-right">{{info.title||''}}</div>
  15. </div>
  16. <div class="form-item adfacjb">
  17. <div class="form-item-left">团队名称</div>
  18. <div class="form-item-right">{{info.teamName||''}}</div>
  19. </div>
  20. <div class="form-item adfacjb">
  21. <div class="form-item-left">创建时间</div>
  22. <div class="form-item-right">{{info.startTime||info.createDate||''}}</div>
  23. </div>
  24. </div>
  25. <div class="btn">
  26. <div class="zt_btn" @click="handleAction">{{generation.actionText}}</div>
  27. </div>
  28. </view>
  29. </template>
  30. <script>
  31. function toNumber(value) {
  32. if (value === '' || value === null || value === undefined) return undefined
  33. const numeric = Number(value)
  34. return Number.isNaN(numeric) ? undefined : numeric
  35. }
  36. function extractReportId(payload) {
  37. if (payload === null || payload === undefined) return undefined
  38. if (typeof payload === 'number') return payload
  39. if (typeof payload === 'string') return toNumber(payload)
  40. if (typeof payload !== 'object') return undefined
  41. const direct = toNumber(payload.reportId ?? payload.id)
  42. if (direct !== undefined) return direct
  43. for (const key of ['data', 'dto', 'info']) {
  44. const nested = extractReportId(payload[key])
  45. if (nested !== undefined) return nested
  46. }
  47. return undefined
  48. }
  49. function normalizeGenerationStatus(payload = {}) {
  50. const source = payload && typeof payload === 'object' ? payload : {}
  51. const state = toNumber(source.state)
  52. return {
  53. reportId: extractReportId(source) || '',
  54. state: state === undefined ? 0 : state,
  55. status: source.status || '',
  56. errorMessage: source.errorMessage || ''
  57. }
  58. }
  59. function applyGenerationStatus(payload = {}) {
  60. const generation = normalizeGenerationStatus(payload)
  61. if (generation.state === -1) {
  62. return {
  63. ...generation,
  64. title: '报告生成失败',
  65. subtitle: '',
  66. description: generation.errorMessage || '失败原因:网络延迟',
  67. errorMessage: generation.errorMessage || '网络延迟',
  68. polling: false,
  69. actionText: '重新生成',
  70. imageKey: 'fail'
  71. }
  72. }
  73. if (generation.state === 1) {
  74. return {
  75. ...generation,
  76. title: '报告生成成功',
  77. subtitle: '',
  78. description: '报告已生成完成,你可以去我的PREILL报告查阅报告结果。',
  79. errorMessage: '',
  80. polling: false,
  81. actionText: '返回',
  82. imageKey: 'success'
  83. }
  84. }
  85. return {
  86. ...generation,
  87. title: '报告正在生成中~',
  88. subtitle: '预计所需时间1小时左右',
  89. description: '我们会在报告生成完成后提示你,你可以去我的PREILL报告查阅报告结果。',
  90. errorMessage: '',
  91. polling: true,
  92. actionText: '返回',
  93. imageKey: 'success'
  94. }
  95. }
  96. function shouldResumePolling(reportId, generation = {}) {
  97. return Boolean(reportId && normalizeGenerationStatus(generation).state === 0)
  98. }
  99. function setStatusPageVisible(vm, visible) {
  100. if(vm.isPageVisible === visible) return vm.statusVisibilityToken
  101. vm.isPageVisible = visible
  102. if(!visible){
  103. vm.statusVisibilityToken++
  104. vm.statusActiveRequestId = 0
  105. vm.statusRequestInFlight = false
  106. }
  107. return vm.statusVisibilityToken
  108. }
  109. function beginStatusRequest(vm) {
  110. if(!vm.isPageVisible || vm.statusRequestInFlight) return null
  111. vm.statusRequestSeq++
  112. vm.statusActiveRequestId = vm.statusRequestSeq
  113. vm.statusRequestInFlight = true
  114. return {
  115. requestId: vm.statusActiveRequestId,
  116. visibilityToken: vm.statusVisibilityToken
  117. }
  118. }
  119. function shouldApplyStatusResponse(vm, meta) {
  120. return Boolean(
  121. meta
  122. && vm.isPageVisible
  123. && vm.statusRequestInFlight
  124. && vm.statusActiveRequestId === meta.requestId
  125. && vm.statusVisibilityToken === meta.visibilityToken
  126. )
  127. }
  128. function finishStatusRequest(vm, meta) {
  129. if(!meta) return
  130. if(vm.statusActiveRequestId === meta.requestId){
  131. vm.statusActiveRequestId = 0
  132. vm.statusRequestInFlight = false
  133. }
  134. }
  135. export default {
  136. data(){
  137. return {
  138. reportId:'',
  139. info:{},
  140. generation:applyGenerationStatus(),
  141. pollTimer:null,
  142. hasStatusErrorToastShown:false,
  143. isPageVisible:false,
  144. statusVisibilityToken:0,
  145. statusRequestSeq:0,
  146. statusActiveRequestId:0,
  147. statusRequestInFlight:false
  148. }
  149. },
  150. onLoad(options) {
  151. this.info = options.info&&JSON.parse(decodeURIComponent(options.info)) || {}
  152. this.reportId = extractReportId({ reportId:options.reportId, dto:options, info:this.info }) || ''
  153. if(!this.reportId){
  154. this.stopPolling('报告编号缺失')
  155. return
  156. }
  157. },
  158. onShow() {
  159. setStatusPageVisible(this, true)
  160. if(shouldResumePolling(this.reportId, this.generation)){
  161. this.startPolling()
  162. this.fetchGenerationStatus(false)
  163. }
  164. },
  165. onHide() {
  166. setStatusPageVisible(this, false)
  167. this.clearPolling()
  168. },
  169. onUnload() {
  170. setStatusPageVisible(this, false)
  171. this.clearPolling()
  172. },
  173. methods:{
  174. startPolling(){
  175. if(!this.isPageVisible || !shouldResumePolling(this.reportId, this.generation) || this.pollTimer) return
  176. this.pollTimer = setInterval(() => {
  177. this.fetchGenerationStatus(false)
  178. }, 5000)
  179. },
  180. clearPolling(){
  181. if(this.pollTimer){
  182. clearInterval(this.pollTimer)
  183. this.pollTimer = null
  184. }
  185. },
  186. reconcileGeneration(status){
  187. this.generation = applyGenerationStatus(status)
  188. this.hasStatusErrorToastShown = false
  189. if(this.generation.polling && this.reportId) return this.startPolling()
  190. this.clearPolling()
  191. },
  192. stopPolling(errorMessage){
  193. this.clearPolling()
  194. this.generation = applyGenerationStatus({
  195. reportId:this.reportId,
  196. state:-1,
  197. errorMessage:errorMessage || this.generation.errorMessage || '网络延迟'
  198. })
  199. },
  200. fetchGenerationStatus(showErrorToast = true){
  201. if(!this.reportId){
  202. this.stopPolling('报告编号缺失')
  203. return Promise.resolve()
  204. }
  205. const requestMeta = beginStatusRequest(this)
  206. if(!requestMeta) return Promise.resolve()
  207. return this.$api.get(`/core/report/generationStatus/${this.reportId}`, {}, false).then(({data:res})=>{
  208. if(!shouldApplyStatusResponse(this, requestMeta)) return
  209. if(res.code!==0){
  210. if(showErrorToast && !this.hasStatusErrorToastShown){
  211. this.$showToast(res.msg || '报告状态获取失败')
  212. this.hasStatusErrorToastShown = true
  213. }
  214. this.startPolling()
  215. return
  216. }
  217. this.reconcileGeneration({ ...(res.data || {}), reportId:this.reportId })
  218. }).catch(error => {
  219. if(!shouldApplyStatusResponse(this, requestMeta)) return
  220. const message = error && (error.message || error.msg) || '报告状态获取失败'
  221. if(showErrorToast && !this.hasStatusErrorToastShown){
  222. this.$showToast(message)
  223. this.hasStatusErrorToastShown = true
  224. }
  225. this.startPolling()
  226. }).finally(() => {
  227. finishStatusRequest(this, requestMeta)
  228. })
  229. },
  230. handleAction(){
  231. if(this.generation.state===-1){
  232. this.handleReCreate()
  233. return
  234. }
  235. uni.navigateBack()
  236. },
  237. handleReCreate(){
  238. this.$api.get(`/core/team/questionnaire/genReport/${this.info.teamQuestionnaireId}`).then(({data:res})=>{
  239. if(res.code!==0) return this.$showToast(res.msg)
  240. const nextReportId = extractReportId(res.data)
  241. if(!nextReportId) return this.$showToast('未获取到报告编号')
  242. this.$showToast('重新生成成功')
  243. uni.redirectTo({
  244. url:'/pagesHome/reportResult?reportId='+nextReportId+'&info='+encodeURIComponent(JSON.stringify(this.info))
  245. })
  246. })
  247. }
  248. }
  249. }
  250. </script>
  251. <style scoped lang="scss">
  252. .default_page{
  253. background: #F7F7F7;
  254. .box{
  255. width: 100%;
  256. background: #FFFFFF;
  257. border-radius: 36rpx 36rpx 0rpx 0rpx;
  258. padding: 54rpx 0 64rpx;
  259. &-loading{
  260. width: 340rpx;
  261. height: 340rpx;
  262. }
  263. &-p1{
  264. font-family: PingFang-SC, PingFang-SC;
  265. font-weight: bold;
  266. font-size: 32rpx;
  267. color: #002846;
  268. line-height: 40rpx;
  269. text-align: center;
  270. margin-top: 41rpx;
  271. }
  272. &-p2{
  273. font-family: PingFang-SC, PingFang-SC;
  274. font-weight: bold;
  275. font-size: 28rpx;
  276. color: #002846;
  277. line-height: 28rpx;
  278. text-align: center;
  279. margin-top: 23rpx;
  280. }
  281. &-p3{
  282. padding: 0 100rpx;
  283. font-family: PingFangSC, PingFang SC;
  284. font-weight: 400;
  285. font-size: 28rpx;
  286. color: #667E90;
  287. line-height: 42rpx;
  288. text-align: center;
  289. margin-top: 24rpx;
  290. }
  291. }
  292. .form{
  293. width: 100%;
  294. margin-top: 20rpx;
  295. &-item{
  296. padding: 28rpx 24rpx;
  297. background: #FFFFFF;
  298. box-shadow: inset 0rpx -1rpx 0rpx 0rpx #EFEFEF;
  299. &-left{
  300. width: 140rpx;
  301. font-family: PingFangSC, PingFang SC;
  302. font-weight: 400;
  303. font-size: 30rpx;
  304. color: #002846;
  305. line-height: 42rpx;
  306. }
  307. &-right{
  308. width: calc(100% - 140rpx);
  309. padding-left: 20rpx;
  310. box-sizing: border-box;
  311. font-family: PingFangSC, PingFang SC;
  312. font-weight: 400;
  313. font-size: 30rpx;
  314. color: #667E90;
  315. line-height: 32rpx;
  316. text-align: right;
  317. }
  318. }
  319. }
  320. .btn{
  321. width: calc(100% -100rpx);
  322. margin: 120rpx 50rpx 0;
  323. }
  324. }
  325. </style>