| <!DOCTYPE html> |
| <html lang="zh-CN"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <meta name="referrer" content="no-referrer"> |
| <title>无印豆包</title> |
| <link rel="icon" type="image/png" sizes="16x16" href="icons/icon16.png"> |
| <link rel="icon" type="image/png" sizes="48x48" href="icons/icon48.png"> |
| <link rel="icon" type="image/png" sizes="128x128" href="icons/icon128.png"> |
| <script src="https://unpkg.com/vue@3.5.25/dist/vue.global.prod.js"></script> |
| <script src="https://unpkg.com/axios@1.12.0/dist/axios.min.js"></script> |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| |
| body { |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; |
| background: #ffffff; |
| min-height: 100vh; |
| color: #1f1f1f; |
| line-height: 1.5; |
| display: flex; |
| flex-direction: column; |
| } |
| |
| #app { |
| max-width: 1000px; |
| margin: 0 auto; |
| padding: 60px 24px 0 24px; |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| width: 100%; |
| } |
| |
| .header { |
| margin-bottom: 32px; |
| } |
| |
| .header-top { |
| display: flex; |
| justify-content: space-between; |
| align-items: baseline; |
| margin-bottom: 8px; |
| } |
| |
| .header h1 { |
| font-size: 32px; |
| font-weight: 600; |
| color: #1f1f1f; |
| } |
| |
| .tab-buttons { |
| display: flex; |
| gap: 12px; |
| margin-bottom: 16px; |
| } |
| |
| .tab-btn { |
| padding: 8px 16px; |
| border: 1px solid #e0e0e0; |
| border-radius: 6px; |
| background: #ffffff; |
| color: #6b6b6b; |
| font-size: 14px; |
| cursor: pointer; |
| transition: all 0.2s; |
| } |
| |
| .tab-btn.active { |
| background: #1f1f1f; |
| color: #ffffff; |
| border-color: #1f1f1f; |
| } |
| |
| .tab-btn:hover:not(.active) { |
| border-color: #1f1f1f; |
| color: #1f1f1f; |
| } |
| |
| .image-count { |
| font-size: 14px; |
| color: #6b6b6b; |
| } |
| |
| .image-count span { |
| font-weight: 600; |
| color: #1f1f1f; |
| } |
| |
| .header p { |
| font-size: 15px; |
| color: #6b6b6b; |
| } |
| |
| .main-card { |
| background: #ffffff; |
| } |
| |
| .input-section { |
| margin-bottom: 32px; |
| } |
| |
| .input-group { |
| display: flex; |
| gap: 12px; |
| margin-bottom: 16px; |
| } |
| |
| .input-field { |
| flex: 1; |
| padding: 10px 14px; |
| border: 1px solid #e0e0e0; |
| border-radius: 6px; |
| font-size: 15px; |
| background: #ffffff; |
| transition: border-color 0.2s; |
| } |
| |
| .input-with-clear { |
| position: relative; |
| flex: 1; |
| } |
| |
| .input-with-clear .input-field { |
| width: 100%; |
| padding-right: 36px; |
| } |
| |
| .clear-btn { |
| position: absolute; |
| right: 8px; |
| top: 50%; |
| transform: translateY(-50%); |
| width: 20px; |
| height: 20px; |
| border: none; |
| background: #d0d0d0; |
| color: #ffffff; |
| border-radius: 50%; |
| font-size: 14px; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| transition: all 0.2s; |
| } |
| |
| .clear-btn:hover { |
| background: #a0a0a0; |
| } |
| |
| .clear-btn.hidden { |
| display: none; |
| } |
| |
| .input-field:focus { |
| outline: none; |
| border-color: #1f1f1f; |
| } |
| |
| .input-field::placeholder { |
| color: #a0a0a0; |
| } |
| |
| .btn { |
| padding: 10px 20px; |
| border: none; |
| border-radius: 6px; |
| font-size: 15px; |
| font-weight: 500; |
| cursor: pointer; |
| transition: all 0.2s; |
| } |
| |
| .btn-primary { |
| background: #1f1f1f; |
| color: #ffffff; |
| } |
| |
| .btn-primary:hover:not(:disabled) { |
| background: #3a3a3a; |
| } |
| |
| .btn-primary:disabled { |
| background: #e0e0e0; |
| color: #a0a0a0; |
| cursor: not-allowed; |
| } |
| |
| .loading { |
| text-align: center; |
| padding: 48px 0; |
| color: #6b6b6b; |
| } |
| |
| .spinner { |
| border: 2px solid #f0f0f0; |
| border-top: 2px solid #1f1f1f; |
| border-radius: 50%; |
| width: 32px; |
| height: 32px; |
| animation: spin 0.8s linear infinite; |
| margin: 0 auto 16px; |
| } |
| |
| .stats { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); |
| gap: 16px; |
| margin: 32px 0; |
| display: none; |
| } |
| |
| .stat-card { |
| background: #f7f7f7; |
| padding: 20px; |
| border-radius: 6px; |
| } |
| |
| .stat-number { |
| font-size: 28px; |
| font-weight: 600; |
| margin-bottom: 4px; |
| color: #1f1f1f; |
| } |
| |
| .stat-label { |
| font-size: 14px; |
| color: #6b6b6b; |
| } |
| |
| .images-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); |
| gap: 16px; |
| margin-top: 32px; |
| } |
| |
| .image-card { |
| position: relative; |
| border-radius: 8px; |
| overflow: hidden; |
| border: 1px solid #e0e0e0; |
| background: #fafafa; |
| transition: border-color 0.2s; |
| } |
| |
| .image-card:hover { |
| border-color: #1f1f1f; |
| } |
| |
| .image-card:hover .image-info { |
| opacity: 1; |
| } |
| |
| .image-card img { |
| width: 100%; |
| height: 240px; |
| object-fit: cover; |
| display: block; |
| } |
| |
| .video-card { |
| position: relative; |
| border-radius: 8px; |
| overflow: hidden; |
| border: 1px solid #e0e0e0; |
| background: #000000; |
| transition: border-color 0.2s; |
| } |
| |
| .video-card:hover { |
| border-color: #1f1f1f; |
| } |
| |
| .videos-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); |
| gap: 16px; |
| margin-top: 32px; |
| } |
| |
| .video-card video { |
| width: 100%; |
| height: auto; |
| max-height: 480px; |
| object-fit: contain; |
| background: #000000; |
| display: block; |
| } |
| |
| .video-info { |
| position: absolute; |
| top: 8px; |
| right: 8px; |
| padding: 4px 8px; |
| background: rgba(0, 0, 0, 0.7); |
| border-radius: 4px; |
| font-size: 11px; |
| color: #ffffff; |
| font-weight: 500; |
| letter-spacing: 0.3px; |
| } |
| |
| .video-actions { |
| display: flex; |
| gap: 8px; |
| padding: 12px; |
| background: #ffffff; |
| border-top: 1px solid #e0e0e0; |
| } |
| |
| .image-info { |
| position: absolute; |
| top: 8px; |
| right: 8px; |
| padding: 4px 8px; |
| background: rgba(0, 0, 0, 0.7); |
| border-radius: 4px; |
| font-size: 11px; |
| color: #ffffff; |
| opacity: 0; |
| transition: opacity 0.2s; |
| font-weight: 500; |
| letter-spacing: 0.3px; |
| } |
| |
| .image-actions { |
| display: flex; |
| gap: 8px; |
| padding: 12px; |
| background: #ffffff; |
| border-top: 1px solid #e0e0e0; |
| } |
| |
| .action-btn { |
| flex: 1; |
| padding: 6px 12px; |
| border: 1px solid #e0e0e0; |
| border-radius: 4px; |
| background: #ffffff; |
| color: #1f1f1f; |
| font-size: 13px; |
| cursor: pointer; |
| transition: all 0.2s; |
| position: relative; |
| } |
| |
| .action-btn:hover { |
| background: #f7f7f7; |
| border-color: #1f1f1f; |
| } |
| |
| .action-btn.success { |
| background: #f0fdf4; |
| border-color: #166534; |
| color: #166534; |
| } |
| |
| .alert { |
| padding: 12px 16px; |
| border-radius: 6px; |
| margin-bottom: 16px; |
| font-size: 14px; |
| } |
| |
| .alert-error { |
| background: #fef2f2; |
| color: #991b1b; |
| border: 1px solid #fecaca; |
| } |
| |
| .empty-state { |
| text-align: center; |
| padding: 80px 20px; |
| color: #a0a0a0; |
| } |
| |
| .empty-state svg { |
| width: 64px; |
| height: 64px; |
| margin-bottom: 16px; |
| stroke: #d0d0d0; |
| } |
| |
| .empty-state h3 { |
| font-size: 16px; |
| font-weight: 500; |
| margin-bottom: 8px; |
| color: #6b6b6b; |
| } |
| |
| .empty-state p { |
| font-size: 14px; |
| color: #a0a0a0; |
| } |
| |
| @keyframes spin { |
| 0% { transform: rotate(0deg); } |
| 100% { transform: rotate(360deg); } |
| } |
| |
| @media (max-width: 768px) { |
| #app { |
| padding: 24px 16px; |
| } |
| |
| .header { |
| margin-bottom: 24px; |
| } |
| |
| .header-top { |
| flex-direction: column; |
| align-items: flex-start; |
| gap: 8px; |
| } |
| |
| .header h1 { |
| font-size: 24px; |
| } |
| |
| .header p { |
| font-size: 14px; |
| line-height: 1.6; |
| } |
| |
| .tab-buttons { |
| width: 100%; |
| gap: 8px; |
| } |
| |
| .tab-btn { |
| flex: 1; |
| padding: 10px 12px; |
| font-size: 15px; |
| } |
| |
| .input-group { |
| flex-direction: column; |
| gap: 12px; |
| } |
| |
| .btn { |
| width: 100%; |
| padding: 12px 20px; |
| font-size: 16px; |
| } |
| |
| .images-grid { |
| grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); |
| gap: 12px; |
| } |
| |
| .videos-grid { |
| grid-template-columns: 1fr; |
| gap: 12px; |
| } |
| |
| .image-card img { |
| height: 180px; |
| } |
| |
| .image-actions, |
| .video-actions { |
| flex-wrap: wrap; |
| gap: 6px; |
| padding: 10px; |
| } |
| |
| .action-btn { |
| font-size: 12px; |
| padding: 8px 10px; |
| } |
| |
| .image-info, |
| .video-info { |
| font-size: 10px; |
| padding: 3px 6px; |
| opacity: 1; |
| } |
| |
| .empty-state { |
| padding: 60px 20px; |
| } |
| |
| .empty-state svg { |
| width: 48px; |
| height: 48px; |
| } |
| |
| .empty-state h3 { |
| font-size: 15px; |
| } |
| |
| .empty-state p { |
| font-size: 13px; |
| } |
| } |
| |
| @media (max-width: 480px) { |
| #app { |
| padding: 20px 12px; |
| } |
| |
| .header h1 { |
| font-size: 20px; |
| } |
| |
| .header p { |
| font-size: 13px; |
| } |
| |
| .tab-btn { |
| padding: 8px 10px; |
| font-size: 14px; |
| } |
| |
| .input-field { |
| font-size: 14px; |
| padding: 10px 12px; |
| } |
| |
| .images-grid { |
| grid-template-columns: 1fr 1fr; |
| gap: 10px; |
| } |
| |
| .image-card img { |
| height: 160px; |
| } |
| |
| .action-btn { |
| flex: 1 1 calc(50% - 3px); |
| min-width: calc(50% - 3px); |
| } |
| |
| .image-count { |
| font-size: 13px; |
| } |
| } |
| |
| @media (hover: none) and (pointer: coarse) { |
| .tab-btn, |
| .btn, |
| .action-btn { |
| min-height: 44px; |
| } |
| |
| .input-field { |
| min-height: 44px; |
| } |
| |
| .image-info, |
| .video-info { |
| opacity: 1; |
| } |
| } |
| |
| .footer { |
| margin-top: auto; |
| padding: 48px 0 28px 0; |
| text-align: center; |
| } |
| |
| .footer-content { |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 16px; |
| font-size: 13px; |
| color: #a0a0a0; |
| } |
| |
| .footer-link { |
| display: inline-flex; |
| align-items: center; |
| gap: 6px; |
| color: #6b6b6b; |
| text-decoration: none; |
| font-size: 13px; |
| transition: all 0.15s ease; |
| } |
| |
| .footer-link:hover { |
| color: #1f1f1f; |
| } |
| |
| .footer-link svg { |
| width: 16px; |
| height: 16px; |
| opacity: 0.7; |
| transition: opacity 0.15s ease; |
| } |
| |
| .footer-link:hover svg { |
| opacity: 1; |
| } |
| |
| .footer-divider { |
| width: 1px; |
| height: 12px; |
| background: #e0e0e0; |
| } |
| |
| .footer-text { |
| color: #a0a0a0; |
| } |
| |
| @media (max-width: 768px) { |
| .footer { |
| padding: 40px 0 24px 0; |
| } |
| |
| .footer-content { |
| flex-direction: column; |
| gap: 8px; |
| } |
| |
| .footer-divider { |
| display: none; |
| } |
| |
| .footer-link { |
| font-size: 12px; |
| } |
| |
| .footer-link svg { |
| width: 14px; |
| height: 14px; |
| } |
| |
| .footer-text { |
| font-size: 12px; |
| } |
| } |
| </style> |
| </head> |
| <body> |
| <div id="app"> |
| <div class="header"> |
| <div class="header-top"> |
| <h1>无印豆包</h1> |
| <div v-if="currentTab === 'image' && images.length > 0" class="image-count"> |
| 已提取 <span>{{ images.length }}</span> 张图片 |
| </div> |
| <div v-if="currentTab === 'video' && videos.length > 0" class="image-count"> |
| 已提取 <span>{{ videos.length }}</span> 个视频 |
| </div> |
| </div> |
| <p>一键提取豆包/千问对话中的高清图片和视频,无水印、无压缩</p> |
| </div> |
|
|
| <div class="main-card"> |
| <div class="input-section"> |
| <div class="tab-buttons"> |
| <button |
| class="tab-btn" |
| :class="{ active: currentTab === 'image' }" |
| @click="switchTab('image')" |
| > |
| 图片解析 |
| </button> |
| <button |
| class="tab-btn" |
| :class="{ active: currentTab === 'video' }" |
| @click="switchTab('video')" |
| > |
| 视频解析 |
| </button> |
| </div> |
|
|
| <div v-if="error" class="alert alert-error"> |
| {{ error }} |
| </div> |
|
|
| <div class="input-group"> |
| <div class="input-with-clear"> |
| <input |
| v-model="url" |
| type="text" |
| class="input-field" |
| :placeholder="currentTab === 'image' ? '输入包含图片的豆包/千问对话分享链接,例如:https://www.doubao.com/thread/xxxxx' : '输入包含视频的豆包或云雀对话分享链接,例如:https://www.doubao.com/thread/xxxxx'" |
| @keyup.enter="parseUrl" |
| @input="toggleClearBtn" |
| > |
| <button |
| class="clear-btn" |
| :class="{ hidden: !url }" |
| @click="clearInput" |
| type="button" |
| > |
| × |
| </button> |
| </div> |
| <button |
| class="btn btn-primary" |
| @click="parseUrl" |
| :disabled="loading || !url" |
| > |
| {{ loading ? '解析中...' : '解析' }} |
| </button> |
| </div> |
| </div> |
|
|
| <div v-if="currentTab === 'image' && images.length > 0"> |
| <div class="images-grid"> |
| <div v-for="(image, index) in images" :key="index" class="image-card"> |
| <img :src="image.url" :alt="`图片 ${index + 1}`" loading="lazy"> |
| <div class="image-info"> |
| {{ image.width }} × {{ image.height }} |
| </div> |
| <div class="image-actions"> |
| <button class="action-btn" @click="viewImage(image.url)"> |
| 查看 |
| </button> |
| <button |
| class="action-btn" |
| :class="{ success: downloadingIndex === index }" |
| @click="downloadImage(image.url, index)" |
| > |
| {{ downloadingIndex === index ? '✓ 已下载' : '下载' }} |
| </button> |
| <button |
| class="action-btn" |
| :class="{ success: copiedIndex === index }" |
| @click="copyUrl(image.url, index)" |
| > |
| {{ copiedIndex === index ? '✓ 已复制' : '复制' }} |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <div v-if="currentTab === 'video' && videos.length > 0"> |
| <div :class="videos.length > 1 ? 'videos-grid' : ''"> |
| <div v-for="(video, index) in videos" :key="index" class="video-card"> |
| <video :src="video.url" :poster="video.poster_url" controls></video> |
| <div class="video-info"> |
| {{ video.width }} × {{ video.height }} | {{ video.definition }} |
| </div> |
| <div class="video-actions"> |
| <button class="action-btn" @click="viewVideo(video.url)"> |
| 查看 |
| </button> |
| <button |
| class="action-btn" |
| :class="{ success: videoDownloadingIndex === index }" |
| @click="downloadVideo(video.url, index)" |
| > |
| {{ videoDownloadingIndex === index ? '✓ 已下载' : '下载' }} |
| </button> |
| <button |
| class="action-btn" |
| :class="{ success: videoCopiedIndex === index }" |
| @click="copyVideoUrl(video.url, index)" |
| > |
| {{ videoCopiedIndex === index ? '✓ 已复制' : '复制' }} |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <div v-if="loading" class="loading"> |
| <div class="spinner"></div> |
| <p>解析中...</p> |
| </div> |
|
|
| <div v-if="!loading && currentTab === 'image' && images.length === 0" class="empty-state"> |
| <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" /> |
| </svg> |
| <h3>暂无数据</h3> |
| <p>输入链接并点击解析来提取图片</p> |
| </div> |
|
|
| <div v-if="!loading && currentTab === 'video' && videos.length === 0" class="empty-state"> |
| <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" /> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> |
| </svg> |
| <h3>暂无数据</h3> |
| <p>输入链接并点击解析来提取视频</p> |
| </div> |
| </div> |
|
|
| <div class="footer"> |
| <div class="footer-content"> |
| <a href="https://github.com/ihmily/doubao-nomark" target="_blank" class="footer-link"> |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"> |
| <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/> |
| </svg> |
| 开源项目 |
| </a> |
| <div class="footer-divider"></div> |
| <span class="footer-text">© 2026 无印豆包</span> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| const { createApp } = Vue; |
| |
| createApp({ |
| data() { |
| return { |
| currentTab: 'image', |
| url: '', |
| images: [], |
| videos: [], |
| loading: false, |
| error: '', |
| downloadingIndex: null, |
| copiedIndex: null, |
| videoDownloadingIndex: null, |
| videoCopiedIndex: null, |
| apiBaseUrl: window.location.origin, |
| urlRegex: /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g |
| } |
| }, |
| |
| methods: { |
| extractUrl(text) { |
| if (!text) return null; |
| const matches = text.match(this.urlRegex); |
| return matches ? matches[0] : null; |
| }, |
| async copyToClipboard(text) { |
| try { |
| if (navigator.clipboard && navigator.clipboard.writeText) { |
| await navigator.clipboard.writeText(text); |
| } else { |
| const textArea = document.createElement('textarea'); |
| textArea.value = text; |
| textArea.style.position = 'fixed'; |
| textArea.style.left = '-999999px'; |
| textArea.style.top = '-999999px'; |
| document.body.appendChild(textArea); |
| textArea.focus(); |
| textArea.select(); |
| try { |
| document.execCommand('copy'); |
| } finally { |
| document.body.removeChild(textArea); |
| } |
| } |
| return true; |
| } catch (error) { |
| return false; |
| } |
| }, |
| switchTab(tab) { |
| this.currentTab = tab; |
| this.url = ''; |
| this.error = ''; |
| if (tab === 'image') { |
| this.videos = []; |
| } else if (tab === 'video') { |
| this.images = []; |
| } |
| this.downloadingIndex = null; |
| this.copiedIndex = null; |
| this.videoDownloadingIndex = null; |
| this.videoCopiedIndex = null; |
| }, |
| async parseUrl() { |
| if (!this.url.trim()) { |
| this.showError('请输入包含链接的文本'); |
| return; |
| } |
| |
| const extractedUrl = this.extractUrl(this.url); |
| |
| if (!extractedUrl) { |
| this.showError('未在输入内容中找到有效的网址'); |
| return; |
| } |
| |
| this.loading = true; |
| this.error = ''; |
| this.images = []; |
| this.videos = []; |
| this.downloadingIndex = null; |
| this.copiedIndex = null; |
| this.videoDownloadingIndex = null; |
| this.videoCopiedIndex = null; |
| |
| try { |
| if (this.currentTab === 'image') { |
| const response = await axios.post(`${this.apiBaseUrl}/parse`, { |
| url: extractedUrl |
| }); |
| |
| if (response.data.success) { |
| this.images = response.data.images; |
| } else { |
| this.showError('解析失败,请检查链接是否正确'); |
| } |
| } else { |
| const response = await axios.post(`${this.apiBaseUrl}/parse-video`, { |
| url: extractedUrl |
| }); |
| |
| if (response.data.success) { |
| this.videos = response.data.videos; |
| } else { |
| this.showError('解析失败,请检查链接是否正确'); |
| } |
| } |
| } catch (error) { |
| console.error(error); |
| if (error.response) { |
| this.showError(error.response.data.detail || '解析失败'); |
| } else if (error.request) { |
| this.showError('无法连接到服务器,请确保后端服务正在运行'); |
| } else { |
| this.showError('发生未知错误'); |
| } |
| } finally { |
| this.loading = false; |
| } |
| }, |
| viewImage(url) { |
| window.open(url, '_blank'); |
| }, |
| async downloadImage(url, index) { |
| try { |
| this.downloadingIndex = index; |
| const response = await fetch(url); |
| const blob = await response.blob(); |
| const link = document.createElement('a'); |
| link.href = URL.createObjectURL(blob); |
| link.download = `doubao_image_${index + 1}.jpg`; |
| link.click(); |
| URL.revokeObjectURL(link.href); |
| |
| setTimeout(() => { |
| this.downloadingIndex = null; |
| }, 2000); |
| } catch (error) { |
| this.downloadingIndex = null; |
| this.showError(`图片 ${index + 1} 下载失败`); |
| } |
| }, |
| async copyUrl(url, index) { |
| const success = await this.copyToClipboard(url); |
| |
| if (success) { |
| this.copiedIndex = index; |
| setTimeout(() => { |
| this.copiedIndex = null; |
| }, 2000); |
| } else { |
| this.showError('复制失败,请手动复制'); |
| } |
| }, |
| viewVideo(url) { |
| window.open(url, '_blank'); |
| }, |
| async downloadVideo(url, index) { |
| try { |
| if (!url) { |
| this.showError('视频地址不存在'); |
| return; |
| } |
| this.videoDownloadingIndex = index; |
| const response = await fetch(url); |
| const blob = await response.blob(); |
| const link = document.createElement('a'); |
| link.href = URL.createObjectURL(blob); |
| link.download = `doubao_video_${index + 1}.mp4`; |
| link.click(); |
| URL.revokeObjectURL(link.href); |
| |
| setTimeout(() => { |
| this.videoDownloadingIndex = null; |
| }, 2000); |
| } catch (error) { |
| this.videoDownloadingIndex = null; |
| this.showError(`视频 ${index + 1} 下载失败`); |
| } |
| }, |
| async copyVideoUrl(url, index) { |
| if (!url) { |
| this.showError('视频地址不存在'); |
| return; |
| } |
| |
| const success = await this.copyToClipboard(url); |
| |
| if (success) { |
| this.videoCopiedIndex = index; |
| setTimeout(() => { |
| this.videoCopiedIndex = null; |
| }, 2000); |
| } else { |
| this.showError('复制失败,请手动复制'); |
| } |
| }, |
| showError(message) { |
| this.error = message; |
| setTimeout(() => this.error = '', 5000); |
| }, |
| clearInput() { |
| this.url = ''; |
| }, |
| toggleClearBtn() {} |
| }, |
| mounted() { |
| const savedUrl = localStorage.getItem('lastUrl'); |
| if (savedUrl) { |
| this.url = savedUrl; |
| } |
| const savedTab = localStorage.getItem('lastTab'); |
| if (savedTab) { |
| this.currentTab = savedTab; |
| } |
| }, |
| watch: { |
| url(newUrl) { |
| if (newUrl) { |
| localStorage.setItem('lastUrl', newUrl); |
| } |
| }, |
| currentTab(newTab) { |
| localStorage.setItem('lastTab', newTab); |
| } |
| } |
| }).mount('#app'); |
| </script> |
| </body> |
| </html> |
|
|