LearnReportShareImage.php 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. <?php
  2. namespace App\Http\Controllers\Api\Video;
  3. use App\Facades\Servers\WechatMini\Mini;
  4. use App\Http\Controllers\Api\Api;
  5. use App\Models\Video\Course;
  6. use App\Models\Video\LearnRecord;
  7. use Intervention\Image\Facades\Image;
  8. use Intervention\Image\Gd\Font;
  9. class LearnReportShareImage extends Api
  10. {
  11. /**
  12. * 学习中报告图片 /api/learn_report_share_image/get
  13. * @param LearnRecord $learnRecordModel
  14. * @param Course $courseModel
  15. * @return array|string
  16. * @throws \App\Exceptions\Api\LoginException
  17. */
  18. public function get_share_image(LearnRecord $learnRecordModel, Course $courseModel)
  19. {
  20. // 登录信息
  21. $uid = $this->checkLogin();
  22. // 接收参数
  23. $courseId = request('course_id', 0);
  24. $fontPath = public_path() . '/fonts/msyh14.ttf'; // 指定字体文件路径
  25. //学习课程名称
  26. $courseName = $courseModel->query()->where([['id', '=', $courseId]])->value('name');
  27. $courseName = str_replace(' ', '', $courseName); //去除课程名称中的空格
  28. //获取总积分(客户)
  29. $totalScore = $learnRecordModel->query()->where([['course_id', '=', $courseId], ['custom_uid', '=', $uid]])->sum('get_score');
  30. // 尝试执行
  31. try {
  32. // 加载图片
  33. $image = Image::make(public_path('uploads/images/default/') . 'learn_report_share_base.png');
  34. $lineHeight = 30; // 行高
  35. $fontSize = 24;
  36. $maxWidth = 330;
  37. // 课程名称过长的时候需要截断
  38. $lines = $this->truncateText($courseName, $fontPath, $fontSize, $maxWidth);
  39. $y = 170; // 初始 y 坐标
  40. // 处理数据
  41. foreach ($lines as $line) {
  42. $image->text($line, 30, $y, function ($font) use ($fontPath, $fontSize) {
  43. $font->file($fontPath);
  44. $font->size($fontSize);
  45. $font->color('#2B3D7c');
  46. $font->align('left');
  47. });
  48. $y += $lineHeight; // 下一行的 y 坐标
  49. }
  50. // 给图片写入文字(总积分)
  51. $image->text($totalScore, 90, 310, function (Font $font) use ($fontPath) {
  52. $font->file($fontPath); // 字体文件地址
  53. $font->size(30); // 字体大小
  54. $font->color('#E86D8F');
  55. $font->align('center');
  56. });
  57. // 基础路径
  58. $basepath = 'images/video_report_learn/'.date('Ymd').'/';
  59. // 保存路径
  60. $path = public_path('uploads/'.$basepath);
  61. // 如果路劲不存在
  62. if (!is_dir($path)) mkdir($path, 0755, true);
  63. // 路径组合
  64. $filename = $courseId.'_'.$uid.'.jpg';
  65. // 保存图片
  66. $image->encode('jpg', 90)->save($path.$filename);
  67. // 下发路径
  68. $path = path_compat($basepath.$filename);
  69. // 返回路径
  70. return json_send(['code' => 'success', 'msg' => '生成成功', 'data' => ['image_url' => $path.'?t='.time(),'title'=>$courseName]]);
  71. } catch (\Throwable $th) {
  72. // 返回路径
  73. return json_send(['code' => 'error', 'msg' => $th->getMessage()]);
  74. }
  75. }
  76. //获取课后分析的答题数量
  77. public function get_after_study_report_image_share(LearnRecord $learnRecordModel)
  78. {
  79. // 登录信息(客户id)
  80. $uid = $this->checkLogin();
  81. // 接收参数
  82. $courseId = request('course_id', 0);
  83. $recordId = request('record_id', 0);
  84. $url = request('page', 'pages/video/record');
  85. $size = request('size', 150);
  86. $scene = request('scene', '');
  87. $position = 'bottom-left';
  88. $fontPath = public_path() . '/fonts/msyh14.ttf'; // 指定字体文件路径
  89. //获取总积分(客户)
  90. $reportData = $learnRecordModel->query()->where([['id','=',$recordId]])->first(['question_total','isanswer_total']);
  91. // 如果没有数据
  92. if( !$reportData ) return json_send(['code' => 'error', 'msg' => '没有答题记录']);
  93. // 返回结果
  94. $totalQuestionNum = $reportData['question_total'];
  95. $correctNum = $reportData['isanswer_total'];
  96. $inCorrectNum = $totalQuestionNum - $correctNum;
  97. $accuracy = $totalQuestionNum ? ($correctNum / $totalQuestionNum) * 100 : 100;
  98. $accuracy = $accuracy < 100 ? number_format($accuracy, 1) : 100;
  99. try {
  100. // 加载图片
  101. $image = Image::make(public_path('uploads/images/default/') . 'analysis_report_share_base.png');
  102. // 生成小程序二维码
  103. $qrcode = Mini::getUnlimit($scene, ['page' => $url, 'width' => $size, 'is_hyaline' => true]);
  104. // 错误提示
  105. if (isset($qrcode['error'])) return json_send(['code' => 'error', 'msg' => $qrcode['error']]);
  106. // 加载图片 压缩
  107. $qrcode = $size < 280 ? Image::make($qrcode)->resize($size, $size) : Image::make($qrcode);
  108. //插入二维码图片
  109. $image->insert($qrcode, $position, 20, 20);
  110. // 给图片写入文字总题数:
  111. $image->text($totalQuestionNum . '题', 260, 300, function (Font $font) use ($fontPath) {
  112. $font->file($fontPath); // 字体文件地址
  113. $font->size(24); // 字体大小
  114. $font->color('#FF7B01');
  115. $font->align('left');
  116. });
  117. // 给图片写入文字正确题数
  118. $image->text($correctNum . '题', 290, 388, function (Font $font) use ($fontPath) {
  119. $font->file($fontPath); // 字体文件地址
  120. $font->size(24); // 字体大小
  121. $font->color('#0E670F');
  122. $font->align('left');
  123. });
  124. // 给图片写入文字错误题数
  125. $image->text($inCorrectNum . '题', 290, 476, function (Font $font) use ($fontPath) {
  126. $font->file($fontPath); // 字体文件地址
  127. $font->size(24); // 字体大小
  128. $font->color('#FF0101');
  129. $font->align('left');
  130. });
  131. // 给图片写入文字 正确率
  132. $image->text($accuracy . '%', 100, 582, function (Font $font) use ($fontPath) {
  133. $font->file($fontPath); // 字体文件地址
  134. $font->size(36); // 字体大小
  135. $font->color('#FA9517');
  136. $font->align('left');
  137. });
  138. // 图片转二进制
  139. $image = $image->encode('jpg', 90)->__toString();
  140. // 转base64
  141. $base64 = 'data:image/jpg;base64,' . base64_encode($image);
  142. // 返回
  143. return json_send(['code' => 'success', 'msg' => '生成成功', 'data' => ['base64_image' => $base64]]);
  144. } catch (\Throwable $th) {
  145. // 返回
  146. return json_send(['code' => 'error', 'msg' => $th->getMessage()]);
  147. }
  148. }
  149. // 截断文字并添加省略号
  150. function truncateText($text, $fontFile, $fontSize, $maxWidth)
  151. {
  152. $lines = [];
  153. $textLength = mb_strlen($text);
  154. $charIndex = 0;
  155. // 第一行
  156. $firstLine = '';
  157. while ($charIndex < $textLength) {
  158. $firstLine .= mb_substr($text, $charIndex, 1);
  159. $testBox = imagettfbbox($fontSize, 0, $fontFile, $firstLine);
  160. $testWidth = abs($testBox[2] - $testBox[0]);
  161. if ($testWidth > $maxWidth) {
  162. // 第一行超出宽度,截断
  163. $firstLine = mb_substr($firstLine, 0, -1);
  164. break;
  165. }
  166. $charIndex++;
  167. }
  168. // 第一行没有超出宽度,直接添加到结果中
  169. $lines[] = $firstLine;
  170. // 第二行
  171. if ($charIndex < $textLength) {
  172. $secondLine = mb_substr($text, $charIndex);
  173. $testBox = imagettfbbox($fontSize, 0, $fontFile, $secondLine);
  174. $testWidth = abs($testBox[2] - $testBox[0]);
  175. if ($testWidth > $maxWidth) {
  176. // 第二行超出宽度,截断并添加省略号
  177. while ($charIndex < $textLength) {
  178. $secondLine = mb_substr($secondLine, 0, -1);
  179. $testBox = imagettfbbox($fontSize, 0, $fontFile, $secondLine);
  180. $testWidth = abs($testBox[2] - $testBox[0]);
  181. if ($testWidth <= $maxWidth) {
  182. $secondLine .= '...';
  183. break;
  184. }
  185. }
  186. }
  187. $lines[] = $secondLine;
  188. }
  189. return $lines;
  190. }
  191. }