generateList.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. <template>
  2. <view class="gbox">
  3. <u-list @scrolltolower="scrolltolower" height="100%">
  4. <u-list-item v-for="(item, index) in list" :key="index">
  5. <view class="gbox-item">
  6. <view class="gbox-item-top adf">
  7. <view class="gbox-item-top-type">{{typeCfg[item.type]||'个人版'}}</view>
  8. <view class="gbox-item-top-title">{{item.reportName||''}}</view>
  9. </view>
  10. <view class="gbox-item-ps">
  11. <view class="gbox-item-p adfac">
  12. <view class="gbox-item-p-left">团队名称:</view>
  13. <view class="gbox-item-p-right">{{item.teamName||''}}</view>
  14. </view>
  15. <view class="gbox-item-p adf">
  16. <view class="gbox-item-p-left">报告状态:</view>
  17. <view class="gbox-item-p-right" :style="{'color':stateColor[item.state]}">{{stateDict[item.state]}}</view>
  18. </view>
  19. <view class="gbox-item-p adf">
  20. <view class="gbox-item-p-left">创建时间:</view>
  21. <view class="gbox-item-p-right">{{item.createDate||''}}</view>
  22. </view>
  23. <!-- <image class="gbox-item-ask" :src="imgBase+'report_ask.png'" @click="askReport(item)" v-if="item.state==1&&item.fileUrl"></image> -->
  24. <image class="gbox-item-ask" :src="imgBase+'report_ask.png'" @click="testShow=true" v-if="item.state==1&&item.fileUrl"></image>
  25. </view>
  26. <view class="gbox-item-bottom adfacjb">
  27. <view class="gbox-item-bottom-rightbox adfac" v-if="item.type==1">
  28. <view class="gbox-item-bottom-right" @click="openEvaluation(item)" v-if="canEditEvaluation(item)">评估修改</view>
  29. <view class="gbox-item-bottom-right report-button-disabled" @click="showReportUnavailable('background')" v-if="item.state==1">背景报告</view>
  30. <view class="gbox-item-bottom-right report-button-disabled" @click="showReportUnavailable('professional')" v-if="item.state==1">教练报告</view>
  31. <view class="gbox-item-bottom-right" @click="reviewArtifact(item, 'public')" v-if="item.state==1">团队报告</view>
  32. <view class="gbox-item-bottom-right" @click="reSendReport(item)" v-if="item.state==-1">重新生成</view>
  33. </view>
  34. <view class="gbox-item-bottom-rightbox adfac" v-else-if="item.type==2">
  35. <view class="gbox-item-bottom-right" @click="openEvaluation(item)" v-if="canEditEvaluation(item)">评估修改</view>
  36. <view class="gbox-item-bottom-right" @click="reviewArtifact(item, 'background')" v-if="item.state==1">背景报告</view>
  37. <view class="gbox-item-bottom-right" @click="reviewArtifact(item, 'professional')" v-if="item.state==1">教练报告</view>
  38. <view class="gbox-item-bottom-right" @click="reviewArtifact(item, 'public')" v-if="item.state==1">团队报告</view>
  39. <view class="gbox-item-bottom-right" @click="forwardReport(item)" v-if="item.state==1&&item.fileUrl">发送邮件</view>
  40. <view class="gbox-item-bottom-right" @click="sendReport(item)" v-if="item.state!=-1">发送报告</view>
  41. <view class="gbox-item-bottom-right" @click="reSendReport(item)" v-if="item.state==-1">重新生成</view>
  42. </view>
  43. </view>
  44. </view>
  45. </u-list-item>
  46. </u-list>
  47. <view class="kind-reminder adffcacjc" v-if="testShow">
  48. <view class="kr-box adffcac">
  49. <view class="kr-title">温馨提示</view>
  50. <view class="kr-p" style="margin-top: 54rpx;">功能测试中,即将上线,敬请期待!</view>
  51. <view class="kr-btn" @click="testShow=false">确定</view>
  52. </view>
  53. </view>
  54. <view class="kind-reminder adffcacjc" v-if="reportReminderShow">
  55. <view class="kr-box adffcac">
  56. <view class="kr-title">温馨提示</view>
  57. <view class="kr-p" style="margin-top: 54rpx;">{{reportReminderMsg}}</view>
  58. <view class="kr-btn" @click="reportReminderShow=false">确定</view>
  59. </view>
  60. </view>
  61. <view class="evaluation-mask adffcacjc" v-if="evaluationVisible">
  62. <view class="evaluation-box">
  63. <view class="kr-title">修改评估内容</view>
  64. <view class="evaluation-label">总体评估结果</view>
  65. <textarea class="evaluation-textarea" maxlength="20000" v-model="evaluationForm.overallAssessment" placeholder="请输入总体评估结果"></textarea>
  66. <view class="evaluation-label">团队发展建议</view>
  67. <textarea class="evaluation-textarea" maxlength="20000" v-model="evaluationForm.developmentAdvice" placeholder="请输入团队发展建议"></textarea>
  68. <view class="evaluation-actions">
  69. <view class="evaluation-cancel" @click="closeEvaluation">取消</view>
  70. <view class="evaluation-submit" :class="{disabled:evaluationSubmitting}" @click="submitEvaluation">{{evaluationSubmitting?'提交中':'保存提交'}}</view>
  71. </view>
  72. </view>
  73. </view>
  74. <cus-select-team-user
  75. ref="selectTeamUser"
  76. :show="teamUserShow"
  77. :list="teamUserList"
  78. @close="handleClose"
  79. @changeAll="changeAll"
  80. @handleSelectUser="handleSelectUser"
  81. @handleConfirm="sendConfirm"
  82. ></cus-select-team-user>
  83. </view>
  84. </template>
  85. <script>
  86. import { getPlatformInfo } from '@/utils/platform.js';
  87. import { reportArtifactUrl, reportArtifactDownloadUrl } from '@/utils/reportArtifactLinks.js'
  88. import { canEditEvaluation, EVALUATION_PROCESSING_PROMPT, normalizeEvaluationForm } from '@/utils/reportEvaluationEdit.js'
  89. import CusSelectTeamUser from '@/components/CusSelectTeamUser/index.vue'
  90. export default {
  91. props:{
  92. list:{
  93. typeof:Array,
  94. default:[]
  95. }
  96. },
  97. components:{ CusSelectTeamUser },
  98. data(){
  99. return {
  100. typeCfg:{
  101. 1:'个人版',
  102. 2:'团队版',
  103. 0:'团队PRO版'
  104. },
  105. stateDict:{
  106. '0':'生成中',
  107. '1':'生成成功',
  108. '-1':'生成失败'
  109. },
  110. stateColor:{
  111. '0':'#FFD750',
  112. '1':'#33A7A7',
  113. '-1':'#F4657A'
  114. },
  115. teamUserShow:false,
  116. teamUserList:[],
  117. categoryData:[],
  118. sendType:1,
  119. selectItem:null,
  120. testShow:false,
  121. reportReminderShow:false,
  122. reportReminderMsg:'',
  123. evaluationVisible:false,
  124. evaluationSubmitting:false,
  125. evaluationReportId:'',
  126. evaluationForm:{overallAssessment:'',developmentAdvice:''}
  127. }
  128. },
  129. methods:{
  130. scrolltolower(){
  131. this.$emit('scrolltolower')
  132. },
  133. getUserCategoryData(){
  134. return new Promise((resolve,reject)=>{
  135. this.$api.get('/getListByType/UserCategory').then(({data:res})=>{
  136. if(res.code!==0) return this.$showToast(res.msg)
  137. this.categoryData = res.data.map(d=>({name:d.dictLabel,id:d.dictValue}))
  138. resolve()
  139. })
  140. })
  141. },
  142. async forwardReport(item){
  143. //发送给账号自己的邮箱
  144. if(!uni.getStorageSync('userInfo')||!JSON.parse(uni.getStorageSync('userInfo')).email) return this.$showModal('发送邮件前请先在我的-设置-用户信息中绑定邮箱')
  145. uni.showModal({
  146. title:'温馨提示',
  147. content:`是否确认将“${item.reportName||''}”下的PDF报告文件发送到账号绑定邮箱中?`,
  148. confirmText:'确认',
  149. success: (res) => {
  150. if(res.confirm){
  151. this.$api.get(`/core/report/sendToEmail/${item.reportId}`).then(({data:res})=>{
  152. if(res.code!==0) return this.$showToast(res.msg)
  153. this.$showModal('邮件发送成功,可在绑定的邮箱中查看详细内容。')
  154. })
  155. }
  156. }
  157. })
  158. // this.sendType = 1;
  159. // this.$refs.selectTeamUser.title = '发送邮件'
  160. // await this.getUserCategoryData()
  161. // this.$api.get(`/core/member/listByQueTeamId/${item.teamQueId}`).then(({data:res})=>{
  162. // if(res.code!==0) return this.$showToast(res.msg)
  163. // this.teamUserList = res.data;
  164. // this.teamUserList.forEach((l,i)=>{
  165. // l.categoryName = this.categoryData.find(c=>c.id==l.category)?.name;
  166. // this.$set(this.teamUserList[i],'select',false);
  167. // })
  168. // this.teamUserShow = true;
  169. // })
  170. },
  171. handleSelectUser(index){
  172. this.$set(this.teamUserList[index],'select',!this.teamUserList[index].select);
  173. this.dealNumAndAll()
  174. },
  175. handleClose(){
  176. this.teamUserShow = false;
  177. this.initSelectUserComponent();
  178. },
  179. changeAll(selected){
  180. this.teamUserList.forEach((l,i)=>{
  181. this.$set(this.teamUserList[i],'select',selected);
  182. })
  183. this.$refs.selectTeamUser.selectNums = this.teamUserList.filter(l=>l.select).length;
  184. },
  185. dealNumAndAll(){
  186. const selectnum = this.teamUserList.filter(l=>l.select).length;
  187. this.$refs.selectTeamUser.selectNums = selectnum;
  188. this.$refs.selectTeamUser.selectAll = selectnum === this.teamUserList.length;
  189. },
  190. showReportUnavailable(kind){
  191. this.reportReminderMsg = kind === 'background'
  192. ? '背景报告需配合团队版评估数据生成'
  193. : '教练报告需要配合团队版评估数据生成';
  194. this.reportReminderShow = true;
  195. },
  196. canEditEvaluation(item){
  197. return canEditEvaluation(item);
  198. },
  199. openEvaluation(item){
  200. if(!this.canEditEvaluation(item)) return;
  201. this.evaluationReportId = item.reportId;
  202. this.evaluationSubmitting = false;
  203. this.evaluationVisible = true;
  204. this.$api.get('/core/report/evaluation/'+item.reportId).then(({data:res})=>{
  205. if(res.code!==0){ this.closeEvaluation(); return this.$showToast(res.msg); }
  206. if(res.data && res.data.processing){ this.closeEvaluation(); return this.$showToast('评估分析正在重新生成中,请稍候'); }
  207. this.evaluationForm = normalizeEvaluationForm(res.data || {});
  208. })
  209. },
  210. closeEvaluation(){
  211. if(this.evaluationSubmitting) return;
  212. this.evaluationVisible = false;
  213. this.evaluationReportId = '';
  214. },
  215. submitEvaluation(){
  216. if(this.evaluationSubmitting) return;
  217. if(!this.evaluationForm.overallAssessment.trim()) return this.$showToast('请输入总体评估结果');
  218. if(!this.evaluationForm.developmentAdvice.trim()) return this.$showToast('请输入团队发展建议');
  219. this.evaluationSubmitting = true;
  220. this.$api.post('/core/report/evaluation/'+this.evaluationReportId, this.evaluationForm).then(({data:res})=>{
  221. if(res.code!==0) return this.$showToast(res.msg);
  222. this.evaluationVisible = false;
  223. this.$emit('reSendReport');
  224. this.reportReminderMsg = EVALUATION_PROCESSING_PROMPT;
  225. this.reportReminderShow = true;
  226. }).finally(()=>{ this.evaluationSubmitting = false; });
  227. },
  228. sendConfirm(){
  229. if(this.teamUserList.filter(t=>t.select).length===0) return this.$showToast('请选择要发送的用户')
  230. if(this.sendType===1&&(!uni.getStorageSync('userInfo')||!JSON.parse(uni.getStorageSync('userInfo')).email)) return this.$showModal('发送前请先在我的-设置-用户信息中绑定邮箱')
  231. if(this.sendType===1){
  232. this.handleClose();
  233. } else if(this.sendType===2){
  234. const item = JSON.parse(JSON.stringify(this.selectItem));
  235. uni.showModal({
  236. title:'发送报告',
  237. content:`是否确认将“${item.reportName||''}”发送给团队成员`,
  238. confirmText:'确认',
  239. success: (res) => {
  240. if(res.confirm){
  241. this.$api.post(`/core/report/sendReport`,{
  242. reportId:item.reportId,
  243. memberList:this.teamUserList.filter(t=>t.select).map(t=>({id:t.id,mobile:t.mobile,category:t.category}))
  244. }).then(({data:res})=>{
  245. if(res.code!==0) return this.$showToast(res.msg)
  246. this.handleClose();
  247. uni.navigateTo({
  248. url:'/pagesHome/sendResult?result='+res.code
  249. })
  250. })
  251. }
  252. }
  253. })
  254. }
  255. },
  256. initSelectUserComponent(){
  257. this.$refs.selectTeamUser.selectNums = 0;
  258. this.$refs.selectTeamUser.selectAll = false;
  259. },
  260. reviewArtifact(item, kind){
  261. const url = reportArtifactUrl(item, kind)
  262. if(!url) return this.$showToast('该报告尚未生成,请稍后再试。')
  263. const { isPC, isMobile, platform } = getPlatformInfo();
  264. console.log(platform,'platform');//android
  265. if (isPC) {
  266. this.$showModal('请在手机端该微信小程序中查看报告')
  267. } else if (isMobile||platform==='devtools') {
  268. this.openReportDocument(url)
  269. // uni.navigateTo({
  270. // url:'/pages/webView?src='+item.fileUrl
  271. // })
  272. }
  273. },
  274. async openReportDocument(fileUrl) {
  275. try {
  276. // 1. 显示加载提示
  277. uni.showLoading({ title: '加载中...' });
  278. // 2. 先下载PDF文件到本地(解决安卓直接预览网络文件慢的问题)
  279. const downloadRes = await uni.downloadFile({
  280. url: reportArtifactDownloadUrl(fileUrl),
  281. // 可选:设置超时时间,避免长时间等待
  282. timeout: 30000
  283. });
  284. const dres = downloadRes[1]||null;
  285. if (dres.statusCode !== 200) {
  286. uni.hideLoading();
  287. uni.showToast({ title: '文件下载失败', icon: 'none' });
  288. return;
  289. }
  290. // 3. 按实际扩展名使用微信原生文档预览
  291. const openRes = await uni.openDocument({
  292. filePath: dres.tempFilePath,
  293. fileType: this.documentType(fileUrl),
  294. showMenu: false
  295. });
  296. uni.hideLoading();
  297. } catch (error) {
  298. uni.hideLoading();
  299. uni.showToast({ title: '加载失败,请重试', icon: 'none' });
  300. }
  301. },
  302. documentType(fileUrl) {
  303. const path = (fileUrl || '').split('?')[0].toLowerCase()
  304. return path.endsWith('.docx') ? 'docx' : 'pdf'
  305. },
  306. askReport(item){
  307. uni.navigateTo({
  308. url:`/pages/reportAsk?pdfUrl=${item.fileUrl}&fileName=${this.typeCfg[item.type]||'个人版'}-${item.reportName}`
  309. })
  310. },
  311. async sendReport(item){
  312. if(!item.fileUrl) return this.$showModal('该报告尚未生成PDF文件,请等待生成后发送。')
  313. this.sendType = 2;
  314. this.$refs.selectTeamUser.title = '发送报告'
  315. this.selectItem = item;
  316. await this.getUserCategoryData()
  317. this.$api.get(`/core/member/listByQueTeamId/${item.teamQueId}`).then(({data:res})=>{
  318. if(res.code!==0) return this.$showToast(res.msg)
  319. this.teamUserList = res.data;
  320. this.teamUserList.forEach((l,i)=>{
  321. l.categoryName = this.categoryData.find(c=>c.id==l.category)?.name;
  322. this.$set(this.teamUserList[i],'select',false);
  323. })
  324. this.teamUserShow = true;
  325. })
  326. },
  327. reSendReport(item){
  328. this.$api.get(`/core/report/regenerateReport/${item.reportId}`).then(({data:res})=>{
  329. if(res.code!==0) return this.$showToast(res.msg)
  330. this.$emit('reSendReport')
  331. })
  332. }
  333. }
  334. }
  335. </script>
  336. <style scoped lang="scss">
  337. @import '../../../static/_asset-config';
  338. .gbox{
  339. height: 100%;
  340. &-item{
  341. margin-top: 20rpx;
  342. background: #FFFFFF;
  343. border-radius: 24rpx;
  344. padding: 40rpx 20rpx 20rpx;
  345. &-top{
  346. &-type{
  347. width: 98rpx;
  348. height: 36rpx;
  349. padding: 2rpx 13rpx;
  350. box-sizing: border-box;
  351. background: #FFF7DC;
  352. border-radius: 6rpx;
  353. font-family: PingFangSC, PingFang SC;
  354. font-weight: 400;
  355. font-size: 24rpx;
  356. color: #BA9B31;
  357. line-height: 33rpx;
  358. }
  359. &-title{
  360. width: calc(100% - 98rpx);
  361. font-family: PingFang-SC, PingFang-SC;
  362. font-weight: bold;
  363. font-size: 32rpx;
  364. color: #002846;
  365. line-height: 32rpx;
  366. text-indent: 17rpx;
  367. }
  368. }
  369. &-ps{
  370. margin-top: 20rpx;
  371. background: #F5F8FA;
  372. border-radius: 5px;
  373. padding: 20rpx 20rpx 30rpx;
  374. position: relative;
  375. }
  376. &-p{
  377. margin-top: 32rpx;
  378. &:first-child{
  379. margin-top: 0;
  380. }
  381. &>view{
  382. font-family: PingFangSC, PingFang SC;
  383. font-weight: 400;
  384. font-size: 24rpx;
  385. color: #667E90;
  386. line-height: 24rpx;
  387. }
  388. &-left{
  389. width: 134rpx;
  390. }
  391. &-right{
  392. width: calc(100% - 134rpx);
  393. }
  394. &-progress{
  395. width: calc(100% - 134rpx);
  396. &-l{
  397. width: 410rpx;
  398. height: 12rpx;
  399. background: #F0F2F8;
  400. border-radius: 6rpx;
  401. position: relative;
  402. &-current{
  403. height: 12rpx;
  404. background: #FFDF73;
  405. border-radius: 6rpx;
  406. position: absolute;
  407. left: 0;
  408. top: 0;
  409. }
  410. }
  411. &-r{
  412. width: calc(100% - 410rpx);
  413. font-family: PingFangSC, PingFang SC;
  414. font-weight: 400;
  415. font-size: 24rpx;
  416. color: #002846;
  417. line-height: 24rpx;
  418. text-align: right;
  419. }
  420. }
  421. }
  422. &-ask{
  423. width: 64rpx;
  424. height: 64rpx;
  425. position: absolute;
  426. right: 20rpx;
  427. bottom: 42rpx;
  428. }
  429. &-bottom{
  430. margin-top: 20rpx;
  431. overflow: hidden;
  432. &-rightbox{
  433. flex: 1;
  434. margin-left: -35rpx;
  435. justify-content: flex-end;
  436. }
  437. &-left{
  438. &>view{
  439. font-family: PingFangSC, PingFang SC;
  440. font-weight: 400;
  441. font-size: 24rpx;
  442. color: #667E90;
  443. line-height: 24rpx;
  444. }
  445. }
  446. &-right{
  447. flex: 1;
  448. width: 0;
  449. height: 64rpx;
  450. padding: 0 8rpx;
  451. text-align: center;
  452. box-sizing: border-box;
  453. line-height: 64rpx;
  454. white-space: nowrap;
  455. background: linear-gradient( 90deg, #33A7A7 0%, #4DB2B2 100%);
  456. border-radius: 20rpx;
  457. font-family: PingFang-SC, PingFang-SC;
  458. font-size: 24rpx;
  459. color: #FFFFFF;
  460. margin-left: 35rpx;
  461. &.report-button-disabled{
  462. background: #D5DDE2;
  463. color: #FFFFFF;
  464. }
  465. }
  466. }
  467. }
  468. .kind-reminder{
  469. position: fixed;
  470. left: 0;
  471. right: 0;
  472. top: 0;
  473. bottom: 0;
  474. z-index: 1000;
  475. background: rgba(0, 0, 0, .6);
  476. .kr-box{
  477. width: calc(100% - 100rpx);
  478. padding: 54rpx 60rpx 48rpx;
  479. box-sizing: border-box;
  480. background: asset-image('remind_bg.png') no-repeat;
  481. background-size: 100% 100%;
  482. position: relative;
  483. image{
  484. width: 48rpx;
  485. height: 48rpx;
  486. position: absolute;
  487. top: 40rpx;
  488. right: 30rpx;
  489. }
  490. .kr-title{
  491. font-family: PingFang-SC, PingFang-SC;
  492. font-weight: bold;
  493. font-size: 36rpx;
  494. color: #002846;
  495. line-height: 56rpx;
  496. text-align: center;
  497. }
  498. .kr-p{
  499. font-family: PingFangSC, PingFang SC;
  500. font-weight: 400;
  501. font-size: 30rpx;
  502. color: #002846;
  503. line-height: 48rpx;
  504. text-align: center;
  505. span{
  506. font-weight: bold;
  507. margin: 0 10rpx;
  508. }
  509. }
  510. .kr-btn{
  511. width: calc(100% - 80rpx);
  512. height: 88rpx;
  513. background: linear-gradient( 90deg, #33A7A7 0%, #64BBBB 100%);;
  514. border-radius: 44rpx;
  515. font-family: PingFang-SC, PingFang-SC;
  516. font-weight: bold;
  517. font-size: 32rpx;
  518. color: #FFFFFF;
  519. line-height: 88rpx;
  520. text-align: center;
  521. letter-spacing: 2rpx;
  522. margin-top: 66rpx;
  523. }
  524. }
  525. }
  526. }
  527. .evaluation-mask{position:fixed;left:0;right:0;top:0;bottom:0;z-index:30;background:rgba(0,0,0,.45);}
  528. .evaluation-box{width:650rpx;background:#fff;border-radius:16rpx;padding:34rpx 28rpx 26rpx;box-sizing:border-box;}
  529. .evaluation-label{font-size:28rpx;font-weight:bold;color:#333;margin:18rpx 0 12rpx;}
  530. .evaluation-textarea{width:100%;height:180rpx;box-sizing:border-box;background:#f7f7f7;border-radius:8rpx;padding:18rpx;font-size:26rpx;color:#333;}
  531. .evaluation-actions{display:flex;justify-content:flex-end;gap:20rpx;margin-top:28rpx;}
  532. .evaluation-cancel,.evaluation-submit{padding:18rpx 28rpx;border-radius:10rpx;font-size:28rpx;}
  533. .evaluation-cancel{color:#666;background:#f2f2f2;}.evaluation-submit{color:#fff;background:#833478;}.evaluation-submit.disabled{opacity:.5;}
  534. </style>