duqing2026 commited on
Commit
9b1b216
·
1 Parent(s): f16a23e

优化升级

Browse files
Files changed (3) hide show
  1. README.md +2 -0
  2. app.py +4 -0
  3. templates/index.html +103 -6
README.md CHANGED
@@ -16,6 +16,8 @@ short_description: '爆款视频封面大师'
16
  ## 功能特点
17
 
18
  - **多尺寸支持**:16:9 (视频), 4:3, 1:1, 9:16 (Shorts/抖音)
 
 
19
  - **爆款样式**:内置“震惊红”、“搞钱黄”、“科技蓝”等高点击配色
20
  - **拖拽编辑**:所有元素(标题、副标题、贴纸、人物)均支持自由拖拽
21
  - **隐私安全**:所有图片处理均在浏览器本地完成,不上传服务器
 
16
  ## 功能特点
17
 
18
  - **多尺寸支持**:16:9 (视频), 4:3, 1:1, 9:16 (Shorts/抖音)
19
+ - **随机灵感**:一键生成不同风格的封面配置,激发创意
20
+ - **丰富字体**:集成多款免费商用中文字体(快乐体、马善政、站酷黄油等)
21
  - **爆款样式**:内置“震惊红”、“搞钱黄”、“科技蓝”等高点击配色
22
  - **拖拽编辑**:所有元素(标题、副标题、贴纸、人物)均支持自由拖拽
23
  - **隐私安全**:所有图片处理均在浏览器本地完成,不上传服务器
app.py CHANGED
@@ -11,6 +11,10 @@ app.jinja_env.variable_end_string = ']]'
11
  def index():
12
  return render_template('index.html')
13
 
 
 
 
 
14
  @app.route('/static/<path:path>')
15
  def send_static(path):
16
  return send_from_directory('static', path)
 
11
  def index():
12
  return render_template('index.html')
13
 
14
+ @app.route('/health')
15
+ def health():
16
+ return 'OK', 200
17
+
18
  @app.route('/static/<path:path>')
19
  def send_static(path):
20
  return send_from_directory('static', path)
templates/index.html CHANGED
@@ -7,9 +7,16 @@
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
9
  <script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
10
- <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;700;900&display=swap" rel="stylesheet">
 
11
  <style>
12
  body { font-family: 'Noto Sans SC', sans-serif; }
 
 
 
 
 
 
13
  .canvas-container {
14
  box-shadow: 0 10px 30px -10px rgba(0,0,0,0.3);
15
  overflow: hidden;
@@ -48,14 +55,21 @@
48
  <h1 class="text-xl font-bold text-gray-800">爆款视频封面大师</h1>
49
  </div>
50
  <div class="flex gap-3">
 
 
 
51
  <button @click="resetConfig" class="px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded-lg transition">
52
  重置画布
53
  </button>
54
- <button @click="downloadImage" class="px-6 py-2 bg-black text-white rounded-lg font-bold hover:bg-gray-800 transition flex items-center gap-2 shadow-lg hover:shadow-xl transform hover:-translate-y-0.5">
55
- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
56
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
57
  </svg>
58
- 导出封面
 
 
 
 
59
  </button>
60
  </div>
61
  </header>
@@ -103,6 +117,17 @@
103
  <h3 class="text-xs font-bold text-gray-400 uppercase tracking-wider mb-3">主标题 (拖拽移动)</h3>
104
  <textarea v-model="config.mainTitle.text" rows="2" class="w-full border border-gray-300 rounded-lg p-2 text-lg font-bold mb-3 focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="输入爆款标题..."></textarea>
105
 
 
 
 
 
 
 
 
 
 
 
 
106
  <div class="grid grid-cols-2 gap-4">
107
  <div>
108
  <label class="text-xs text-gray-500 block mb-1">字号 {{ config.mainTitle.size }}px</label>
@@ -237,8 +262,8 @@
237
  </div>
238
 
239
  <!-- Main Title -->
240
- <div class="draggable-item absolute z-20 whitespace-pre-wrap font-black leading-tight"
241
- :class="{ 'active': activeElement === 'mainTitle' }"
242
  @mousedown="startDrag($event, 'mainTitle')"
243
  :style="{
244
  left: config.mainTitle.x + 'px',
@@ -531,6 +556,78 @@
531
  document.removeEventListener('mouseup', stopDrag);
532
  };
533
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
534
  return {
535
  config,
536
  ratios,
 
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
9
  <script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
10
+ <!-- 引入更多中文字体 -->
11
+ <link href="https://fonts.googleapis.com/css2?family=Ma+Shan+Zheng&family=Noto+Sans+SC:wght@400;700;900&family=Noto+Serif+SC:wght@700;900&family=ZCOOL+KuaiLe&family=ZCOOL+QingKe+HuangYou&display=swap" rel="stylesheet">
12
  <style>
13
  body { font-family: 'Noto Sans SC', sans-serif; }
14
+ .font-sans { font-family: 'Noto Sans SC', sans-serif; }
15
+ .font-serif { font-family: 'Noto Serif SC', serif; }
16
+ .font-kuai-le { font-family: 'ZCOOL KuaiLe', cursive; }
17
+ .font-ma-shan-zheng { font-family: 'Ma Shan Zheng', cursive; }
18
+ .font-huang-you { font-family: 'ZCOOL QingKe HuangYou', sans-serif; }
19
+
20
  .canvas-container {
21
  box-shadow: 0 10px 30px -10px rgba(0,0,0,0.3);
22
  overflow: hidden;
 
55
  <h1 class="text-xl font-bold text-gray-800">爆款视频封面大师</h1>
56
  </div>
57
  <div class="flex gap-3">
58
+ <button @click="randomTemplate" class="px-4 py-2 text-sm bg-purple-100 text-purple-700 hover:bg-purple-200 rounded-lg transition flex items-center gap-1">
59
+ <span class="text-lg">🎲</span> 随机灵感
60
+ </button>
61
  <button @click="resetConfig" class="px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded-lg transition">
62
  重置画布
63
  </button>
64
+ <button @click="downloadImage" :disabled="isDownloading" class="px-6 py-2 bg-black text-white rounded-lg font-bold hover:bg-gray-800 transition flex items-center gap-2 shadow-lg hover:shadow-xl transform hover:-translate-y-0.5 disabled:opacity-50 disabled:cursor-not-allowed">
65
+ <svg v-if="!isDownloading" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
66
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
67
  </svg>
68
+ <svg v-else class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
69
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
70
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
71
+ </svg>
72
+ {{ isDownloading ? '生成中...' : '导出封面' }}
73
  </button>
74
  </div>
75
  </header>
 
117
  <h3 class="text-xs font-bold text-gray-400 uppercase tracking-wider mb-3">主标题 (拖拽移动)</h3>
118
  <textarea v-model="config.mainTitle.text" rows="2" class="w-full border border-gray-300 rounded-lg p-2 text-lg font-bold mb-3 focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="输入爆款标题..."></textarea>
119
 
120
+ <div class="mb-3">
121
+ <label class="text-xs text-gray-500 block mb-1">字体风格</label>
122
+ <select v-model="config.mainTitle.fontFamily" class="w-full border border-gray-300 rounded-lg p-2 text-sm">
123
+ <option value="font-sans">无衬线黑体 (现代)</option>
124
+ <option value="font-serif">衬线宋体 (文艺)</option>
125
+ <option value="font-kuai-le">快乐体 (可爱)</option>
126
+ <option value="font-ma-shan-zheng">马善政毛笔 (国潮)</option>
127
+ <option value="font-huang-you">站酷黄油 (醒目)</option>
128
+ </select>
129
+ </div>
130
+
131
  <div class="grid grid-cols-2 gap-4">
132
  <div>
133
  <label class="text-xs text-gray-500 block mb-1">字号 {{ config.mainTitle.size }}px</label>
 
262
  </div>
263
 
264
  <!-- Main Title -->
265
+ <div class="draggable-item absolute z-20 whitespace-pre-wrap font-black leading-tight text-center"
266
+ :class="[{ 'active': activeElement === 'mainTitle' }, config.mainTitle.fontFamily]"
267
  @mousedown="startDrag($event, 'mainTitle')"
268
  :style="{
269
  left: config.mainTitle.x + 'px',
 
556
  document.removeEventListener('mouseup', stopDrag);
557
  };
558
 
559
+ return {
560
+ config,
561
+ ratios,
562
+ presets,
563
+ canvasStyle,
564
+ activeElement,
565
+ handleBgUpload,
566
+ handleFgUpload,
567
+ addSticker,
568
+ removeSticker,
569
+ applyTitleStyle,
570
+ resetConfig,
571
+ downloadImage,
572
+ startDrag,
573
+ randomTemplate,
574
+ isDownloading
575
+ };
576
+ }
577
+ }).mount('#app');
578
+ </script>
579
+ </body>
580
+ </html>
581
+ const rect = e.target.getBoundingClientRect();
582
+ // Just store the starting mouse position
583
+ dragOffset.startX = clientX;
584
+ dragOffset.startY = clientY;
585
+ dragOffset.initialObjX = currentX;
586
+ dragOffset.initialObjY = currentY;
587
+
588
+ document.addEventListener('mousemove', onDrag);
589
+ document.addEventListener('mouseup', stopDrag);
590
+ };
591
+
592
+ const onDrag = (e) => {
593
+ if (!isDragging) return;
594
+ e.preventDefault();
595
+
596
+ // Calculate scale factor from the transform
597
+ const element = document.getElementById('canvas');
598
+ // Parse scale from style string or computed style
599
+ const transform = element.style.transform;
600
+ const match = transform.match(/scale\(([^)]+)\)/);
601
+ const scale = match ? parseFloat(match[1]) : 1;
602
+
603
+ const dx = (e.clientX - dragOffset.startX) / scale;
604
+ const dy = (e.clientY - dragOffset.startY) / scale;
605
+
606
+ let newX = dragOffset.initialObjX + dx;
607
+ let newY = dragOffset.initialObjY + dy;
608
+
609
+ if (currentDragTarget === 'mainTitle') {
610
+ config.mainTitle.x = newX;
611
+ config.mainTitle.y = newY;
612
+ } else if (currentDragTarget === 'subTitle') {
613
+ config.subTitle.x = newX;
614
+ config.subTitle.y = newY;
615
+ } else if (currentDragTarget === 'fg') {
616
+ config.fgPos.x = newX;
617
+ config.fgPos.y = newY;
618
+ } else if (currentDragTarget.startsWith('sticker')) {
619
+ config.stickers[stickerIndex].x = newX;
620
+ config.stickers[stickerIndex].y = newY;
621
+ }
622
+ };
623
+
624
+ const stopDrag = () => {
625
+ isDragging = false;
626
+ currentDragTarget = null;
627
+ document.removeEventListener('mousemove', onDrag);
628
+ document.removeEventListener('mouseup', stopDrag);
629
+ };
630
+
631
  return {
632
  config,
633
  ratios,