reportResult.vue 9.8 KB

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