Hanye官网
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

useCaptcha.ts 4.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import { ref, computed, reactive } from 'vue';
  2. /**
  3. * 生成随机字符串
  4. * @param length 字符串长度
  5. * @returns 随机字符串
  6. */
  7. function generateRandomString(length: number): string {
  8. // 使用更简单的字符集,避免混淆字符
  9. const characters = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
  10. let result = '';
  11. for (let i = 0; i < length; i++) {
  12. result += characters.charAt(Math.floor(Math.random() * characters.length));
  13. }
  14. return result;
  15. }
  16. /**
  17. * 为验证码文本生成SVG
  18. * @param text 验证码文本
  19. * @returns SVG 字符串
  20. */
  21. function generateCaptchaSvg(text: string): string {
  22. const width = 150;
  23. const height = 50;
  24. const fontSize = 30;
  25. const letters = text.split('');
  26. // 文字元素(确保可读性)
  27. const textElements = letters.map((char, index) => {
  28. const x = (width / text.length) * (index + 0.5);
  29. const y = height / 2 + fontSize / 3;
  30. const fill = `hsl(${Math.random() * 360}, 70%, 80%)`; // 提高亮度,增加可读性
  31. return `<text
  32. x="${x}"
  33. y="${y}"
  34. font-size="${fontSize}"
  35. font-family="Arial, sans-serif"
  36. font-weight="bold"
  37. text-anchor="middle"
  38. fill="${fill}"
  39. style="letter-spacing: 1px;"
  40. >${char}</text>`;
  41. }).join('');
  42. // 生成干扰线
  43. let lines = '';
  44. // 添加2-4条随机曲线
  45. for (let i = 0; i < 3; i++) {
  46. const startX = Math.random() * width * 0.2;
  47. const startY = Math.random() * height;
  48. const endX = width - Math.random() * width * 0.2;
  49. const endY = Math.random() * height;
  50. const control1X = width * 0.3 + Math.random() * width * 0.2;
  51. const control1Y = Math.random() * height;
  52. const control2X = width * 0.5 + Math.random() * width * 0.2;
  53. const control2Y = Math.random() * height;
  54. // 使用贝塞尔曲线创建更自然的干扰线
  55. lines += `<path
  56. d="M ${startX} ${startY} C ${control1X} ${control1Y}, ${control2X} ${control2Y}, ${endX} ${endY}"
  57. stroke="rgba(255, 255, 255, ${0.2 + Math.random() * 0.3})"
  58. stroke-width="${1 + Math.random()}"
  59. fill="none"
  60. />`;
  61. }
  62. // 添加5-10个随机点
  63. for (let i = 0; i < 8; i++) {
  64. const x = Math.random() * width;
  65. const y = Math.random() * height;
  66. const radius = 1 + Math.random() * 2;
  67. lines += `<circle
  68. cx="${x}"
  69. cy="${y}"
  70. r="${radius}"
  71. fill="rgba(255, 255, 255, ${0.4 + Math.random() * 0.4})"
  72. />`;
  73. }
  74. return `
  75. <svg
  76. xmlns="http://www.w3.org/2000/svg"
  77. width="${width}"
  78. height="${height}"
  79. viewBox="0 0 ${width} ${height}"
  80. style="background-color: rgba(55, 65, 81, 0.5); border-radius: 6px; border: 1px solid rgba(107, 114, 128, 0.5);"
  81. >
  82. ${lines}
  83. ${textElements}
  84. </svg>
  85. `;
  86. }
  87. /**
  88. * 验证码组件
  89. * @param length 验证码长度
  90. * @returns 验证码相关状态和方法
  91. */
  92. export function useCaptcha(length: number = 4) {
  93. // 使用状态对象存储所有相关状态
  94. const state = reactive({
  95. captchaText: '', // 验证码文本
  96. userInput: '', // 用户输入
  97. svgHtml: '', // 验证码SVG
  98. error: '', // 错误信息
  99. });
  100. /**
  101. * 生成新的验证码
  102. */
  103. const generateCaptcha = () => {
  104. // 生成新的验证码文本
  105. const newText = generateRandomString(length);
  106. // 更新状态
  107. state.captchaText = newText;
  108. state.userInput = ''; // 清空用户输入
  109. state.error = ''; // 清除错误
  110. // 生成验证码SVG
  111. state.svgHtml = generateCaptchaSvg(newText);
  112. };
  113. /**
  114. * 验证用户输入
  115. * @returns 是否验证通过
  116. */
  117. const validateCaptcha = (): boolean => {
  118. // 确保userInput和captchaText都被处理为大写字符串进行比较
  119. const normalizedInput = (state.userInput || '').toUpperCase().trim();
  120. const normalizedCaptcha = (state.captchaText || '').toUpperCase().trim();
  121. if (!normalizedInput) {
  122. state.error = '请输入验证码';
  123. return false;
  124. }
  125. // 验证用户输入是否与验证码匹配
  126. const isValid = normalizedInput === normalizedCaptcha;
  127. if (!isValid) {
  128. state.error = '验证码错误';
  129. generateCaptcha(); // 错误后自动刷新
  130. return false;
  131. }
  132. state.error = '';
  133. return true;
  134. };
  135. /**
  136. * 获取当前验证码文本
  137. */
  138. const getCurrentCaptcha = () => {
  139. return state.captchaText;
  140. };
  141. // 初始化生成第一个验证码
  142. generateCaptcha();
  143. return {
  144. state,
  145. userInput: computed({
  146. get: () => state.userInput,
  147. set: (value) => { state.userInput = value; }
  148. }),
  149. captchaSvg: computed(() => state.svgHtml),
  150. error: computed(() => state.error),
  151. generateCaptcha,
  152. validateCaptcha,
  153. getCurrentCaptcha,
  154. };
  155. }