pdfZyb.vue 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106
  1. <template>
  2. <view class="page-wrappe">
  3. <cus-header title=' ' bgColor="transparent"></cus-header>
  4. <view id="pdfContainer" class="pdf-container" :style="{'transform':'scale('+scale+')', 'height': containerScaledHeight + 'px'}" v-if="reportData">
  5. <!-- 封面 -->
  6. <view class="cd_box fm2 adffc" style="border: none;margin-top: 20px;height: 868px;">
  7. <img class="fm2-logo" src="https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/fm_logo.png">
  8. <img class="fm2-logo2" src="https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/fm_logo2.png">
  9. <img class="fm2-perill" src="https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/fm_perill.png">
  10. <view class="fm2-line"></view>
  11. <view class="fm2-p" style="margin-top: 4px;font-size: 38px;">团队发展动态评估报告(团队版)</view>
  12. <view class="fm2-texts adf">
  13. <view class="fm2-texts-pre adf" style="margin-top: 6px;">
  14. <view class="fm2-texts-pre-span">客户名称:</view>
  15. <view class="fm2-texts-pre-val">{{ reportData.teamInfo.enterpriseName||'' }}</view>
  16. </view>
  17. <view class="fm2-texts-pre adf" style="margin-top: 6px;">
  18. <view class="fm2-texts-pre-span">团队名称:</view>
  19. <view class="fm2-texts-pre-val">{{ reportData.teamInfo.teamName||'' }}</view>
  20. </view>
  21. <view class="fm2-texts-pre adf" style="margin-top: 6px;">
  22. <view class="fm2-texts-pre-span">团队职能类型:</view>
  23. <view class="fm2-texts-pre-val">{{ reportData.teamInfo.functionName||'' }}</view>
  24. </view>
  25. <view class="fm2-texts-pre adf" style="margin-top: 6px;">
  26. <view class="fm2-texts-pre-span">团队模式类型:</view>
  27. <view class="fm2-texts-pre-val">{{ reportData.teamInfo.organizationName||'' }}</view>
  28. </view>
  29. <view class="fm2-texts-pre adf" style="margin-top: 6px;">
  30. <view class="fm2-texts-pre-span">评估发起人:</view>
  31. <view class="fm2-texts-pre-val">{{ reportData.teamInfo.initiator||'' }}</view>
  32. </view>
  33. <view class="fm2-texts-pre adf" style="margin-top: 6px;">
  34. <view class="fm2-texts-pre-span">报告生成时间:</view>
  35. <view class="fm2-texts-pre-val">{{ reportData.teamInfo.reportDate||'' }}</view>
  36. </view>
  37. </view>
  38. <view class="fm2-tip" style="margin-top: 12px;">免责声明:本团队测评报告基于您方团队填写的测评数据及相关信息生成,深圳创衡管理顾问有限公司不对报告内容的真实性、准确性和完整性负责。本报告仅供您了解团队情况、优化管理决策提供参考。报告结论不构成任何法律、商业或投资建议,亦不替代专业咨询意见。我方不对因使用本报告内容而产生的任何直接或间接损失承担责任。</view>
  39. </view>
  40. <!-- 介绍 -->
  41. <view class="cd_box" style="border: none">
  42. <view class="v2-top adfacjb" :style="{'background-image':'url(https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/'+'intro'+'_title_bg.png)','padding':'0 8px 0 24px'}">
  43. <view class="vt-left" style="color: #FFFFFF;">PERILL模型介绍</view>
  44. <view class="vt-right">团队发展动态评估报告(团队版) </view>
  45. </view>
  46. <view class="v2-box">
  47. <img class="vb-img1" :src="'https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/intro_box_img1.png'">
  48. <view class="v2-p">PERILL团队发展动态评估源于团队教练辅导领域的先驱、管理思维与团队绩效领域的权威、全球顶尖团队教练David Clutterbuck教授及其团队通过深入研究,提炼出影响团队绩效的140多个基于实证的因素,整合而成的团队评估和提升工具-PERILL模型。</view>
  49. <view class="v2-p" style="margin-top: 8px;">创衡国际基于10多年来在全球与200多家具有前瞻性的国际公司以及国内具有行业代表性公司的合作经验,结合CCMI的PERILL评估工具,在中国推出的团队发展动态评估系统,旨在帮助团队更全面、更有效地从六个维度评估团队的发展现状,为支持团队成为高价值创造团队提供全景式的客观评估。</view>
  50. <view class="v2-p" style="margin-top: 8px;">PERILL团队发展动态评估(团队版)的主体内容由<span>{{ reportData.teamInfo.questionCount }}</span>个关于团队的描述组成。</view>
  51. </view>
  52. <view class="v2-six">
  53. <view class="vsix-title">PERILL六大纬度</view>
  54. <view class="vsix-p">PERILL评估提供了一个复杂的团队系统概览,它并非针对孤立的问题,也不是简单的优缺点,而是着眼于团队系统的复杂性。它通过6个影响因素(如下所述)提出问题,以揭示团队系统各要素之间的联系,以及这些联系如何影响团队的高效运作能力。</view>
  55. <view class="vsix-boxs">
  56. <view class="vsb adfac" v-for="(item,index) in sixWd" :key="index">
  57. <img class="vsb-img" :src="item.img"/>
  58. <view class="vsb-right">
  59. <view class="vsbr-top adfac">
  60. <view class="vsbrt-type" :style="{'background':item.color}">{{ item.type }}</view>
  61. <view class="vsbrt-title" :style="{'color':item.color}">{{ item.title }}</view>
  62. </view>
  63. <view class="vsbr-desc">{{ item.desc }}</view>
  64. </view>
  65. </view>
  66. </view>
  67. </view>
  68. </view>
  69. <!-- 总体评估分析 -->
  70. <view class="cd_box adffc" style="border: none;">
  71. <view class="v2-top adfacjb" :style="{'background-image':'url(https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/'+'intro'+'_top_title_bg2.png)'}">
  72. <view class="vt-left">总体评估分析</view>
  73. <view class="vt-right">团队发展动态评估报告(团队版) </view>
  74. </view>
  75. <view class="v2-box" style="border: 1px solid #33A7A7;">
  76. <img class="vb-img1" :src="'https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/intro_box_img1.png'">
  77. <view class="v2-p2">报告的核心是PERILL评估分析下的整体团队表现。这包括团队在PERILL模型每个关键要素上的综合得分,通过结合得分和置信指数,我们能够展示出高功能和低功能领域。</view>
  78. <view class="v2-p2" style="margin-top: 2px;font-weight: 400;color: #002846;font-size: 10px;">下面图中的位置标记显示了团队按影响力划分的总分。</view>
  79. <view class="vb-category">
  80. <view class="vbc-pre adfac">
  81. <view class="vbcp-yuan y1"></view>
  82. <view class="vbcp-text">团队领导者 Team Leader</view>
  83. </view>
  84. <view class="vbc-pre adfac">
  85. <view class="vbcp-yuan y2"></view>
  86. <view class="vbcp-text">团队成员 Team Member</view>
  87. </view>
  88. <view class="vbc-pre adfac">
  89. <view class="vbcp-yuan y3"></view>
  90. <view class="vbcp-text">利益相关方 Stakeholder</view>
  91. </view>
  92. <view class="vbc-pre adfac">
  93. <view class="vbcp-yuan y4"></view>
  94. <view class="vbcp-text">赞助人/出资人 Sponsor</view>
  95. </view>
  96. </view>
  97. <view style="width:360px;height:360px;margin: 0 auto;" class="pdfEchart">
  98. <l-echart ref="ztzdfxRef" :canvas2d="true" @finished="initZtzdfxChart" style="width: 100%;height: 100%;"></l-echart>
  99. </view>
  100. <view class="v2b-tip-title" style="color: #002846;">注: 关于认同度、重要性分、影响力分的定义</view>
  101. <view class="v2b-tip-memo">
  102. a.“认同度分”,指标逻辑为团队全体对当前主题所对应各问卷题目认同度评分的均值,用来表征团队对这一主题问卷所陈述内容与团队情况的相符合程度的平均认知,最高分:5分,最低分:0分;<br>
  103. b.“重要分”,指标逻辑为根据团队全体对当前主题所对应各问卷题目重要性评分的均值,用来表征团队对这一主题问卷所陈述内容对于团队重要性的平均认知,最高分:5分,最低分:0分;<br>
  104. c.“影响力分”,指标逻辑为综合团队全体对当前主题所对应各问卷题目的认同度与重要性的评分数据测算而来,用来表征团队对这一主题问卷所陈述内容对于团队的影响力水平,最高分:5分,最低分:0分;</view>
  105. <view class="v2b-title">评估结果</view>
  106. <view class="v2b-p" v-html="renderMarkdown(reportData.totalDiagnosticOutput||'')"></view>
  107. </view>
  108. </view>
  109. <!-- 总体评估分析 评估建议-->
  110. <view class="cd_box adffc" style="border: none;">
  111. <view class="v2-top adfacjb" :style="{'background-image':'url(https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/intro_top_title_bg2.png)'}">
  112. <view class="vt-left">总体评估分析</view>
  113. <view class="vt-right">团队发展动态评估报告(团队版) </view>
  114. </view>
  115. <view class="v2-box" style="border: 1px solid #33A7A7;">
  116. <img class="vb-img1" :src="'https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/intro_box_img1.png'">
  117. <view class="v2b-title" style="margin-top: 0;">评估建议</view>
  118. <view class="v2b-p" v-html="renderMarkdown(reportData.totalDiagnosisSuggest||'')"></view>
  119. <view class="v2b-alert">注:团队发展本身就不是单一答案的过程,PERILL AI教练根据创衡独创团队教练方法论,可以从不同角度解读团队,发现那些隐藏的机会点。多元建议代表多元可能。您可以尝试再次生成评估报告,探索更多可能性。</view>
  120. </view>
  121. </view>
  122. <!-- 多维度 -->
  123. <canvas type="2d" canvas-id="score-canvas" id="score-canvas" class="offscreen-canvas"></canvas>
  124. <template v-if="reportData&&reportData.dimensionAnalysis&&reportData.dimensionAnalysis.length">
  125. <block v-for="(item,index) in reportData.dimensionAnalysis" :key="index">
  126. <view class="cd_box adffc" style="border: none;">
  127. <view class="v2-top adfacjb" :style="{'background-image':'url(https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/'+(typeDict[item.title]||'zzdj')+'_title_bg.png)','padding':'0 8px 0 24px'}">
  128. <view class="vt-left" :class="{'black':(item.title=='人际关系'||item.title=='学习')}">{{ item.title }}</view>
  129. <view class="vt-right">团队发展动态评估报告(团队版) </view>
  130. </view>
  131. <view class="v2-box" :style="{'border':'none','padding':0}">
  132. <view class="v2-p2">{{ item.desc }}</view>
  133. <view class="vb-table adf" :style="{'border':'1px solid '+item.bordercolor,'margin-top':'12px'}">
  134. <view class="vbt2-left">
  135. <view class="vbt2l-th adf" :style="{'background':item.thbgcolor,'color':item.thtextcolor}">
  136. <view class="vbt2l-th-title adfac">主题</view>
  137. <view class="vbt2l-th-score adfacjc">影响力分</view>
  138. </view>
  139. <view class="vbt2l-pre" v-for="(ss,si) in item.scoreSpreads" :key="si" :style="{'border-bottom':'1px solid '+item.bordercolor}">
  140. <view class="vbt2l-pre-l adffc">
  141. <view class="vbt2l-pre-l-title" :style="{'color':item.titlecolor}">{{ ss.theme||'' }}</view>
  142. <view class="vbt2l-pre-l-tip">{{ ss.question||'' }}</view>
  143. </view>
  144. <view class="vbt2l-pre-r adfacjc">{{ ss.regular.avgScore||0 }}</view>
  145. </view>
  146. </view>
  147. <view class="vbt2-right adffc">
  148. <view class="vbt2r-th adf" :style="{'background':item.thbgcolor,'color':item.thtextcolor}">
  149. <view class="vbt2r-pre adfacjc">认同度分</view>
  150. <view class="vbt2r-pre adfacjc">重要性分</view>
  151. </view>
  152. <view class="vbt2r-tb adf">
  153. <view class="vbt2r-tb-l adffc">
  154. <view class="vbt2r-tb-l-pre red adfac" v-for="(ss,si) in item.scoreSpreads" :key="si" :style="{'border-bottom':'1px solid '+item.bordercolor}">
  155. <view class="vbt2r-tb-l-pre-bg"></view>
  156. <view class="vbt2r-tb-l-pre-zzt red" :style="{'width':(ss.regular.avgAgreement/6*100)+'%'}">
  157. <view class="vbt2r-tb-l-pre-zzt-numl">{{ ss.regular.avgAgreement||0 }}</view>
  158. </view>
  159. </view>
  160. </view>
  161. <view class="vbt2r-tb-l">
  162. <view class="vbt2r-tb-l-pre green adfac" v-for="(ss,si) in item.scoreSpreads" :key="si" :style="{'border-bottom':'1px solid '+item.bordercolor}">
  163. <view class="vbt2r-tb-l-pre-bg r"></view>
  164. <view class="vbt2r-tb-l-pre-zzt green" :style="{'width':(ss.regular.avgVital/6*100)+'%'}">
  165. <view class="vbt2r-tb-l-pre-zzt-numr">{{ ss.regular.avgVital||0 }}</view>
  166. </view>
  167. </view>
  168. </view>
  169. </view>
  170. </view>
  171. </view>
  172. <view class="v2-subp">评估结果</view>
  173. <view class="v2-p" style="margin-top: 6px;" v-html="renderMarkdown2(item.diagnosisOutput||'')"></view>
  174. </view>
  175. </view>
  176. <view class="cd_box adffc" style="border: none;">
  177. <view class="v2-top adfacjb" :style="{'background-image':'url(https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/'+(typeDict[item.title]||'zzdj')+'_title_bg.png)','padding':'0 8px 0 24px'}">
  178. <view class="vt-left" :class="{'black':(item.title=='人际关系'||item.title=='学习')}">{{ item.title }}</view>
  179. <view class="vt-right">团队发展动态评估报告(团队版) </view>
  180. </view>
  181. <view class="v2-box" :style="{'border':'none','padding':0}">
  182. <view class="v2-p" style="margin-top: 6px;" v-html="renderMarkdown2(item.diagnosisSuggest||'')"></view>
  183. </view>
  184. </view>
  185. </block>
  186. </template>
  187. <!-- 封底页 -->
  188. <view class="cd_box fdy" style="border: none;">
  189. <view class="v2-top adfacjb" :style="{'background-image':'url(https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/fdy_title_bg.png)'}">
  190. <view class="vt-left" style="color: #FFFFFF;">结言</view>
  191. <view class="vt-right">团队发展动态评估报告(团队版)</view>
  192. </view>
  193. <view class="fdy-title">致团队:从评估到行动,开启团队进化跃迁之旅</view>
  194. <view class="fdy-p">当您阅读至此,首先恭喜您和团队伙伴共同完成了一次完整、专业的团队评估!这标志着团队发展旅程中一个充满可能性的新起点。</view>
  195. <view class="fdy-p">作为团队教练,我们希望与您分享PERILL评估的科学价值,并为您描绘从系统诊断到协同进化的路径。 </view>
  196. <view class="fdy-subtitle">一、这份报告为团队带来了什么? </view>
  197. <view class="fdy-p" style="margin-top: 6px;">这份报告整合了团队内部(如:团队领导者与成员)与外部(如:客户、合作伙伴、赞助人)的多方视角,为您呈现团队作为一个“系统”的真实状态。它帮助团队:</view>
  198. <view class="fdy-p" style="margin-top: 0px;text-indent: 24px;">· 看见惯性模式与盲点——那些日常被忽略的协作断点、沟通落差与共识偏差。</view>
  199. <view class="fdy-p" style="margin-top: 0px;text-indent: 24px;">· 获得立体画像而非主观印象——基于数据而非直觉,客观定位团队在六大维度的表现。</view>
  200. <view class="fdy-p" style="margin-top: 0px;text-indent: 24px;">· 识别高杠杆改进区——不只列出问题,更揭示“从哪里改变,能带动全局提升”。</view>
  201. <view class="fdy-subtitle">二、PERILL 评估的科学洞察:不只是分数,更是系统思维</view>
  202. <view class="fdy-p" style="margin-top: 6px;">PERILL源自David Clutterbuck教授数十年的实证研究,其价值远超过一份评分报告。它帮助团队实现:</view>
  203. <view class="fdy-subtitle2">从“凭感觉管理”到“系统诊断”</view>
  204. <view class="fdy-p" style="margin-top: 6px;text-indent: 24px;">传统团队讨论容易陷入“我觉得”“你认为”的主观争论。PERILL提供共同语言与结构框架,让对话基于事实,聚焦系统障碍而非个人指责。</view>
  205. <view class="fdy-subtitle2">揭示“维度关联”,避免单一解药</view>
  206. <view class="fdy-p" style="margin-top: 6px;text-indent: 24px;">PERILL 揭示六大维度间的动态关联,例如:</view>
  207. <view class="fdy-p" style="margin-top: 0px;text-indent: 24px;">· 人际关系(R)紧张 → 抑制信息共享 → 影响学习(L)与创新 → 动摇宗旨认同(P)。</view>
  208. <view class="fdy-p" style="margin-top: 0px;text-indent: 24px;">· 改进关键点:可能只需提升心理安全(R),即可激活学习(L)与协同(I)。</view>
  209. <view class="fdy-subtitle2">赋能团队成为“自我进化系统”</view>
  210. <view class="fdy-p" style="margin-top: 6px;text-indent: 24px;">评估过程本身就是一次深度对话与反思。通过共同诊断、共同制定行动,团队主人翁意识被激发,变革可持续性远高于外部强加的“方案”。</view>
  211. <view class="fdy-subtitle">三、接下来,团队可以如何行动?</view>
  212. <view class="fdy-p" style="margin-top: 8px;">报告只是开始,真正的进化发生在对话与实践中。可以依照团队的需求去安排以下内容</view>
  213. <view class="fdy-subtitle2">1. 召开一次“报告解读与共识工作坊”</view>
  214. <view class="fdy-p" style="margin-top: 6px;text-indent: 24px;">邀请专业团队教练与团队一起开放地探讨评估结果,理解数据背后的含义,并就<span style="font-weight: bold;">“我们接下来共同聚焦哪1-2个维度?”</span>达成共识。这个过程本身,就是一次强有力的团队对话与承诺建立。</view>
  215. <view class="fdy-subtitle2">2. 针对关键挑战,设计专题行动实验</view>
  216. <view class="fdy-subtitle2">3. 如需深度支持,可考虑引入团队教练辅导助力团队发展</view>
  217. <view class="fdy-p">每一支高价值团队,都始于对现状清醒的认知与共同进化的勇气。PERILL为团队提供了导航仪与路线图——下一步,行动在您们手中。</view>
  218. <view class="fdy-p">PERILL不止于评估,更在于赋能。期待与您和团队同行,见证未来的更多可能。</view>
  219. <view class="fdy-p" style="margin-top: 41px;">具体需求可联系您的团队教练 或 扫码联系“⼤衡同学”。</view>
  220. <view class="fdy-code adfacjc"><img class="fdy-code-team" :src="'https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/fdy_code.png'"></view>
  221. </view>
  222. </view>
  223. <view class="pdf_btn" @click="createPdf">生成PDF</view>
  224. <view class="loading adffcacjc" v-if="isLoading">
  225. <u-loading-icon text="数据加载中,请耐心等待" :vertical="true" size="72" color="#FFFFFF" textSize="32" textColor="#FFFFFF"></u-loading-icon>
  226. </view>
  227. </view>
  228. </template>
  229. <script name="">
  230. import { BaseApi } from '@/http/baseApi.js';
  231. import * as echarts from '@/pagesHome/components/lime-echart/static/echarts.min.js'
  232. import lEchart from '@/pagesHome/components/lime-echart/components/l-echart/l-echart.vue'
  233. export default {
  234. name: 'ZtzdfxChart',
  235. components:{ lEchart },
  236. data() {
  237. return {
  238. isLoading: true,
  239. reportId:'',
  240. reportData: null,
  241. isChartReady: false,
  242. scale:1,
  243. originalContainerHeight: 0,
  244. containerScaledHeight: 'auto',
  245. typeDict: {
  246. '宗旨与动机':'zzdj',
  247. '外部流程、系统与结构':'wbjg',
  248. '人际关系':'rjgx',
  249. '内部流程、系统与结构':'nbjg',
  250. '学习':'xx',
  251. '领导力':'ldl'
  252. },
  253. sixWd: [
  254. {
  255. img:'https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/intro_img_p.png',
  256. type:'P',
  257. title:'宗旨与动机',
  258. desc:'指团队共享的目的和存在的意义, 包含对共同的愿景,目标和优先级的清晰度。',
  259. color:'#761E6A'
  260. },
  261. {
  262. img:'https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/intro_img_e.png',
  263. type:'E',
  264. title:'外部流程、系统与结构',
  265. desc:'指团队与其外部利益相关者 - 客户,供应商,股东,组织内的上级及其他团队的互动关系和协作机制。',
  266. color:'#009191'
  267. },
  268. {
  269. img:'https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/intro_img_r.png',
  270. type:'R',
  271. title:'人际关系',
  272. desc:'指团队成员共同工作时的关系状态–他们是否相互尊重和信任对方的能力,是否足够心理安全以能够坦诚沟通,是否真正关心彼此的幸福感等。',
  273. color:'#FFD750'
  274. },
  275. {
  276. img:'https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/intro_img_i.png',
  277. type:'I',
  278. title:'内部流程、系统与结构',
  279. desc:'指团队如何管理工作任务和流程(包括但不限于会议、任务分配和团队情绪等),互相支持以及高质量的沟通和决策。',
  280. color:'#4EB2B2'
  281. },
  282. {
  283. img:'https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/intro_img_l.png',
  284. type:'L',
  285. title:'学习',
  286. desc:'指团队如何应对多变的环境和保持持续的进步和成⻓,能够从经验中反思、提炼并应用知识的能力。',
  287. color:'#AFCDF5'
  288. },
  289. {
  290. img:'https://gitee.com/hw_0302/chuang-heng-wechat-images/raw/master/versionTwo/intro_img_l2.png',
  291. type:'L',
  292. title:'领导力',
  293. desc:'指团队认为需要怎样的领导行为能够让他们,作为个人或者团队做到最好。团队可以和他们的领导者讨论他们的责任及承担方式,以帮助领导者成为他们需要的领导者。',
  294. color:'#002846'
  295. }
  296. ],
  297. pdfImages:[],
  298. };
  299. },
  300. onLoad(options) {
  301. this.reportId = options.reportId;
  302. this.getReportData();
  303. },
  304. mounted() {
  305. this.calculateScaleAndPosition();
  306. uni.onWindowResize(() => {
  307. this.calculateScaleAndPosition();
  308. });
  309. },
  310. methods: {
  311. renderMarkdown(val) {
  312. if(!val) return '';
  313. val = '<span style="padding-left:4ch;"></span>'+val;
  314. val = this.formattedText2(val);
  315. val = val.replaceAll('\n','<br><span style="padding-left:4ch;"></span>')
  316. val = this.formattedText(val);
  317. return val
  318. },
  319. renderMarkdown2(val) {
  320. if(!val) return '';
  321. val = '<span style="padding-left:4ch;"></span>'+val;
  322. val = val.replaceAll('\n','<br><span style="padding-left:4ch;"></span>')
  323. return val
  324. },
  325. formattedText(val) {
  326. // 正则表达式:匹配**包裹的内容
  327. // /\*\*(.*?)\*\*/g 解析:
  328. // \*\* 匹配两个星号(*需要转义)
  329. // (.*?) 非贪婪匹配任意字符(括号捕获匹配的内容)
  330. // g 全局匹配(替换所有符合条件的内容)
  331. const regex = /\*\*(.*?)\*\*/g
  332. // 替换为<b>标签,$1表示正则中括号捕获的内容
  333. return val.replace(regex, '<b>$1</b>')
  334. },
  335. formattedText2(val) {
  336. // 正则解析:
  337. // #{1,6} 匹配1到6个#
  338. // (.*?) 非贪婪捕获#后、空格前的任意文字(核心要保留的内容)
  339. // \s 匹配后面的空格(注意是单个空格,若要兼容多个空格可改为\s+)
  340. // g 全局匹配,替换所有符合条件的内容
  341. const regex = /#{1,6}\s*(.+?)\s*\n/g;
  342. // 替换规则:将匹配到的 "#文字 " / "##文字 " / "###文字 " 转为 "<b>文字</b>"
  343. return val.replace(regex, '<b>$1</b><br>')
  344. },
  345. getReportData(){
  346. this.$api.get(`/core/report/previewReport/${this.reportId}`).then(async ({data:res})=>{
  347. if(res.code!==0) return this.$showToast(res.msg)
  348. this.reportData = res.data;
  349. const tempDimensionAnalysis = [
  350. {
  351. title:'宗旨与动机',thbgcolor:'#761E6A',thtextcolor:'#FFFFFF',titlecolor:'#761E6A',dimensionCode:"purpose",bordercolor:'#E4D2E1',
  352. desc:`「宗旨与动机」维度,我们旨在探究团队是否有清晰的存在理由和明确的方向,能够激发团队成员的动力并吸引他们的想象力,以及个人与集体的身份认同是否围绕共同的目标,并在实现目标的优先次序上达成一致。`
  353. },
  354. {
  355. title:'外部流程、系统与结构',thbgcolor:'#009191',thtextcolor:'#FFFFFF',titlecolor:'#009191',dimensionCode:"external",bordercolor:'#B3DEDE',
  356. desc:`「外部流程、系统与结构」维度,我们旨在探究团队如何与各种利益相关者互动,他们与团队的利益相关方各自如何寻求了解对方,以及现有系统和流程的有效性,以帮助管理不同利益相关者的期望和需求。`
  357. },
  358. {
  359. title:'人际关系',thbgcolor:'#FFD750',thtextcolor:'#002846',titlecolor:'#002846',dimensionCode:"relationship",bordercolor:'#FFEBA8',
  360. desc:`「人际关系」维度,我们旨在探究团队成员相互沟通交流的状态,团队成员的信任程度、尊重和关心的程度、心理安全度,以及团队成员之间的关系如何促进(或破坏)协作。`
  361. },
  362. {
  363. title:'内部流程、系统与结构',thbgcolor:'#4EB2B2',thtextcolor:'#FFFFFF',titlecolor:'#33A7A7',dimensionCode:"internal",bordercolor:'#B3DEDE',
  364. desc:`「内部流程、系统与结构」维度,我们旨在探究团队在管理工作任务和流程中如何平衡责任与自主权进行协作。主要关注团队应对变化时的敏捷程度、日常沟通方式以及内部决策过程的有效性。`
  365. },
  366. {
  367. title:'学习',thbgcolor:'#AFCDF5',thtextcolor:'#002846',titlecolor:'#002846',dimensionCode:"learning",bordercolor:'#C7DCF8',
  368. desc:`「学习」维度,我们旨在探究团队如何提高其绩效(完成当下的任务)、能力(提高技能和资源以处理明天的任务)和容量(⻓期的愿景,用更少的资源做更多的事情)以应对当前和未来的任务。同时还关注团队如何管理这些能力和提高效率。`
  369. },
  370. {
  371. title:'领导力',thbgcolor:'#002846',thtextcolor:'#FFFFFF',titlecolor:'#002846',dimensionCode:"leadership",bordercolor:'#E6EAED',
  372. desc:`「领导力」维度,我们旨在探究团队领导者的素质和行为(包括但不限于正式与非正式的引导、赋能与责任承担方式)如何对团队功能和其他因素产生影响,以及这是积极的还是消极的。`
  373. }
  374. ]
  375. this.reportData.dimensionAnalysis.forEach((d,i)=>{
  376. d.scoreSpreads.forEach(s=>{
  377. s.theme = s.theme.replaceAll(',',' ').replaceAll(',','');
  378. })
  379. let temp = tempDimensionAnalysis.find(t=>t.dimensionCode === d.dimensionCode)||{};
  380. this.reportData.dimensionAnalysis[i] = {...d,...temp}
  381. })
  382. // 【关键逻辑】在数据渲染后,开始预加载图片
  383. await this.$nextTick(); // 确保DOM更新完毕
  384. try {
  385. const imageUrls = this.collectImageUrls();
  386. const results = await this.preloadAllImages(imageUrls);
  387. // (可选) 检查加载结果并上报或提示
  388. const failedImages = results
  389. .filter(result => result.status === 'rejected')
  390. .map(result => result.reason.url); // 获取加载失败的图片URL
  391. if (failedImages.length > 0) {
  392. console.error(`加载失败的图片 (重试3次后):`, failedImages);
  393. this.$showToast('部分图片资源加载失败');
  394. } else {
  395. console.log('所有依赖的图片已加载完成!');
  396. }
  397. } catch (error) {
  398. // Promise.allSettled 本身不应该抛出错误,这里作为保险
  399. console.error('预加载过程中发生意外错误:', error);
  400. } finally {
  401. // 无论成功或失败,最后都隐藏loading
  402. this.isLoading = false;
  403. }
  404. }).catch(err => {
  405. // 网络请求失败等情况
  406. console.error('获取报告数据失败:', err);
  407. this.isLoading = false;
  408. this.$showToast('获取报告数据失败,请检查网络');
  409. });
  410. },
  411. /**
  412. * @description 新增:收集页面上所有需要预加载的图片URL
  413. * @returns {Array<string>} 包含所有图片URL的数组
  414. */
  415. collectImageUrls() {
  416. // 使用 Set 来自动去重
  417. const imageUrls = new Set();
  418. const baseUrl = 'https://oss.familydaf.cn/sxsnfile/';
  419. // 1. 静态图片 (封面、介绍等)
  420. imageUrls.add(baseUrl + '20251230/b70c19716d86452aaa0c9808a2851e8b.png');
  421. console.log(`[Preload] 收集到 ${imageUrls.size} 个待加载的图片URL`);
  422. return Array.from(imageUrls); // 将 Set 转换为数组
  423. },
  424. /**
  425. * @description 【新增】带重试逻辑的单个图片加载器
  426. * @param {string} url 图片URL
  427. * @param {number} maxRetries 最大重试次数
  428. * @returns {Promise} 成功时 resolve, 彻底失败时 reject
  429. */
  430. loadImageWithRetry(url, maxRetries = 5) {
  431. return new Promise((resolve, reject) => {
  432. let attempts = 0;
  433. const tryLoad = () => {
  434. attempts++;
  435. uni.getImageInfo({
  436. src: url,
  437. success: (res) => {
  438. // console.log(`图片加载成功: ${url} (尝试次数: ${attempts})`);
  439. resolve(res);
  440. },
  441. fail: (err) => {
  442. console.warn(`图片加载失败: ${url} (尝试次数: ${attempts}/${maxRetries})`);
  443. if (attempts < maxRetries) {
  444. // 稍作延迟后重试,避免立即请求导致服务器压力
  445. setTimeout(tryLoad, 300);
  446. } else {
  447. // 达到最大重试次数,彻底失败
  448. console.error(`图片彻底加载失败: ${url}`);
  449. reject({ url, error: err }); // reject时返回包含url的对象,方便追踪
  450. }
  451. }
  452. });
  453. };
  454. tryLoad();
  455. });
  456. },
  457. /**
  458. * @description 【优化】预加载所有图片,并处理重试逻辑
  459. * @param {Array<string>} urls 图片URL数组
  460. * @returns {Promise<Array<Object>>} 返回 Promise.allSettled 的结果数组
  461. */
  462. preloadAllImages(urls) {
  463. const promises = urls.map(url => this.loadImageWithRetry(url, 5));
  464. // 使用 Promise.allSettled
  465. // 它会等待所有 Promise 完成(无论是 resolve还是reject)
  466. // 这样即使有图片加载失败,也不会中断整个过程
  467. return Promise.allSettled(promises);
  468. },
  469. async createPdf(){
  470. uni.showLoading({
  471. title:'正在生成PDF所需的图片...'
  472. })
  473. try {
  474. const ztzdfxImgPromise = this.downloadZtzdfxImg();
  475. const dimensionImagePromises = this.reportData.dimensionAnalysis.map(d => {
  476. return this.generateScoreImage(d,d.scoreSpreads);
  477. });
  478. const allImageUrls = await Promise.all([
  479. ztzdfxImgPromise,
  480. ...dimensionImagePromises
  481. ]);
  482. this.pdfImages = allImageUrls;
  483. this.$api.post('/core/report/reportToPdf',{
  484. images:this.pdfImages,
  485. reportId:this.reportId
  486. }).then(({data:res})=>{
  487. if(res.code!==0) return this.$showToast(res.msg)
  488. uni.hideLoading();
  489. this.$showToast('生成成功');
  490. setTimeout(()=>{
  491. uni.redirectTo({
  492. url:'/pagesHome/report'
  493. })
  494. },1500)
  495. })
  496. } catch (error) {
  497. uni.hideLoading();
  498. console.error('生成图片过程中发生错误:', error);
  499. uni.showToast({ title: '生成图片失败,请重试', icon: 'none' });
  500. }
  501. },
  502. // 绘制主函数
  503. async generateScoreImage(dimensionData, scoreData) {
  504. return new Promise(resolve => {
  505. console.log('开始生成图片...');
  506. // --- 1. 定义尺寸和样式 ---
  507. const canvasWidth = 588;
  508. const headerHeight = 26;
  509. const rowHeight = 54;
  510. const totalHeight = headerHeight + rowHeight * scoreData.length;
  511. const leftColWidth = 280;
  512. const rightColWidth = 308;
  513. // --- 2. 获取 Canvas 节点 ---
  514. const query = uni.createSelectorQuery().in(this);
  515. query.select('#score-canvas')
  516. .fields({
  517. node: true,
  518. size: true
  519. })
  520. .exec(async (res) => {
  521. if (!res || !res[0] || !res[0].node) {
  522. console.error('获取 Canvas 节点失败,请检查 canvas-id 和 type="2d" 是否正确设置。');
  523. uni.showToast({
  524. title: '组件初始化失败',
  525. icon: 'none'
  526. });
  527. return;
  528. }
  529. const canvasNode = res[0].node;
  530. const ctx = canvasNode.getContext('2d');
  531. const dpr = uni.getSystemInfoSync().pixelRatio;
  532. // --- 3. 设置画布尺寸和缩放以适应高分屏 ---
  533. canvasNode.width = canvasWidth * dpr;
  534. canvasNode.height = totalHeight * dpr;
  535. ctx.scale(dpr, dpr);
  536. // --- 4. 开始绘制 ---
  537. // 绘制大背景
  538. ctx.fillStyle = '#FFFFFF';
  539. ctx.fillRect(0, 0, canvasWidth, totalHeight);
  540. // --- 5. 绘制表头 ---
  541. // 左侧表头背景
  542. ctx.fillStyle = dimensionData.thbgcolor;
  543. ctx.fillRect(0, 0, leftColWidth, headerHeight);
  544. // 右侧表头背景
  545. ctx.fillRect(leftColWidth, 0, rightColWidth, headerHeight);
  546. // 左侧表头文字
  547. ctx.fillStyle = dimensionData.thtextcolor;
  548. ctx.font = 'bold 9px sans-serif';
  549. ctx.textBaseline = 'middle';
  550. ctx.textAlign = 'left';
  551. ctx.fillText('主题', 15, headerHeight / 2);
  552. // 左侧表头分割线
  553. ctx.strokeStyle = 'rgba(255,255,255,0.24)';
  554. ctx.lineWidth = 1;
  555. ctx.beginPath();
  556. ctx.moveTo(leftColWidth - 54, 0);
  557. ctx.lineTo(leftColWidth - 54, headerHeight);
  558. ctx.stroke();
  559. // 影响力分右侧分割线 (Header)
  560. ctx.beginPath();
  561. ctx.moveTo(leftColWidth, 0);
  562. ctx.lineTo(leftColWidth, headerHeight);
  563. ctx.stroke();
  564. ctx.textAlign = 'center';
  565. ctx.fillText('影响力分', leftColWidth - 27, headerHeight / 2);
  566. // 右侧表头分割线
  567. ctx.beginPath();
  568. ctx.moveTo(leftColWidth + rightColWidth / 2, 0);
  569. ctx.lineTo(leftColWidth + rightColWidth / 2, headerHeight);
  570. ctx.stroke();
  571. // 右侧表头文字
  572. ctx.fillText('认同度分', leftColWidth + rightColWidth / 4, headerHeight / 2);
  573. ctx.fillText('重要性分', leftColWidth + rightColWidth * 0.75, headerHeight / 2);
  574. // --- 6. 循环绘制每一行 ---
  575. for (let i = 0; i < scoreData.length; i++) {
  576. const item = scoreData[i];
  577. const yPos = headerHeight + i * rowHeight;
  578. this.drawTableItem(ctx, item, yPos, leftColWidth, rightColWidth, rowHeight, dimensionData);
  579. }
  580. // --- 7. 绘制最外层边框 ---
  581. ctx.strokeStyle = dimensionData.bordercolor;
  582. ctx.lineWidth = 1;
  583. ctx.strokeRect(0, 0, canvasWidth, totalHeight);
  584. // --- 9. 生成图片 ---
  585. uni.hideLoading();
  586. uni.canvasToTempFilePath({
  587. canvas: canvasNode,
  588. x: 0,
  589. y: 0,
  590. width: canvasWidth,
  591. height: totalHeight,
  592. destWidth: canvasWidth * dpr,
  593. destHeight: totalHeight * dpr,
  594. success: async (result) => {
  595. console.log('图片生成成功!', result.tempFilePath);
  596. const fileurl = await this.uploadFilePromise(result.tempFilePath);
  597. console.log(fileurl, 'fileurl');
  598. resolve(fileurl)
  599. },
  600. fail: (err) => {
  601. console.error('图片生成失败', err);
  602. uni.showToast({
  603. title: '图片生成失败',
  604. icon: 'none'
  605. });
  606. }
  607. }, this);
  608. });
  609. })
  610. },
  611. drawTableItem(ctx, item, y, leftW, rightW, h, dimensionData) {
  612. // 1. 设置通用边框样式
  613. ctx.strokeStyle = dimensionData.bordercolor;
  614. ctx.lineWidth = 1;
  615. // 绘制整行下边框 (贯穿左右)
  616. ctx.beginPath();
  617. ctx.moveTo(0, y + h);
  618. ctx.lineTo(leftW + rightW, y + h);
  619. ctx.stroke();
  620. // --- 计算高度 ---
  621. // 必须先设置字体,否则 measureText 计算不准确
  622. ctx.font = 'bold 9px sans-serif';
  623. let themeHeight = this.calculateWrappedTextHeight(ctx, item.theme, 14, leftW - 54 - 20);
  624. ctx.font = '8px sans-serif';
  625. let questionHeight = this.calculateWrappedTextHeight(ctx, item.question, 10, leftW - 54 - 20);
  626. let totalTextHeight = themeHeight + 4 + questionHeight; // 4 is spacing
  627. let startY = y + (h - totalTextHeight) / 2;
  628. // --- 绘制左侧文字 ---
  629. // Theme Title
  630. ctx.fillStyle = dimensionData.titlecolor;
  631. ctx.font = 'bold 9px sans-serif';
  632. ctx.textAlign = 'left';
  633. ctx.textBaseline = 'top';
  634. this.drawWrappedTextTop(ctx, item.theme, 10, startY, 14, leftW - 54 - 20);
  635. // Question Tip
  636. ctx.fillStyle = '#002846';
  637. ctx.font = '8px sans-serif';
  638. this.drawWrappedTextTop(ctx, item.question, 10, startY + themeHeight + 4, 10, leftW - 54 - 20);
  639. // Score
  640. ctx.fillStyle = '#002846';
  641. ctx.font = 'bold 12px DIN, sans-serif';
  642. ctx.textAlign = 'center';
  643. ctx.textBaseline = 'middle';
  644. ctx.fillText((item.regular && item.regular.avgScore) || 0, leftW - 27, y + h / 2);
  645. // 2. 右侧
  646. const rightX = leftW;
  647. // 绘制中间线
  648. const centerX = rightX + rightW / 2;
  649. // Bar Charts
  650. const barHeight = 10;
  651. const barY = y + (h - barHeight) / 2;
  652. // Agreement (Left, Red/Purple)
  653. // Width = avgAgreement / 6 * 100% of half width?
  654. const maxVal = 6;
  655. const halfW = rightW / 2;
  656. const avgAgreement = (item.regular && item.regular.avgAgreement) || 0;
  657. const avgVital = (item.regular && item.regular.avgVital) || 0;
  658. const agreeW = (avgAgreement / maxVal) * halfW;
  659. const vitalW = (avgVital / maxVal) * halfW;
  660. // --- 绘制背景轨道 (灰色) ---
  661. // 模仿 CSS .vbt2r-tb-l-pre-bg: width: calc(100% - 16px); height: 4px;
  662. // 左右各留 8px 空隙?或者就是简单的横条
  663. const trackHeight = 4;
  664. const trackY = y + (h - trackHeight) / 2;
  665. ctx.fillStyle = '#E6EAED';
  666. // 左侧轨道背景 (从右向左)
  667. this.drawRoundedRect(ctx, rightX + 8, trackY, halfW - 8, trackHeight, 2);
  668. // 右侧轨道背景 (从左向右)
  669. this.drawRoundedRect(ctx, centerX, trackY, halfW - 8, trackHeight, 2);
  670. // Draw Agreement Bar (grows from center to left)
  671. ctx.fillStyle = '#BA8EB4';
  672. // 左侧条,圆角在左边 [2, 0, 0, 2]
  673. this.drawCustomRoundedRect(ctx, centerX - agreeW, barY, agreeW, barHeight, [2, 0, 0, 2]);
  674. // Draw Vital Bar (grows from center to right)
  675. ctx.fillStyle = '#80C8C8';
  676. // 右侧条,圆角在右边 [0, 2, 2, 0]
  677. this.drawCustomRoundedRect(ctx, centerX, barY, vitalW, barHeight, [0, 2, 2, 0]);
  678. // --- 绘制数字标签 ---
  679. const tagW = 16;
  680. const tagH = 20;
  681. // 左标签位置 (中心点)
  682. // CSS: left: -8px (relative to bar start/end).
  683. // 实际上是在条形图的末端(远离中心的那一端)
  684. // 左侧条形图末端 x = centerX - agreeW
  685. // 标签中心 x = centerX - agreeW
  686. this.drawValueTag(ctx, avgAgreement, centerX - agreeW, y + h/2, '#904A87', 'rgba(131,52,120,0.19)', 'rgba(118,30,106,0.1)');
  687. // 右标签位置
  688. // 右侧条形图末端 x = centerX + vitalW
  689. this.drawValueTag(ctx, avgVital, centerX + vitalW, y + h/2, '#199C9C', 'rgba(51,167,167,0.31)', 'rgba(118,30,106,0.1)');
  690. },
  691. // 辅助函数:绘制带样式的数值标签
  692. drawValueTag(ctx, value, cx, cy, textColor, borderColor, shadowColor) {
  693. const width = 16;
  694. const height = 20;
  695. const x = cx - width / 2;
  696. const y = cy - height / 2;
  697. const radius = 4;
  698. ctx.save();
  699. // 阴影
  700. ctx.shadowOffsetX = 0;
  701. ctx.shadowOffsetY = 4;
  702. ctx.shadowBlur = 8;
  703. ctx.shadowColor = shadowColor;
  704. // 背景
  705. ctx.fillStyle = '#FFFFFF';
  706. this.drawRoundedRectPath(ctx, x, y, width, height, radius);
  707. ctx.fill();
  708. // 边框 (取消阴影绘制边框)
  709. ctx.shadowColor = 'transparent';
  710. ctx.strokeStyle = borderColor;
  711. ctx.lineWidth = 1;
  712. ctx.stroke();
  713. // 文字
  714. ctx.fillStyle = textColor;
  715. ctx.font = 'bold 12px DIN, sans-serif';
  716. ctx.textAlign = 'center';
  717. ctx.textBaseline = 'middle';
  718. ctx.fillText(value, cx, cy);
  719. ctx.restore();
  720. },
  721. // 辅助函数:绘制圆角矩形路径
  722. drawRoundedRectPath(ctx, x, y, w, h, r) {
  723. ctx.beginPath();
  724. ctx.moveTo(x + r, y);
  725. ctx.lineTo(x + w - r, y);
  726. ctx.arcTo(x + w, y, x + w, y + h, r);
  727. ctx.lineTo(x + w, y + h - r);
  728. ctx.arcTo(x + w, y + h, x, y + h, r);
  729. ctx.lineTo(x + r, y + h);
  730. ctx.arcTo(x, y + h, x, y, r);
  731. ctx.lineTo(x, y + r);
  732. ctx.arcTo(x, y, x + w, y, r);
  733. ctx.closePath();
  734. },
  735. // 辅助函数:绘制圆角矩形 (简单版)
  736. drawRoundedRect(ctx, x, y, w, h, r) {
  737. this.drawRoundedRectPath(ctx, x, y, w, h, r);
  738. ctx.fill();
  739. },
  740. // 辅助函数:绘制自定义四个角圆角的矩形
  741. drawCustomRoundedRect(ctx, x, y, w, h, radii) {
  742. // radii: [tl, tr, br, bl]
  743. if (!Array.isArray(radii)) radii = [radii, radii, radii, radii];
  744. const [tl, tr, br, bl] = radii;
  745. ctx.beginPath();
  746. ctx.moveTo(x + tl, y);
  747. ctx.lineTo(x + w - tr, y);
  748. ctx.arcTo(x + w, y, x + w, y + h, tr);
  749. ctx.lineTo(x + w, y + h - br);
  750. ctx.arcTo(x + w, y + h, x, y + h, br);
  751. ctx.lineTo(x + bl, y + h);
  752. ctx.arcTo(x, y + h, x, y, bl);
  753. ctx.lineTo(x, y + tl);
  754. ctx.arcTo(x, y, x + w, y, tl);
  755. ctx.closePath();
  756. ctx.fill();
  757. },
  758. // 辅助函数:计算自动换行文字的总高度
  759. calculateWrappedTextHeight(ctx, text, lineHeight, maxWidth) {
  760. if (!text) return 0;
  761. let words = text.split('');
  762. let line = '';
  763. let height = lineHeight; // 至少有一行
  764. for (let n = 0; n < words.length; n++) {
  765. let testLine = line + words[n];
  766. let metrics = ctx.measureText(testLine);
  767. let testWidth = metrics.width;
  768. if (testWidth > maxWidth && n > 0) {
  769. line = words[n];
  770. height += lineHeight;
  771. } else {
  772. line = testLine;
  773. }
  774. }
  775. return height;
  776. },
  777. // 辅助函数:绘制自动换行文字 (从顶部开始)
  778. drawWrappedTextTop(ctx, text, x, y, lineHeight, maxWidth) {
  779. if (!text) return;
  780. let words = text.split('');
  781. let line = '';
  782. let currentY = y;
  783. for (let n = 0; n < words.length; n++) {
  784. let testLine = line + words[n];
  785. let metrics = ctx.measureText(testLine);
  786. let testWidth = metrics.width;
  787. if (testWidth > maxWidth && n > 0) {
  788. ctx.fillText(line, x, currentY); // 使用 currentY 作为顶部基线
  789. line = words[n];
  790. currentY += lineHeight;
  791. } else {
  792. line = testLine;
  793. }
  794. }
  795. ctx.fillText(line, x, currentY);
  796. },
  797. calculateScaleAndPosition() {
  798. uni.getSystemInfo({
  799. success: (res) => {
  800. const screenWidth = res.windowWidth; // 手机屏幕的宽度
  801. const pcContentWidth = 630; // PC端内容的原始宽度
  802. this.scale = screenWidth / pcContentWidth;
  803. this.$nextTick(() => {
  804. if (this.$refs.ztzdfxRef) {
  805. this.initZtzdfxChart();
  806. }
  807. });
  808. }
  809. });
  810. },
  811. calculatePdfContainerHeight() {
  812. uni.createSelectorQuery().in(this).select('#pdfContainer').boundingClientRect(rect => {
  813. if (rect) {
  814. this.originalContainerHeight = rect.height;
  815. this.containerScaledHeight = this.originalContainerHeight * this.scale;
  816. // console.log('原始高度:', this.originalContainerHeight, '缩放比例:', this.scale, '缩放后高度:', this.containerScaledHeight);
  817. }
  818. }).exec();
  819. },
  820. downloadZtzdfxImg(){
  821. return new Promise(resolve=>{
  822. if (!this.isChartReady) return console.log('图表尚未准备好');
  823. const chartRef = this.$refs.ztzdfxRef;
  824. if (!chartRef) return console.log('无法找到图表组件');
  825. chartRef.canvasToTempFilePath({
  826. success: async (res) => {
  827. const imgUrl = await this.uploadFilePromise(res.tempFilePath);
  828. console.log(imgUrl,'imgUrl');
  829. resolve(imgUrl)
  830. },
  831. fail: (err) => {
  832. console.log('生成图片失败:', err);
  833. }
  834. });
  835. })
  836. },
  837. uploadFilePromise(url) {
  838. return new Promise((resolve, reject) => {
  839. let a = uni.uploadFile({
  840. url: BaseApi+'/uploadFile',
  841. filePath: url,
  842. name: 'file',
  843. success: (res) => {
  844. setTimeout(() => {
  845. let data = JSON.parse(res.data)
  846. if(data&&data.code===0){
  847. resolve(data.data);
  848. }else this.$showToast(data?.msg)
  849. }, 1000);
  850. },
  851. fail: err =>{
  852. resolve('');
  853. }
  854. });
  855. });
  856. },
  857. async initZtzdfxChart() {
  858. let dataSum = this.reportData.overall.length*this.reportData.overall[0].themeTotalSpreads.length;
  859. const leaderData = [],memberData = [],stakeholderData=[],sponsorData=[];
  860. const overall = this.reportData.overall||[];
  861. overall.forEach(o=>{
  862. let themeTotalSpreads = o.themeTotalSpreads||[];
  863. if(['内部流程、系统与结构','学习','领导力'].includes(o.dimension)) themeTotalSpreads = o.themeTotalSpreads.reverse()||[];
  864. themeTotalSpreads.forEach(t=>{
  865. leaderData.push(t.scoreLeader||0);
  866. memberData.push(t.scoreMember||0);
  867. stakeholderData.push(t.scoreStakeholder||0);
  868. sponsorData.push(t.scoreSponsor||0);
  869. })
  870. })
  871. const sumArr = leaderData.concat(memberData).concat(stakeholderData).concat(sponsorData);
  872. const maxValue = sumArr.reduce((a,b)=>Math.max(a,b));
  873. const minValue = sumArr.reduce((a,b)=>Math.min(a,b));
  874. const chart = await this.$refs.ztzdfxRef.init(echarts);
  875. let option = {
  876. graphic: [
  877. {
  878. type: 'image',
  879. id: 'radar-bg',
  880. z: -1,
  881. bounding: 'raw',
  882. left: 'center',
  883. top: 'center',
  884. style: {
  885. image:'https://oss.familydaf.cn/sxsnfile/20251230/b70c19716d86452aaa0c9808a2851e8b.png',
  886. width: 360*this.scale-20,
  887. height: 360*this.scale-20,
  888. opacity: 1
  889. }
  890. },
  891. {
  892. type: 'circle',
  893. z: 100, // 设置一个较高的 z 值,确保它在最顶层
  894. left: 'center', // 水平居中
  895. top: 'center', // 垂直居中
  896. shape: {
  897. r: 12*this.scale // 圆形的半径,您可以根据需要调整大小
  898. },
  899. style: {
  900. fill: '#FFFFFF', // 填充色为白色
  901. shadowBlur: 20*this.scale, // 阴影的模糊范围
  902. shadowColor: 'rgba(0, 0, 0, 0.15)', // 阴影颜色
  903. shadowOffsetY: 4*this.scale // 向下的阴影偏移,产生悬浮效果
  904. }
  905. }
  906. ],
  907. radar: {
  908. // shape: 'circle',
  909. indicator: new Array(dataSum).fill({ max:maxValue, min:minValue }),
  910. axisName: {
  911. show: false
  912. },
  913. splitArea:{
  914. show:false
  915. },
  916. splitLine: {
  917. show: false
  918. },
  919. axisLine: {
  920. show: false
  921. },
  922. startAngle: 95
  923. },
  924. series: [
  925. {
  926. type: 'radar',
  927. data: [
  928. {
  929. value: sponsorData,
  930. itemStyle: {
  931. color: '#012846'
  932. },
  933. lineStyle: {
  934. color: '#012846',
  935. width:1
  936. },
  937. areaStyle: {
  938. color: 'rgba(255, 255, 255, 0.4)'
  939. },
  940. symbolSize: 2
  941. },
  942. {
  943. value: stakeholderData,
  944. itemStyle: {
  945. color: '#FFD650'
  946. },
  947. lineStyle: {
  948. color: '#FFD650',
  949. width:1
  950. },
  951. areaStyle: {
  952. color: 'rgba(255, 255, 255, 0.4)'
  953. },
  954. symbolSize: 2
  955. },
  956. {
  957. value: memberData,
  958. itemStyle: {
  959. color: '#AFCDF5'
  960. },
  961. lineStyle: {
  962. color: '#AFCDF5',
  963. width:1
  964. },
  965. areaStyle: {
  966. color: 'rgba(255, 255, 255, 0.4)'
  967. },
  968. symbolSize: 2
  969. },
  970. {
  971. value: leaderData,
  972. itemStyle: {
  973. color: '#9F6196'
  974. },
  975. lineStyle: {
  976. color: '#9F6196',
  977. width:1
  978. },
  979. areaStyle: {
  980. color: 'rgba(255, 255, 255, 0.4)'
  981. },
  982. symbolSize: 2
  983. }
  984. ]
  985. }
  986. ]
  987. };
  988. chart.setOption(option);
  989. this.isChartReady = true;
  990. this.$nextTick(() => {
  991. this.calculatePdfContainerHeight();
  992. });
  993. },
  994. }
  995. };
  996. </script>
  997. <style scoped lang="scss">
  998. .loading{
  999. position: fixed;
  1000. left: 0;
  1001. right: 0;
  1002. top: 0;
  1003. bottom: 0;
  1004. background: rgba(0,0,0,.4);
  1005. z-index: 1001;
  1006. }
  1007. .page-wrappe{
  1008. width: 100%;
  1009. background: #FFFFFF;
  1010. overflow-x: hidden;
  1011. overflow-y: auto;
  1012. .pdf-container{
  1013. width: 630px;
  1014. padding: 0 20rpx;
  1015. box-sizing: border-box;
  1016. transform-origin: top left;
  1017. }
  1018. }
  1019. .offscreen-canvas {
  1020. position: fixed;
  1021. top: -9999px;
  1022. left: -9999px;
  1023. }
  1024. .pdf_btn{
  1025. padding: 15rpx 20rpx;
  1026. border-radius: 20rpx;
  1027. font-size: 28rpx;
  1028. color: #FFFFFF;
  1029. background: #189B9B;
  1030. position: fixed;
  1031. right: 30rpx;
  1032. bottom: 100rpx;
  1033. z-index: 1000;
  1034. }
  1035. @import '../static/pdf.scss';
  1036. </style>