优化结果
This commit is contained in:
132
InfoGenie-frontend/public/60sapi/实用功能/生成二维码/css/background.css
Normal file
132
InfoGenie-frontend/public/60sapi/实用功能/生成二维码/css/background.css
Normal file
@@ -0,0 +1,132 @@
|
||||
/* 背景样式文件 - 独立分离便于迁移 */
|
||||
|
||||
/* 主背景渐变 */
|
||||
body {
|
||||
background: linear-gradient(135deg,
|
||||
#e8f5e8 0%,
|
||||
#f1f8e9 25%,
|
||||
#e8f5e8 50%,
|
||||
#c8e6c9 75%,
|
||||
#e8f5e8 100%);
|
||||
background-size: 400% 400%;
|
||||
animation: backgroundShift 15s ease-in-out infinite;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 背景动画 */
|
||||
@keyframes backgroundShift {
|
||||
0%, 100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
25% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 50% 100%;
|
||||
}
|
||||
75% {
|
||||
background-position: 50% 0%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 背景装饰元素 */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 20%, rgba(76, 175, 80, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 80%, rgba(129, 199, 132, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 40% 60%, rgba(165, 214, 167, 0.08) 0%, transparent 50%);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
animation: floatingBubbles 20s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes floatingBubbles {
|
||||
0%, 100% {
|
||||
transform: translateY(0px) rotate(0deg);
|
||||
opacity: 1;
|
||||
}
|
||||
33% {
|
||||
transform: translateY(-20px) rotate(120deg);
|
||||
opacity: 0.8;
|
||||
}
|
||||
66% {
|
||||
transform: translateY(10px) rotate(240deg);
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
/* 背景粒子效果 */
|
||||
body::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
radial-gradient(2px 2px at 20px 30px, rgba(76, 175, 80, 0.3), transparent),
|
||||
radial-gradient(2px 2px at 40px 70px, rgba(129, 199, 132, 0.2), transparent),
|
||||
radial-gradient(1px 1px at 90px 40px, rgba(165, 214, 167, 0.3), transparent),
|
||||
radial-gradient(1px 1px at 130px 80px, rgba(76, 175, 80, 0.2), transparent),
|
||||
radial-gradient(2px 2px at 160px 30px, rgba(129, 199, 132, 0.3), transparent);
|
||||
background-repeat: repeat;
|
||||
background-size: 200px 100px;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
animation: particleFloat 25s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes particleFloat {
|
||||
0% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-100px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式背景调整 */
|
||||
@media (max-width: 768px) {
|
||||
body::after {
|
||||
background-size: 150px 75px;
|
||||
animation-duration: 20s;
|
||||
}
|
||||
|
||||
body::before {
|
||||
animation-duration: 15s;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
body::after {
|
||||
background-size: 100px 50px;
|
||||
animation-duration: 15s;
|
||||
}
|
||||
|
||||
body::before {
|
||||
animation-duration: 12s;
|
||||
}
|
||||
|
||||
body {
|
||||
animation-duration: 12s;
|
||||
}
|
||||
}
|
||||
|
||||
/* 高性能模式 - 减少动画 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
body,
|
||||
body::before,
|
||||
body::after {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(135deg, #e8f5e8 0%, #f1f8e9 50%, #c8e6c9 100%);
|
||||
}
|
||||
}
|
||||
468
InfoGenie-frontend/public/60sapi/实用功能/生成二维码/css/style.css
Normal file
468
InfoGenie-frontend/public/60sapi/实用功能/生成二维码/css/style.css
Normal file
@@ -0,0 +1,468 @@
|
||||
/* 基础样式重置 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #2d5a3d;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* 容器样式 */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
padding: 40px 20px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 8px 32px rgba(76, 175, 80, 0.1);
|
||||
border: 1px solid rgba(76, 175, 80, 0.2);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, transparent, #4caf50, transparent);
|
||||
animation: headerGlow 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes headerGlow {
|
||||
0% { left: -100%; }
|
||||
50% { left: 100%; }
|
||||
100% { left: 100%; }
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, #4caf50, #81c784, #4caf50);
|
||||
background-size: 200% 200%;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin-bottom: 10px;
|
||||
animation: titleGradient 4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes titleGradient {
|
||||
0%, 100% { background-position: 0% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 1.1rem;
|
||||
color: #66bb6a;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* 主要内容区域 */
|
||||
.main {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 40px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
/* 表单容器 */
|
||||
.form-container {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
padding: 35px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 8px 32px rgba(76, 175, 80, 0.1);
|
||||
border: 1px solid rgba(76, 175, 80, 0.2);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.form-container:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 12px 40px rgba(76, 175, 80, 0.15);
|
||||
}
|
||||
|
||||
/* 表单样式 */
|
||||
.qr-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
font-weight: 600;
|
||||
color: #2d5a3d;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
textarea, select {
|
||||
padding: 12px 16px;
|
||||
border: 2px solid rgba(76, 175, 80, 0.3);
|
||||
border-radius: 12px;
|
||||
font-size: 1rem;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
transition: all 0.3s ease;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 100px;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
textarea:focus, select:focus {
|
||||
outline: none;
|
||||
border-color: #4caf50;
|
||||
box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);
|
||||
background: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
.options-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.generate-btn {
|
||||
padding: 15px 30px;
|
||||
background: linear-gradient(135deg, #4caf50, #66bb6a);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.generate-btn::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
transition: left 0.5s ease;
|
||||
}
|
||||
|
||||
.generate-btn:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.generate-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
|
||||
.generate-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.generate-btn.loading .btn-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.generate-btn.loading .btn-loading {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.btn-loading {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 结果容器 */
|
||||
.result-container {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
padding: 35px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 8px 32px rgba(76, 175, 80, 0.1);
|
||||
border: 1px solid rgba(76, 175, 80, 0.2);
|
||||
min-height: 400px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.loading {
|
||||
text-align: center;
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 4px solid rgba(76, 175, 80, 0.2);
|
||||
border-top: 4px solid #4caf50;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 错误样式 */
|
||||
.error {
|
||||
text-align: center;
|
||||
color: #d32f2f;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
padding: 10px 20px;
|
||||
background: #4caf50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.retry-btn:hover {
|
||||
background: #45a049;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* 结果显示 */
|
||||
.result {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.qr-display {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.qr-display img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(76, 175, 80, 0.2);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.qr-display img:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 8px 30px rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
|
||||
.result-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.result-text {
|
||||
font-size: 1rem;
|
||||
color: #666;
|
||||
word-break: break-all;
|
||||
background: rgba(76, 175, 80, 0.1);
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #4caf50;
|
||||
}
|
||||
|
||||
.result-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.download-btn, .copy-btn, .new-btn {
|
||||
padding: 10px 16px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
background: #4caf50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: #2196f3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.new-btn {
|
||||
background: #ff9800;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.download-btn:hover, .copy-btn:hover, .new-btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* 隐藏类 */
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 页脚样式 */
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
padding: 25px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(15px);
|
||||
border-radius: 15px;
|
||||
color: #66bb6a;
|
||||
font-size: 0.9rem;
|
||||
border: 1px solid rgba(76, 175, 80, 0.1);
|
||||
}
|
||||
|
||||
/* 平板端适配 (768px - 1024px) */
|
||||
@media (max-width: 1024px) and (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 95%;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.main {
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
.form-container, .result-container {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.options-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机端适配 (最大768px) */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 25px;
|
||||
padding: 25px 15px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.main {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.form-container, .result-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.options-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.result-actions {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.download-btn, .copy-btn, .new-btn {
|
||||
width: 100%;
|
||||
max-width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 小屏手机适配 (最大480px) */
|
||||
@media (max-width: 480px) {
|
||||
.container {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20px 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.form-container, .result-container {
|
||||
padding: 15px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.generate-btn {
|
||||
padding: 12px 20px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.qr-display img {
|
||||
max-width: 90%;
|
||||
}
|
||||
}
|
||||
98
InfoGenie-frontend/public/60sapi/实用功能/生成二维码/index.html
Normal file
98
InfoGenie-frontend/public/60sapi/实用功能/生成二维码/index.html
Normal file
@@ -0,0 +1,98 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>二维码生成器</title>
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<link rel="stylesheet" href="css/background.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<h1>🔗 二维码生成器</h1>
|
||||
<p>快速生成高质量二维码</p>
|
||||
</header>
|
||||
|
||||
<main class="main">
|
||||
<div class="form-container">
|
||||
<form id="qrForm" class="qr-form">
|
||||
<div class="input-group">
|
||||
<label for="text">文本内容</label>
|
||||
<textarea id="text" name="text" placeholder="请输入要生成二维码的文本或URL" required></textarea>
|
||||
</div>
|
||||
|
||||
<div class="options-grid">
|
||||
<div class="input-group">
|
||||
<label for="size">尺寸大小</label>
|
||||
<select id="size" name="size">
|
||||
<option value="128">128x128</option>
|
||||
<option value="256" selected>256x256</option>
|
||||
<option value="512">512x512</option>
|
||||
<option value="1024">1024x1024</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<label for="level">容错级别</label>
|
||||
<select id="level" name="level">
|
||||
<option value="L">L - 低 (7%)</option>
|
||||
<option value="M" selected>M - 中 (15%)</option>
|
||||
<option value="Q">Q - 高 (25%)</option>
|
||||
<option value="H">H - 最高 (30%)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<label for="encoding">返回格式</label>
|
||||
<select id="encoding" name="encoding">
|
||||
<option value="image" selected>图片</option>
|
||||
<option value="json">JSON</option>
|
||||
<option value="text">文本</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="generate-btn">
|
||||
<span class="btn-text">生成二维码</span>
|
||||
<span class="btn-loading">生成中...</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="result-container">
|
||||
<div id="loading" class="loading hidden">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>正在生成二维码...</p>
|
||||
</div>
|
||||
|
||||
<div id="error" class="error hidden">
|
||||
<div class="error-icon">⚠️</div>
|
||||
<p class="error-message"></p>
|
||||
<button class="retry-btn">重试</button>
|
||||
</div>
|
||||
|
||||
<div id="result" class="result hidden">
|
||||
<div class="qr-display">
|
||||
<img id="qrImage" src="" alt="生成的二维码">
|
||||
</div>
|
||||
<div class="result-info">
|
||||
<p class="result-text"></p>
|
||||
<div class="result-actions">
|
||||
<button class="download-btn">下载二维码</button>
|
||||
<button class="copy-btn">复制链接</button>
|
||||
<button class="new-btn">生成新的</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<p>© 2024 二维码生成器 - 简单快捷的二维码生成工具</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="js/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
453
InfoGenie-frontend/public/60sapi/实用功能/生成二维码/js/script.js
Normal file
453
InfoGenie-frontend/public/60sapi/实用功能/生成二维码/js/script.js
Normal file
@@ -0,0 +1,453 @@
|
||||
// 二维码生成器 JavaScript
|
||||
class QRCodeGenerator {
|
||||
constructor() {
|
||||
this.apiEndpoints = [];
|
||||
this.currentApiIndex = 0;
|
||||
this.init();
|
||||
}
|
||||
|
||||
// 初始化
|
||||
async init() {
|
||||
await this.loadApiEndpoints();
|
||||
this.bindEvents();
|
||||
this.setupFormValidation();
|
||||
}
|
||||
|
||||
// 加载API接口列表
|
||||
async loadApiEndpoints() {
|
||||
try {
|
||||
// 直接在代码中配置API接口,避免CORS问题
|
||||
this.apiEndpoints = [
|
||||
"https://60s.api.shumengya.top"
|
||||
];
|
||||
console.log('已加载API接口:', this.apiEndpoints);
|
||||
} catch (error) {
|
||||
console.error('加载API接口失败:', error);
|
||||
this.showError('加载配置失败,请刷新页面重试');
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
bindEvents() {
|
||||
const form = document.getElementById('qrForm');
|
||||
const retryBtn = document.querySelector('.retry-btn');
|
||||
const downloadBtn = document.querySelector('.download-btn');
|
||||
const copyBtn = document.querySelector('.copy-btn');
|
||||
const newBtn = document.querySelector('.new-btn');
|
||||
const textArea = document.getElementById('text');
|
||||
|
||||
if (form) {
|
||||
form.addEventListener('submit', (e) => this.handleSubmit(e));
|
||||
}
|
||||
if (retryBtn) {
|
||||
retryBtn.addEventListener('click', () => this.retryGeneration());
|
||||
}
|
||||
if (downloadBtn) {
|
||||
downloadBtn.addEventListener('click', () => this.downloadQRCode());
|
||||
}
|
||||
if (copyBtn) {
|
||||
copyBtn.addEventListener('click', () => this.copyImageLink());
|
||||
}
|
||||
if (newBtn) {
|
||||
newBtn.addEventListener('click', () => this.resetForm());
|
||||
}
|
||||
if (textArea) {
|
||||
textArea.addEventListener('input', () => this.updateCharCount());
|
||||
}
|
||||
}
|
||||
|
||||
// 设置表单验证
|
||||
setupFormValidation() {
|
||||
const textArea = document.getElementById('text');
|
||||
const form = document.getElementById('qrForm');
|
||||
|
||||
textArea.addEventListener('blur', () => {
|
||||
if (textArea.value.trim() === '') {
|
||||
this.showFieldError(textArea, '请输入要生成二维码的内容');
|
||||
} else {
|
||||
this.clearFieldError(textArea);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 显示字段错误
|
||||
showFieldError(field, message) {
|
||||
this.clearFieldError(field);
|
||||
field.style.borderColor = '#d32f2f';
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'field-error';
|
||||
errorDiv.style.color = '#d32f2f';
|
||||
errorDiv.style.fontSize = '0.8rem';
|
||||
errorDiv.style.marginTop = '5px';
|
||||
errorDiv.textContent = message;
|
||||
field.parentNode.appendChild(errorDiv);
|
||||
}
|
||||
|
||||
// 清除字段错误
|
||||
clearFieldError(field) {
|
||||
field.style.borderColor = '';
|
||||
const errorDiv = field.parentNode.querySelector('.field-error');
|
||||
if (errorDiv) {
|
||||
errorDiv.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 更新字符计数
|
||||
updateCharCount() {
|
||||
const textArea = document.getElementById('text');
|
||||
const text = textArea.value;
|
||||
const length = text.length;
|
||||
|
||||
// 移除旧的计数显示
|
||||
const oldCounter = textArea.parentNode.querySelector('.char-counter');
|
||||
if (oldCounter) oldCounter.remove();
|
||||
|
||||
// 添加新的计数显示
|
||||
if (length > 0) {
|
||||
const counter = document.createElement('div');
|
||||
counter.className = 'char-counter';
|
||||
counter.style.fontSize = '0.8rem';
|
||||
counter.style.color = '#666';
|
||||
counter.style.textAlign = 'right';
|
||||
counter.style.marginTop = '5px';
|
||||
counter.textContent = `${length} 个字符`;
|
||||
textArea.parentNode.appendChild(counter);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理表单提交
|
||||
async handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(e.target);
|
||||
const params = {
|
||||
text: formData.get('text').trim(),
|
||||
size: formData.get('size'),
|
||||
level: formData.get('level'),
|
||||
encoding: formData.get('encoding')
|
||||
};
|
||||
|
||||
// 验证输入
|
||||
if (!params.text) {
|
||||
this.showFieldError(document.getElementById('text'), '请输入要生成二维码的内容');
|
||||
return;
|
||||
}
|
||||
|
||||
this.showLoading();
|
||||
await this.generateQRCode(params);
|
||||
}
|
||||
|
||||
// 生成二维码
|
||||
async generateQRCode(params) {
|
||||
let success = false;
|
||||
let lastError = null;
|
||||
|
||||
// 尝试所有API接口
|
||||
for (let i = 0; i < this.apiEndpoints.length; i++) {
|
||||
const apiIndex = (this.currentApiIndex + i) % this.apiEndpoints.length;
|
||||
const apiUrl = this.apiEndpoints[apiIndex];
|
||||
|
||||
try {
|
||||
console.log(`尝试API ${apiIndex + 1}:`, apiUrl);
|
||||
const result = await this.callAPI(apiUrl, params);
|
||||
|
||||
if (result.success) {
|
||||
this.currentApiIndex = apiIndex; // 记录成功的API
|
||||
this.showResult(result.data, params);
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`API ${apiIndex + 1} 失败:`, error);
|
||||
lastError = error;
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
this.showError(lastError?.message || '所有API接口都无法访问,请稍后重试');
|
||||
}
|
||||
}
|
||||
|
||||
// 调用API
|
||||
async callAPI(baseUrl, params) {
|
||||
const url = new URL('/v2/qrcode', baseUrl);
|
||||
|
||||
// 添加查询参数
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value !== null && value !== undefined && value !== '') {
|
||||
url.searchParams.append(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('请求URL:', url.toString());
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
|
||||
|
||||
try {
|
||||
const response = await fetch(url.toString(), {
|
||||
method: 'GET',
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
'Accept': 'application/json, image/*'
|
||||
}
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
// 根据返回格式处理
|
||||
if (params.encoding === 'image' || !params.encoding) {
|
||||
// 默认返回图片格式
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (contentType && contentType.startsWith('image/')) {
|
||||
const blob = await response.blob();
|
||||
const imageUrl = URL.createObjectURL(blob);
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
imageUrl: imageUrl,
|
||||
text: params.text,
|
||||
size: params.size,
|
||||
level: params.level,
|
||||
format: 'image'
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// 如果返回的不是图片,尝试解析JSON
|
||||
const jsonData = await response.json();
|
||||
if (jsonData.code === 0 && jsonData.data && jsonData.data.data_uri) {
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
imageUrl: jsonData.data.data_uri,
|
||||
text: params.text,
|
||||
size: params.size,
|
||||
level: params.level,
|
||||
format: 'json',
|
||||
base64: jsonData.data.base64,
|
||||
mimeType: jsonData.data.mime_type
|
||||
}
|
||||
};
|
||||
} else {
|
||||
throw new Error(jsonData.message || '生成失败');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// JSON或text格式
|
||||
const jsonData = await response.json();
|
||||
if (jsonData.code === 0 && jsonData.data) {
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
imageUrl: jsonData.data.data_uri,
|
||||
text: params.text,
|
||||
size: params.size,
|
||||
level: params.level,
|
||||
format: params.encoding,
|
||||
base64: jsonData.data.base64,
|
||||
mimeType: jsonData.data.mime_type
|
||||
}
|
||||
};
|
||||
} else {
|
||||
throw new Error(jsonData.message || '生成失败');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId);
|
||||
if (error.name === 'AbortError') {
|
||||
throw new Error('请求超时,请重试');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示加载状态
|
||||
showLoading() {
|
||||
this.hideAllStates();
|
||||
document.getElementById('loading').classList.remove('hidden');
|
||||
|
||||
const btn = document.querySelector('.generate-btn');
|
||||
btn.classList.add('loading');
|
||||
btn.disabled = true;
|
||||
}
|
||||
|
||||
// 显示错误
|
||||
showError(message) {
|
||||
this.hideAllStates();
|
||||
const errorDiv = document.getElementById('error');
|
||||
const errorMessage = errorDiv.querySelector('.error-message');
|
||||
errorMessage.textContent = message;
|
||||
errorDiv.classList.remove('hidden');
|
||||
|
||||
this.resetButton();
|
||||
}
|
||||
|
||||
// 显示结果
|
||||
showResult(data, params) {
|
||||
this.hideAllStates();
|
||||
|
||||
const resultDiv = document.getElementById('result');
|
||||
const qrImage = document.getElementById('qrImage');
|
||||
const resultText = document.querySelector('.result-text');
|
||||
|
||||
qrImage.src = data.imageUrl;
|
||||
qrImage.alt = `二维码: ${data.text}`;
|
||||
|
||||
resultText.innerHTML = `
|
||||
<strong>内容:</strong> ${this.escapeHtml(data.text)}<br>
|
||||
<strong>尺寸:</strong> ${data.size}x${data.size}<br>
|
||||
<strong>容错级别:</strong> ${data.level}<br>
|
||||
<strong>格式:</strong> ${data.format.toUpperCase()}
|
||||
`;
|
||||
|
||||
resultDiv.classList.remove('hidden');
|
||||
this.resetButton();
|
||||
|
||||
// 保存数据供下载使用
|
||||
this.currentQRData = data;
|
||||
}
|
||||
|
||||
// 隐藏所有状态
|
||||
hideAllStates() {
|
||||
document.getElementById('loading').classList.add('hidden');
|
||||
document.getElementById('error').classList.add('hidden');
|
||||
document.getElementById('result').classList.add('hidden');
|
||||
}
|
||||
|
||||
// 重置按钮状态
|
||||
resetButton() {
|
||||
const btn = document.querySelector('.generate-btn');
|
||||
btn.classList.remove('loading');
|
||||
btn.disabled = false;
|
||||
}
|
||||
|
||||
// 重试生成
|
||||
async retryGeneration() {
|
||||
const form = document.getElementById('qrForm');
|
||||
const formData = new FormData(form);
|
||||
const params = {
|
||||
text: formData.get('text').trim(),
|
||||
size: formData.get('size'),
|
||||
level: formData.get('level'),
|
||||
encoding: formData.get('encoding')
|
||||
};
|
||||
|
||||
this.showLoading();
|
||||
await this.generateQRCode(params);
|
||||
}
|
||||
|
||||
// 下载二维码
|
||||
downloadQRCode() {
|
||||
if (!this.currentQRData) return;
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = this.currentQRData.imageUrl;
|
||||
link.download = `qrcode_${Date.now()}.png`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
this.showToast('二维码已下载');
|
||||
}
|
||||
|
||||
// 复制图片链接
|
||||
async copyImageLink() {
|
||||
if (!this.currentQRData) return;
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(this.currentQRData.imageUrl);
|
||||
this.showToast('链接已复制到剪贴板');
|
||||
} catch (error) {
|
||||
console.error('复制失败:', error);
|
||||
this.showToast('复制失败,请手动复制');
|
||||
}
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
resetForm() {
|
||||
document.getElementById('qrForm').reset();
|
||||
this.hideAllStates();
|
||||
this.currentQRData = null;
|
||||
|
||||
// 清除字符计数
|
||||
const counter = document.querySelector('.char-counter');
|
||||
if (counter) counter.remove();
|
||||
|
||||
// 清除字段错误
|
||||
document.querySelectorAll('input, textarea, select').forEach(field => {
|
||||
this.clearFieldError(field);
|
||||
});
|
||||
|
||||
// 聚焦到文本框
|
||||
document.getElementById('text').focus();
|
||||
}
|
||||
|
||||
// 显示提示消息
|
||||
showToast(message) {
|
||||
// 移除旧的toast
|
||||
const oldToast = document.querySelector('.toast');
|
||||
if (oldToast) oldToast.remove();
|
||||
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'toast';
|
||||
toast.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: #4caf50;
|
||||
color: white;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
||||
z-index: 1000;
|
||||
animation: slideIn 0.3s ease;
|
||||
`;
|
||||
toast.textContent = message;
|
||||
|
||||
document.body.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.style.animation = 'slideOut 0.3s ease';
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// HTML转义
|
||||
escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加CSS动画
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes slideIn {
|
||||
from { transform: translateX(100%); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
@keyframes slideOut {
|
||||
from { transform: translateX(0); opacity: 1; }
|
||||
to { transform: translateX(100%); opacity: 0; }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new QRCodeGenerator();
|
||||
});
|
||||
|
||||
// 错误处理
|
||||
window.addEventListener('error', (e) => {
|
||||
console.error('全局错误:', e.error);
|
||||
});
|
||||
|
||||
window.addEventListener('unhandledrejection', (e) => {
|
||||
console.error('未处理的Promise拒绝:', e.reason);
|
||||
});
|
||||
3
InfoGenie-frontend/public/60sapi/实用功能/生成二维码/接口集合.json
Normal file
3
InfoGenie-frontend/public/60sapi/实用功能/生成二维码/接口集合.json
Normal file
@@ -0,0 +1,3 @@
|
||||
[
|
||||
"https://60s.api.shumengya.top"
|
||||
]
|
||||
Reference in New Issue
Block a user