screen.page.vue 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. <script setup lang="ts">
  2. import { Notify } from '@/platform';
  3. import { copyrightMethod, processMethod } from '@/request/api';
  4. import { useVisitor } from '@/stores';
  5. import { useElementSize } from '@vueuse/core';
  6. import { useRequest } from 'alova/client';
  7. import p5 from 'p5';
  8. const router = useRouter();
  9. const Visitor = useVisitor();
  10. const title = import.meta.env.SIX_APP_TITLE;
  11. const { data: copyright } = useRequest(copyrightMethod);
  12. const { send: handle, loading } = useRequest(processMethod, { immediate: false }).onSuccess(({ data }) => {
  13. if ( data ) {
  14. Visitor.$reset();
  15. router.push(data);
  16. } else {
  17. Notify.warning(`[路由] 配置异常无法解析正确路径,请联系管理员`);
  18. }
  19. });
  20. const container = useTemplateRef<HTMLDivElement>('container');
  21. const { width, height } = useElementSize(container);
  22. interface Bubble {
  23. text: string;
  24. color: string;
  25. x?: number;
  26. y?: number;
  27. dx?: number;
  28. dy?: number;
  29. diameter?: number;
  30. }
  31. watchEffect(() => {
  32. if ( width.value && height.value ) {
  33. init(
  34. { width: width.value, height: height.value * 0.90, container: container.value! });
  35. }
  36. });
  37. function init({ width, height, container }: { width: number; height: number; container: HTMLElement }) {
  38. const bubbles = [];
  39. new p5((sketch) => {
  40. let scanLineOffset;
  41. let scanLineOffStep = 5;
  42. const drawScan = (w = 24, x = 40, h = w, y = x, { color = '#34a76b', size = 5 } = {}) => {
  43. sketch.push();
  44. sketch.stroke(color);
  45. sketch.strokeWeight(size);
  46. // 左上角
  47. sketch.line(x, y, x + w, y);
  48. sketch.line(x, y, x, y + h);
  49. // 左下角
  50. sketch.line(x, height - y, x + w, height - y);
  51. sketch.line(x, height - y, x, height - y - h);
  52. // 右上角
  53. sketch.line(width - x, y, width - x - w, y);
  54. sketch.line(width - x, y, width - x, y + h);
  55. // 右下角
  56. sketch.line(width - x, height - y, width - x - w, height - y);
  57. sketch.line(width - x, height - y, width - x, height - y - h);
  58. // 线
  59. const yT = y * 2;
  60. const yB = height - yT;
  61. scanLineOffset ??= yT;
  62. if ( scanLineOffset < yT || scanLineOffset > yB ) scanLineOffStep = -scanLineOffStep;
  63. scanLineOffset += scanLineOffStep;
  64. sketch.line(x, scanLineOffset, width - x, scanLineOffset);
  65. sketch.pop();
  66. };
  67. const bubbles: Bubble[] = [
  68. { text: '有气\n无力', color: '#367dd599' },
  69. { text: '容易\n犯困', color: '#b1450399' },
  70. { text: '睡眠\n障碍', color: '#34b10399' },
  71. { text: '消化\n不良', color: '#b1860399' },
  72. { text: '肩颈\n腰痛', color: '#03b19b99' },
  73. { text: '掉\n头发', color: '#b1a30399' },
  74. { text: '记忆力\n下降', color: '#34b10399' },
  75. ];
  76. const drawBubble = (x = 40, y = x, diameter = 90) => {
  77. for ( const bubble of <Required<Bubble>[]> bubbles ) {
  78. bubble.diameter ??= diameter;
  79. const radius = Math.floor(bubble.diameter / 2);
  80. bubble.x ??= sketch.random(x + radius, width - x - radius);
  81. bubble.y ??= sketch.random(y + radius, height - y - radius);
  82. bubble.dx ??= sketch.random(-2, 2);
  83. bubble.dy ??= sketch.random(-2, 2);
  84. // 移动
  85. bubble.x += bubble.dx;
  86. bubble.y += bubble.dy;
  87. if ( bubble.x + radius >= width - y ) bubble.x = width - y;
  88. if ( bubble.y + radius >= height - y ) bubble.y = height - y;
  89. // 绘制
  90. const size = 24;
  91. const color = sketch.color(bubble.color);
  92. sketch.push();
  93. sketch.fill('#fff');
  94. sketch.textSize(size);
  95. sketch.textAlign(sketch.CENTER);
  96. sketch.text(bubble.text, bubble.x, bubble.y - size / 4);
  97. sketch.fill(color);
  98. sketch.circle(bubble.x, bubble.y, bubble.diameter);
  99. sketch.pop();
  100. // 检测边界
  101. if ( bubble.x - radius <= x || bubble.x + radius >= width - x ) {
  102. bubble.dx *= -1;
  103. if ( bubble.x - radius <= x ) { bubble.x = x + radius; } else { bubble.x = width - x - radius; }
  104. }
  105. if ( bubble.y - radius <= y || bubble.y + radius >= height - y ) {
  106. bubble.dy *= -1;
  107. if ( bubble.y - radius <= y ) { bubble.y = y + radius; } else { bubble.y = height - y - radius; }
  108. }
  109. }
  110. const collide = (bubble: Required<Bubble>, other: Required<Bubble>) => {
  111. const radius = Math.floor(bubble.diameter / 2);
  112. let angle = sketch.atan2(other.y - bubble.y, other.x - bubble.x);
  113. let target = sketch.createVector(bubble.x, bubble.y);
  114. let a = p5.Vector.fromAngle(angle + sketch.PI, radius);
  115. let b = p5.Vector.fromAngle(angle, radius);
  116. bubble.x = target.x + a.x;
  117. bubble.y = target.y + a.y;
  118. other.x = target.x + b.x;
  119. other.y = target.y + b.y;
  120. bubble.dx *= -1;
  121. other.dx *= -1;
  122. };
  123. for ( let i = 0; i < bubbles.length; i++ ) {
  124. for ( let j = i + 1; j < bubbles.length; j++ ) {
  125. const bubble = bubbles[ i ] as Required<Bubble>;
  126. const other = bubbles[ j ] as Required<Bubble>;
  127. const d = sketch.dist(bubble.x, bubble.y, other.x, other.y);
  128. if ( d < bubble.diameter ) {
  129. collide(bubble, other);
  130. collide(other, bubble);
  131. }
  132. }
  133. }
  134. };
  135. sketch.setup = () => {
  136. sketch.createCanvas(width, height);
  137. sketch.noStroke();
  138. };
  139. sketch.draw = () => {
  140. sketch.clear();
  141. drawScan();
  142. drawBubble();
  143. };
  144. }, container);
  145. }
  146. </script>
  147. <template>
  148. <div class="wrapper">
  149. <div class="fixed size-full" ref="container"></div>
  150. <div class="fixed flex flex-col size-full">
  151. <div class="flex-none" style="height: 85vh;">
  152. <img class="mx-auto h-full object-scale-down" style="width: 32vw;" src="@/assets/images/title.png" :alt="title">
  153. </div>
  154. <div class="flex-auto flex flex-col">
  155. <div class="flex-auto flex justify-center items-center">
  156. <van-button class="decorate" :loading @click="handle()">开始检测</van-button>
  157. </div>
  158. <div class="flex-none text-xl p-8 text-center" v-html="copyright"></div>
  159. </div>
  160. </div>
  161. </div>
  162. </template>
  163. <style scoped lang="scss">
  164. .wrapper {
  165. background: url("@/assets/images/screen.png") no-repeat center / 100%;
  166. }
  167. </style>