Explorar el Código

pwa功能修复,分享链接功能兼容性适配,增加npm scp发布文件

wilhelm wong hace 2 meses
padre
commit
42f059c000

+ 124 - 0
deploy-npm.ps1

@@ -0,0 +1,124 @@
+# PowerShell Deployment Script using npm
+Write-Host "Starting PWA deployment with npm..." -ForegroundColor Green
+
+try {
+    # Pull latest code
+    Write-Host "Pulling latest code..." -ForegroundColor Yellow
+    & "C:\Program Files\Git\bin\git.exe" pull origin main
+    if ($LASTEXITCODE -ne 0) {
+        throw "Git pull failed"
+    }
+
+    # Clean old build files
+    Write-Host "Cleaning old build files..." -ForegroundColor Yellow
+    if (Test-Path "dist") {
+        Remove-Item -Recurse -Force "dist"
+    }
+
+    # Clean node_modules if exists
+    if (Test-Path "node_modules") {
+        Write-Host "Cleaning node_modules..." -ForegroundColor Yellow
+        Remove-Item -Recurse -Force "node_modules" -ErrorAction SilentlyContinue
+    }
+
+    # Remove lock files
+    if (Test-Path "package-lock.json") {
+        Remove-Item "package-lock.json" -ErrorAction SilentlyContinue
+    }
+    if (Test-Path "yarn.lock") {
+        Remove-Item "yarn.lock" -ErrorAction SilentlyContinue
+    }
+
+    # Install dependencies with npm
+    Write-Host "Installing dependencies with npm..." -ForegroundColor Yellow
+    npm install
+    if ($LASTEXITCODE -ne 0) {
+        throw "Dependency installation failed"
+    }
+
+    # Build production version
+    Write-Host "Building production version..." -ForegroundColor Yellow
+    npm run build
+    if ($LASTEXITCODE -ne 0) {
+        throw "Build failed"
+    }
+
+    # Check build results
+    Write-Host "Checking build results..." -ForegroundColor Yellow
+    if (-not (Test-Path "dist")) {
+        throw "Build failed, dist directory does not exist"
+    }
+
+    # Deploy to server
+    Write-Host "Deploying to server..." -ForegroundColor Yellow
+    
+    # Try different upload methods
+    $uploadSuccess = $false
+    
+    # Try rsync first
+    try {
+        & "C:\Program Files\Git\bin\rsync.exe" --version | Out-Null
+        Write-Host "Using rsync to upload..." -ForegroundColor Cyan
+        & "C:\Program Files\Git\bin\rsync.exe" --exclude='node_modules/' -ravzh --delete -e "ssh -o StrictHostKeyChecking=no" ./dist/ junma-server:/var/www/junma-show/
+        if ($LASTEXITCODE -eq 0) {
+            $uploadSuccess = $true
+        }
+    }
+    catch {
+        Write-Host "rsync not available, trying scp..." -ForegroundColor Yellow
+    }
+    
+    # Try scp if rsync failed
+    if (-not $uploadSuccess) {
+        try {
+            # Try different scp paths
+            $scpPaths = @(
+                "C:\Program Files\Git\usr\bin\scp.exe",
+                "C:\Program Files\Git\bin\scp.exe",
+                "scp"
+            )
+            
+            foreach ($scpPath in $scpPaths) {
+                try {
+                    if ($scpPath -eq "scp") {
+                        & $scpPath -r -o StrictHostKeyChecking=no ./dist/* junma-server:/var/www/junma-show/
+                    } else {
+                        if (Test-Path $scpPath) {
+                            & $scpPath -r -o StrictHostKeyChecking=no ./dist/* junma-server:/var/www/junma-show/
+                        } else {
+                            continue
+                        }
+                    }
+                    
+                    if ($LASTEXITCODE -eq 0) {
+                        Write-Host "Upload successful with: $scpPath" -ForegroundColor Green
+                        $uploadSuccess = $true
+                        break
+                    }
+                }
+                catch {
+                    continue
+                }
+            }
+        }
+        catch {
+            Write-Host "All upload methods failed" -ForegroundColor Red
+        }
+    }
+    
+    if (-not $uploadSuccess) {
+        throw "File upload failed - no working upload method found"
+    }
+    
+    if ($LASTEXITCODE -ne 0) {
+        throw "File upload failed"
+    }
+
+    Write-Host "Deployment completed!" -ForegroundColor Green
+    Write-Host "Application URL: https://your-domain.com" -ForegroundColor Cyan
+    Write-Host "PWA features enabled" -ForegroundColor Cyan
+}
+catch {
+    Write-Host "Deployment failed: $($_.Exception.Message)" -ForegroundColor Red
+    exit 1
+}

+ 305 - 0
src/components/DesktopAddGuide.vue

@@ -0,0 +1,305 @@
+<template>
+  <div class="desktop-add-guide">
+    <!-- 主要添加方案 -->
+    <div class="main-solutions">
+      <h3 class="text-lg font-semibold text-white/90 mb-4">快速添加到桌面</h3>
+      
+      <!-- 方案列表 -->
+      <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
+        <!-- Web Shortcuts API -->
+        <div v-if="capabilities.hasWebShortcuts" 
+             class="solution-card bg-blue-500/10 border border-blue-500/20">
+          <div class="flex items-center gap-3 mb-3">
+            <div class="w-10 h-10 rounded-full bg-blue-500/20 flex items-center justify-center">
+              <svg class="w-5 h-5 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
+              </svg>
+            </div>
+            <div>
+              <h4 class="font-medium text-blue-200">一键添加快捷方式</h4>
+              <p class="text-sm text-blue-300/80">最新浏览器支持</p>
+            </div>
+          </div>
+          <button @click="addWebShortcut" 
+                  :disabled="isAdding"
+                  class="w-full px-4 py-2 rounded-lg bg-blue-500/20 text-blue-300 text-sm font-medium hover:bg-blue-500/30 transition disabled:opacity-50">
+            <svg v-if="isAdding" class="w-4 h-4 animate-spin mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
+            </svg>
+            <span v-else>立即添加</span>
+          </button>
+        </div>
+
+        <!-- PWA安装 -->
+        <div v-if="capabilities.hasBeforeInstallPrompt" 
+             class="solution-card bg-green-500/10 border border-green-500/20">
+          <div class="flex items-center gap-3 mb-3">
+            <div class="w-10 h-10 rounded-full bg-green-500/20 flex items-center justify-center">
+              <svg class="w-5 h-5 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                <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"/>
+              </svg>
+            </div>
+            <div>
+              <h4 class="font-medium text-green-200">安装为PWA应用</h4>
+              <p class="text-sm text-green-300/80">完整应用体验</p>
+            </div>
+          </div>
+          <button @click="installPWA" 
+                  :disabled="isInstalling"
+                  class="w-full px-4 py-2 rounded-lg bg-green-500/20 text-green-300 text-sm font-medium hover:bg-green-500/30 transition disabled:opacity-50">
+            <svg v-if="isInstalling" class="w-4 h-4 animate-spin mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
+            </svg>
+            <span v-else>安装应用</span>
+          </button>
+        </div>
+
+        <!-- 书签方案 -->
+        <div v-if="capabilities.hasClipboard || capabilities.hasShare" 
+             class="solution-card bg-purple-500/10 border border-purple-500/20">
+          <div class="flex items-center gap-3 mb-3">
+            <div class="w-10 h-10 rounded-full bg-purple-500/20 flex items-center justify-center">
+              <svg class="w-5 h-5 text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z"/>
+              </svg>
+            </div>
+            <div>
+              <h4 class="font-medium text-purple-200">保存为书签</h4>
+              <p class="text-sm text-purple-300/80">通用兼容方案</p>
+            </div>
+          </div>
+          <button @click="addBookmark" 
+                  :disabled="isAdding"
+                  class="w-full px-4 py-2 rounded-lg bg-purple-500/20 text-purple-300 text-sm font-medium hover:bg-purple-500/30 transition disabled:opacity-50">
+            <svg v-if="isAdding" class="w-4 h-4 animate-spin mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
+            </svg>
+            <span v-else>保存书签</span>
+          </button>
+        </div>
+
+        <!-- 二维码方案 -->
+        <div class="solution-card bg-orange-500/10 border border-orange-500/20">
+          <div class="flex items-center gap-3 mb-3">
+            <div class="w-10 h-10 rounded-full bg-orange-500/20 flex items-center justify-center">
+              <svg class="w-5 h-5 text-orange-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z"/>
+              </svg>
+            </div>
+            <div>
+              <h4 class="font-medium text-orange-200">二维码分享</h4>
+              <p class="text-sm text-orange-300/80">扫码快速访问</p>
+            </div>
+          </div>
+          <button @click="showQRCode" 
+                  class="w-full px-4 py-2 rounded-lg bg-orange-500/20 text-orange-300 text-sm font-medium hover:bg-orange-500/30 transition">
+            生成二维码
+          </button>
+        </div>
+      </div>
+    </div>
+
+    <!-- 二维码弹窗 -->
+    <div v-if="showQR" class="fixed inset-0 z-50 bg-black/60 flex items-center justify-center p-4">
+      <div class="bg-surface rounded-2xl border border-white/10 p-6 max-w-sm w-full">
+        <div class="flex items-center justify-between mb-4">
+          <h3 class="text-lg font-semibold text-white/90">二维码分享</h3>
+          <button @click="showQR = false" class="text-white/60 hover:text-white">
+            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
+            </svg>
+          </button>
+        </div>
+        <div class="text-center">
+          <img :src="qrCodeUrl" alt="二维码" class="mx-auto mb-4 rounded-lg">
+          <p class="text-sm text-white/60 mb-4">扫描二维码快速访问</p>
+          <button @click="downloadQR" 
+                  class="px-4 py-2 rounded-lg bg-brand text-slate-900 text-sm font-medium">
+            下载二维码
+          </button>
+        </div>
+      </div>
+    </div>
+
+    <!-- 浏览器特定指导 -->
+    <div v-if="browserGuide" class="mt-6 p-4 rounded-xl bg-amber-500/10 border border-amber-500/20">
+      <div class="flex items-start gap-3">
+        <div class="flex-shrink-0 w-8 h-8 rounded-full bg-amber-500/20 flex items-center justify-center">
+          <svg class="w-4 h-4 text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
+          </svg>
+        </div>
+        <div class="flex-1">
+          <h4 class="font-medium text-amber-200">{{ browserGuide.title }}</h4>
+          <p class="text-sm text-amber-300/80 mt-1">{{ browserGuide.description }}</p>
+          <ol class="mt-2 space-y-1 text-sm text-amber-300/70">
+            <li v-for="(step, index) in browserGuide.steps" :key="index" class="flex items-start gap-2">
+              <span class="flex-shrink-0 w-4 h-4 rounded-full bg-amber-500/30 flex items-center justify-center text-xs font-medium">{{ index + 1 }}</span>
+              <span>{{ step }}</span>
+            </li>
+          </ol>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue';
+import { 
+  getBrowserCapabilities, 
+  createWebShortcut, 
+  createBookmark, 
+  generateQRCode,
+  getBestDesktopSolution 
+} from '@/utils/desktop-shortcuts';
+import { installPWA as installPWAUtil, getDeferredPrompt } from '@/utils/pwa';
+
+// 状态
+const isAdding = ref(false);
+const isInstalling = ref(false);
+const showQR = ref(false);
+const qrCodeUrl = ref('');
+
+// 浏览器能力检测
+const capabilities = getBrowserCapabilities();
+
+// 浏览器特定指导
+const browserGuide = computed(() => {
+  if (capabilities.isQQ) {
+    return {
+      title: "QQ浏览器优化建议",
+      description: "为了获得最佳体验,建议:",
+      steps: [
+        "更新到最新版本的QQ浏览器",
+        "在设置中启用「桌面模式」",
+        "允许网站创建快捷方式"
+      ]
+    };
+  }
+  
+  if (capabilities.isUC) {
+    return {
+      title: "UC浏览器优化建议",
+      description: "为了获得最佳体验,建议:",
+      steps: [
+        "更新到最新版本的UC浏览器",
+        "在设置中启用「PWA支持」",
+        "允许网站添加到桌面"
+      ]
+    };
+  }
+  
+  if (capabilities.isWechat) {
+    return {
+      title: "微信内置浏览器",
+      description: "微信内置浏览器功能有限,建议:",
+      steps: [
+        "点击右上角「...」按钮",
+        "选择「在浏览器中打开」",
+        "在外部浏览器中添加快捷方式"
+      ]
+    };
+  }
+  
+  return null;
+});
+
+// 添加Web Shortcut
+async function addWebShortcut() {
+  isAdding.value = true;
+  
+  try {
+    const success = await createWebShortcut({
+      title: 'Junma Show',
+      url: window.location.href,
+      icon: '/icons/icon-192x192.png',
+      description: '在线视频播放平台'
+    });
+    
+    if (success) {
+      alert('✅ 快捷方式已添加到桌面!');
+    } else {
+      throw new Error('创建失败');
+    }
+  } catch (error) {
+    console.error('❌ 添加快捷方式失败:', error);
+    alert('❌ 添加快捷方式失败,请尝试其他方案');
+  } finally {
+    isAdding.value = false;
+  }
+}
+
+// 安装PWA
+async function installPWA() {
+  isInstalling.value = true;
+  
+  try {
+    const result = await installPWAUtil();
+    
+    if (result.success) {
+      alert('✅ PWA应用安装成功!');
+    } else {
+      throw new Error(result.error || '安装失败');
+    }
+  } catch (error) {
+    console.error(' PWA安装失败:', error);
+    alert('当前浏览器暂不支持,推荐使用谷歌浏览器安装');
+  } finally {
+    isInstalling.value = false;
+  }
+}
+
+// 添加书签
+async function addBookmark() {
+  isAdding.value = true;
+  
+  try {
+    const success = await createBookmark({
+      title: 'Junma Show',
+      url: window.location.href,
+      description: '在线视频播放平台'
+    });
+    
+    if (success) {
+      alert('✅ 书签已保存!请按照提示添加到桌面');
+    } else {
+      throw new Error('保存失败');
+    }
+  } catch (error) {
+    console.error('❌ 保存书签失败:', error);
+    alert('当前浏览器暂不支持,推荐使用谷歌浏览器安装');
+  } finally {
+    isAdding.value = false;
+  }
+}
+
+// 显示二维码
+function showQRCode() {
+  qrCodeUrl.value = generateQRCode({
+    title: 'Junma Show',
+    url: window.location.href,
+    size: 200
+  });
+  showQR.value = true;
+}
+
+// 下载二维码
+function downloadQR() {
+  const link = document.createElement('a');
+  link.href = qrCodeUrl.value;
+  link.download = 'junma-show-qr.png';
+  link.click();
+}
+
+onMounted(() => {
+  console.log('🔍 浏览器能力检测:', capabilities);
+  console.log('📋 可用方案:', getBestDesktopSolution());
+});
+</script>
+
+<style scoped>
+.solution-card {
+  @apply p-4 rounded-xl transition-all duration-200 hover:scale-105;
+}
+</style>

+ 123 - 4
src/components/ShareDialog.vue

@@ -47,11 +47,67 @@
         </button>
       </div>
     </div>
+
+    <!-- 成功提示弹窗 -->
+    <div
+      v-if="showSuccessToast"
+      class="fixed inset-0 bg-black/50 flex items-center justify-center z-[9999]"
+      @click.self="showSuccessToast = false"
+    >
+      <div class="bg-surface border border-white/10 rounded-2xl p-6 w-full max-w-sm mx-4 text-center">
+        <!-- 成功图标 -->
+        <div class="mb-4">
+          <div class="w-12 h-12 mx-auto bg-green-500/20 rounded-full flex items-center justify-center">
+            <svg class="w-6 h-6 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
+            </svg>
+          </div>
+        </div>
+        <!-- 成功信息 -->
+        <h3 class="text-lg font-semibold text-white/90 mb-2">复制成功</h3>
+        <p class="text-sm text-white/70 mb-6">{{ successMessage }}</p>
+        <!-- 确认按钮 -->
+        <button
+          @click="showSuccessToast = false"
+          class="w-full px-4 py-2 bg-brand text-slate-900 rounded-lg hover:bg-brand/90 transition"
+        >
+          确定
+        </button>
+      </div>
+    </div>
+
+    <!-- 错误提示弹窗 -->
+    <div
+      v-if="showErrorToast"
+      class="fixed inset-0 bg-black/50 flex items-center justify-center z-[9999]"
+      @click.self="showErrorToast = false"
+    >
+      <div class="bg-surface border border-white/10 rounded-2xl p-6 w-full max-w-sm mx-4 text-center">
+        <!-- 错误图标 -->
+        <div class="mb-4">
+          <div class="w-12 h-12 mx-auto bg-red-500/20 rounded-full flex items-center justify-center">
+            <svg class="w-6 h-6 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"/>
+            </svg>
+          </div>
+        </div>
+        <!-- 错误信息 -->
+        <h3 class="text-lg font-semibold text-white/90 mb-2">复制失败</h3>
+        <p class="text-sm text-white/70 mb-6">{{ errorMessage }}</p>
+        <!-- 确认按钮 -->
+        <button
+          @click="showErrorToast = false"
+          class="w-full px-4 py-2 bg-brand text-slate-900 rounded-lg hover:bg-brand/90 transition"
+        >
+          确定
+        </button>
+      </div>
+    </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { computed, watch } from 'vue';
+import { computed, watch, ref } from 'vue';
 import { generateShareLink } from '@/services/shareApi';
 import { useUserStore } from '@/store/user';
 
@@ -70,6 +126,12 @@ const emit = defineEmits<{
 
 const userStore = useUserStore();
 
+// 提示弹窗状态
+const showSuccessToast = ref(false);
+const showErrorToast = ref(false);
+const successMessage = ref('');
+const errorMessage = ref('');
+
 // 计算分享链接
 const shareUrl = computed(() => {
   console.log('🔄 shareUrl 计算属性执行:', {
@@ -101,14 +163,71 @@ const closeDialog = () => {
 // 复制分享链接
 const copyShareLink = async () => {
   try {
-    await navigator.clipboard.writeText(shareUrl.value);
-    emit('success', '链接已复制到剪贴板!');
+    // 检查是否支持 Clipboard API
+    if (navigator.clipboard && navigator.clipboard.writeText) {
+      await navigator.clipboard.writeText(shareUrl.value);
+      showSuccessMessage('链接已复制到剪贴板!');
+    } else {
+      // 降级方案:使用传统的复制方法
+      fallbackCopyToClipboard(shareUrl.value);
+    }
   } catch (error) {
     console.error('复制失败:', error);
-    emit('error', '复制失败,请手动复制链接');
+    // 尝试降级方案
+    try {
+      fallbackCopyToClipboard(shareUrl.value);
+    } catch (fallbackError) {
+      console.error('降级复制也失败:', fallbackError);
+      showErrorMessage('复制失败,请手动复制链接');
+    }
+  }
+};
+
+// 降级复制方案
+const fallbackCopyToClipboard = (text: string) => {
+  // 创建临时文本区域
+  const textArea = document.createElement('textarea');
+  textArea.value = text;
+  textArea.style.position = 'fixed';
+  textArea.style.left = '-999999px';
+  textArea.style.top = '-999999px';
+  document.body.appendChild(textArea);
+  
+  try {
+    // 选择文本
+    textArea.focus();
+    textArea.select();
+    textArea.setSelectionRange(0, 99999); // 移动端支持
+    
+    // 执行复制命令
+    const successful = document.execCommand('copy');
+    document.body.removeChild(textArea);
+    
+    if (successful) {
+      showSuccessMessage('链接已复制到剪贴板!');
+    } else {
+      throw new Error('execCommand failed');
+    }
+  } catch (error) {
+    document.body.removeChild(textArea);
+    throw error;
   }
 };
 
+// 显示成功消息
+const showSuccessMessage = (message: string) => {
+  successMessage.value = message;
+  showSuccessToast.value = true;
+  // 不发送事件给父组件,避免重复提示
+};
+
+// 显示错误消息
+const showErrorMessage = (message: string) => {
+  errorMessage.value = message;
+  showErrorToast.value = true;
+  // 不发送事件给父组件,避免重复提示
+};
+
 // 移除分享行为记录逻辑
 // 分享者点击分享按钮时不需要调用recordShare接口
 // 只有被分享者点击分享链接时才调用recordShareClick接口

+ 285 - 0
src/utils/desktop-shortcuts.ts

@@ -0,0 +1,285 @@
+/**
+ * 桌面快捷方式管理工具
+ * 提供多种快速添加桌面的方案
+ */
+
+// 检测浏览器支持的功能
+export function getBrowserCapabilities() {
+  const capabilities = {
+    // 基础功能
+    hasClipboard: !!navigator.clipboard,
+    hasShare: !!navigator.share,
+    hasWebShortcuts: 'shortcuts' in navigator || !!(navigator as any).shortcuts,
+    hasBeforeInstallPrompt: 'onbeforeinstallprompt' in window,
+    
+    // 浏览器特定功能
+    isChrome: /chrome/.test(navigator.userAgent) && !/edge/.test(navigator.userAgent),
+    isSafari: /safari/.test(navigator.userAgent) && !/chrome/.test(navigator.userAgent),
+    isEdge: /edge/.test(navigator.userAgent),
+    isFirefox: /firefox/.test(navigator.userAgent),
+    isQQ: /qqbrowser/.test(navigator.userAgent) || /mqqbrowser/.test(navigator.userAgent),
+    isUC: /ucbrowser/.test(navigator.userAgent) || /ucweb/.test(navigator.userAgent),
+    isQuark: /quark/.test(navigator.userAgent),
+    isWechat: /micromessenger/.test(navigator.userAgent),
+    isAndroid: /android/.test(navigator.userAgent),
+    isIOS: /iphone|ipad|ipod/.test(navigator.userAgent),
+  };
+
+  return capabilities;
+}
+
+// 方案1:Web Shortcut API(最新标准)
+export async function createWebShortcut(options: {
+  title: string;
+  url: string;
+  icon?: string;
+  description?: string;
+}): Promise<boolean> {
+  const caps = getBrowserCapabilities();
+  
+  if (!caps.hasWebShortcuts) {
+    console.warn('⚠️ 当前浏览器不支持Web Shortcuts API');
+    return false;
+  }
+
+  try {
+    // 使用Web Shortcuts API(类型断言处理)
+    const shortcuts = (navigator as any).shortcuts;
+    if (shortcuts && shortcuts.add) {
+      await shortcuts.add({
+        title: options.title,
+        url: options.url,
+        icon: options.icon,
+        description: options.description
+      });
+      
+      console.log('✅ Web Shortcut创建成功');
+      return true;
+    } else {
+      throw new Error('Web Shortcuts API not available');
+    }
+  } catch (error) {
+    console.error('❌ Web Shortcut创建失败:', error);
+    return false;
+  }
+}
+
+// 方案2:书签方案(通用兼容)
+export async function createBookmark(options: {
+  title: string;
+  url: string;
+  description?: string;
+}): Promise<boolean> {
+  const caps = getBrowserCapabilities();
+  
+  try {
+    // 尝试使用现代分享API
+    if (caps.hasShare) {
+      await navigator.share({
+        title: options.title,
+        url: options.url,
+        text: options.description
+      });
+      return true;
+    }
+    
+    // 降级到剪贴板方案
+    if (caps.hasClipboard) {
+      await navigator.clipboard.writeText(options.url);
+      showBookmarkInstructions(options.title);
+      return true;
+    }
+    
+    // 最终降级:显示链接
+    showBookmarkFallback(options.title, options.url);
+    return true;
+    
+  } catch (error) {
+    console.error('❌ 书签创建失败:', error);
+    return false;
+  }
+}
+
+// 方案3:自定义协议方案
+export function createCustomProtocol(options: {
+  title: string;
+  url: string;
+  protocol: string;
+}): boolean {
+  try {
+    // 创建自定义协议链接
+    const protocolUrl = `${options.protocol}://${encodeURIComponent(options.url)}`;
+    
+    // 尝试打开协议链接
+    const link = document.createElement('a');
+    link.href = protocolUrl;
+    link.click();
+    
+    console.log('✅ 自定义协议链接已创建');
+    return true;
+  } catch (error) {
+    console.error('❌ 自定义协议创建失败:', error);
+    return false;
+  }
+}
+
+// 方案4:二维码方案
+export function generateQRCode(options: {
+  title: string;
+  url: string;
+  size?: number;
+}): string {
+  const size = options.size || 200;
+  const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=${size}x${size}&data=${encodeURIComponent(options.url)}`;
+  
+  return qrUrl;
+}
+
+// 方案5:桌面快捷方式生成器(Windows)
+export function generateDesktopShortcut(options: {
+  title: string;
+  url: string;
+  icon?: string;
+}): string {
+  const shortcutContent = `[InternetShortcut]
+URL=${options.url}
+IconFile=${options.icon || '/icons/icon-32x32.png'}
+IconIndex=0
+IDList=
+HotKey=0
+`;
+  
+  return `data:text/plain;charset=utf-8,${encodeURIComponent(shortcutContent)}`;
+}
+
+// 显示书签指导
+function showBookmarkInstructions(title: string): void {
+  const message = `
+📱 书签已复制到剪贴板!
+
+请按以下步骤添加到桌面:
+1. 打开浏览器书签管理器
+2. 点击"添加新书签"
+3. 粘贴链接并保存
+4. 在书签中找到"${title}"
+5. 右键选择"添加到桌面"
+  `;
+  
+  alert(message);
+}
+
+// 书签降级方案
+function showBookmarkFallback(title: string, url: string): void {
+  const message = `
+📱 请手动添加书签:
+
+标题:${title}
+链接:${url}
+
+请复制上述链接到浏览器书签中。
+  `;
+  
+  prompt('请复制以下链接到书签:', url);
+}
+
+// 获取最佳方案
+export function getBestDesktopSolution(): {
+  method: string;
+  description: string;
+  priority: number;
+}[] {
+  const caps = getBrowserCapabilities();
+  const solutions = [];
+
+  // 优先级1:Web Shortcuts API
+  if (caps.hasWebShortcuts) {
+    solutions.push({
+      method: 'web-shortcuts',
+      description: '一键创建桌面快捷方式',
+      priority: 1
+    });
+  }
+
+  // 优先级2:PWA安装
+  if (caps.hasBeforeInstallPrompt) {
+    solutions.push({
+      method: 'pwa-install',
+      description: '安装为PWA应用',
+      priority: 2
+    });
+  }
+
+  // 优先级3:书签方案
+  if (caps.hasClipboard || caps.hasShare) {
+    solutions.push({
+      method: 'bookmark',
+      description: '保存为书签',
+      priority: 3
+    });
+  }
+
+  // 优先级4:二维码方案
+  solutions.push({
+    method: 'qr-code',
+    description: '生成二维码分享',
+    priority: 4
+  });
+
+  // 优先级5:自定义协议
+  if (caps.isAndroid || caps.isIOS) {
+    solutions.push({
+      method: 'custom-protocol',
+      description: '创建应用链接',
+      priority: 5
+    });
+  }
+
+  return solutions.sort((a, b) => a.priority - b.priority);
+}
+
+// 执行最佳方案
+export async function executeBestSolution(options: {
+  title: string;
+  url: string;
+  icon?: string;
+  description?: string;
+}): Promise<boolean> {
+  const solutions = getBestDesktopSolution();
+  
+  for (const solution of solutions) {
+    try {
+      let success = false;
+      
+      switch (solution.method) {
+        case 'web-shortcuts':
+          success = await createWebShortcut(options);
+          break;
+        case 'bookmark':
+          success = await createBookmark(options);
+          break;
+        case 'qr-code':
+          const qrUrl = generateQRCode(options);
+          window.open(qrUrl, '_blank');
+          success = true;
+          break;
+        case 'custom-protocol':
+          success = createCustomProtocol({
+            ...options,
+            protocol: 'junmashow'
+          });
+          break;
+      }
+      
+      if (success) {
+        console.log(`✅ 使用方案成功: ${solution.description}`);
+        return true;
+      }
+    } catch (error) {
+      console.warn(`⚠️ 方案失败: ${solution.description}`, error);
+      continue;
+    }
+  }
+  
+  console.error('❌ 所有方案都失败了');
+  return false;
+}

+ 254 - 0
src/utils/mobile-shortcuts.ts

@@ -0,0 +1,254 @@
+/**
+ * 移动端快捷方式生成器
+ * 针对Android和iOS的特殊处理
+ */
+
+// 检测移动端平台
+export function getMobilePlatform() {
+  const ua = navigator.userAgent.toLowerCase();
+  
+  return {
+    isAndroid: /android/.test(ua),
+    isIOS: /iphone|ipad|ipod/.test(ua),
+    isMobile: /mobile|android|iphone|ipad|ipod/.test(ua),
+    isTablet: /ipad|android(?!.*mobile)/.test(ua),
+    userAgent: ua
+  };
+}
+
+// Android快捷方式生成
+export function generateAndroidShortcut(options: {
+  title: string;
+  url: string;
+  icon?: string;
+  description?: string;
+}): string {
+  const shortcutData = {
+    name: options.title,
+    url: options.url,
+    icon: options.icon || '/icons/icon-192x192.png',
+    description: options.description || '快速访问'
+  };
+  
+  // 生成Android Intent URL
+  const intentUrl = `intent://${encodeURIComponent(options.url)}#Intent;scheme=https;package=com.android.chrome;end`;
+  
+  return intentUrl;
+}
+
+// iOS快捷方式生成
+export function generateIOSShortcut(options: {
+  title: string;
+  url: string;
+  icon?: string;
+  description?: string;
+}): string {
+  // iOS使用Safari的"添加到主屏幕"功能
+  const shortcutData = {
+    title: options.title,
+    url: options.url,
+    icon: options.icon || '/icons/icon-192x192.png',
+    description: options.description || '快速访问'
+  };
+  
+  return options.url;
+}
+
+// 生成桌面快捷方式文件
+export function generateDesktopShortcutFile(options: {
+  title: string;
+  url: string;
+  icon?: string;
+  description?: string;
+}): string {
+  const platform = getMobilePlatform();
+  
+  if (platform.isAndroid) {
+    return generateAndroidShortcut(options);
+  } else if (platform.isIOS) {
+    return generateIOSShortcut(options);
+  }
+  
+  // 默认Windows快捷方式
+  return generateWindowsShortcut(options);
+}
+
+// Windows快捷方式生成
+export function generateWindowsShortcut(options: {
+  title: string;
+  url: string;
+  icon?: string;
+  description?: string;
+}): string {
+  const shortcutContent = `[InternetShortcut]
+URL=${options.url}
+IconFile=${options.icon || '/icons/icon-32x32.png'}
+IconIndex=0
+IDList=
+HotKey=0
+`;
+  
+  return `data:text/plain;charset=utf-8,${encodeURIComponent(shortcutContent)}`;
+}
+
+// 生成移动端安装指导
+export function getMobileInstallGuide(): {
+  platform: string;
+  title: string;
+  steps: string[];
+  icon: string;
+} {
+  const platform = getMobilePlatform();
+  
+  if (platform.isAndroid) {
+    return {
+      platform: 'Android',
+      title: 'Android设备安装指导',
+      steps: [
+        '长按浏览器地址栏',
+        '选择"添加到主屏幕"',
+        '确认名称后点击"添加"',
+        '应用图标将出现在桌面'
+      ],
+      icon: 'android'
+    };
+  } else if (platform.isIOS) {
+    return {
+      platform: 'iOS',
+      title: 'iOS设备安装指导',
+      steps: [
+        '点击Safari底部"分享"按钮',
+        '选择"添加到主屏幕"',
+        '确认名称后点击"添加"',
+        '应用图标将出现在主屏幕'
+      ],
+      icon: 'ios'
+    };
+  }
+  
+  return {
+    platform: 'Desktop',
+    title: '桌面设备安装指导',
+    steps: [
+      '右键点击浏览器书签',
+      '选择"添加到桌面"',
+      '确认创建快捷方式',
+      '快捷方式将出现在桌面'
+    ],
+    icon: 'desktop'
+  };
+}
+
+// 创建移动端快捷方式
+export async function createMobileShortcut(options: {
+  title: string;
+  url: string;
+  icon?: string;
+  description?: string;
+}): Promise<boolean> {
+  const platform = getMobilePlatform();
+  
+  try {
+    if (platform.isAndroid) {
+      // Android使用Intent URL
+      const intentUrl = generateAndroidShortcut(options);
+      window.open(intentUrl, '_blank');
+      return true;
+    } else if (platform.isIOS) {
+      // iOS使用Safari的"添加到主屏幕"功能
+      if (navigator.share) {
+        await navigator.share({
+          title: options.title,
+          url: options.url,
+          text: options.description
+        });
+        return true;
+      } else {
+        // 降级到剪贴板
+        await navigator.clipboard.writeText(options.url);
+        alert('链接已复制,请在Safari中打开并添加到主屏幕');
+        return true;
+      }
+    } else {
+      // 桌面端
+      const shortcutUrl = generateDesktopShortcutFile(options);
+      const link = document.createElement('a');
+      link.href = shortcutUrl;
+      link.download = `${options.title}.url`;
+      link.click();
+      return true;
+    }
+  } catch (error) {
+    console.error('❌ 创建移动端快捷方式失败:', error);
+    return false;
+  }
+}
+
+// 检测是否支持移动端快捷方式
+export function supportsMobileShortcuts(): boolean {
+  const platform = getMobilePlatform();
+  
+  if (platform.isAndroid) {
+    // Android支持Intent URL
+    return true;
+  } else if (platform.isIOS) {
+    // iOS支持分享API或剪贴板
+    return !!(navigator.share || navigator.clipboard);
+  }
+  
+  // 桌面端支持下载
+  return true;
+}
+
+// 获取最佳移动端方案
+export function getBestMobileSolution(): {
+  method: string;
+  description: string;
+  priority: number;
+}[] {
+  const platform = getMobilePlatform();
+  const solutions = [];
+  
+  if (platform.isAndroid) {
+    solutions.push(
+      {
+        method: 'android-intent',
+        description: 'Android快捷方式',
+        priority: 1
+      },
+      {
+        method: 'bookmark',
+        description: '保存为书签',
+        priority: 2
+      }
+    );
+  } else if (platform.isIOS) {
+    solutions.push(
+      {
+        method: 'ios-share',
+        description: 'iOS分享到主屏幕',
+        priority: 1
+      },
+      {
+        method: 'safari-bookmark',
+        description: 'Safari书签',
+        priority: 2
+      }
+    );
+  } else {
+    solutions.push(
+      {
+        method: 'desktop-shortcut',
+        description: '桌面快捷方式',
+        priority: 1
+      },
+      {
+        method: 'bookmark',
+        description: '浏览器书签',
+        priority: 2
+      }
+    );
+  }
+  
+  return solutions.sort((a, b) => a.priority - b.priority);
+}

+ 8 - 1
src/views/Favorite.vue

@@ -37,6 +37,12 @@
           </button>
         </div>
       </div>
+      
+      <!-- 桌面添加快捷方式 -->
+      <div class="mt-6">
+        <DesktopAddGuide />
+      </div>
+      
       <div class="mt-3 flex gap-2">
         <button
           class="px-3 py-2 rounded-lg bg-brand text-slate-900 text-sm font-medium"
@@ -105,6 +111,7 @@ import {
   onPromptReady,
   offPromptReady
 } from "@/utils/pwa";
+import DesktopAddGuide from "@/components/DesktopAddGuide.vue";
 
 const showFavSheet = ref(false);
 const canInstallPWA = ref(false);
@@ -138,7 +145,7 @@ async function installPWA() {
       console.log('✅ 安装成功');
       canInstallPWA.value = false;
     } else if (result.error) {
-      console.error('安装失败:', result.error);
+      console.error('安装失败:', result.error);
       alert('安装失败,请尝试手动安装');
     } else {
       console.log('ℹ️ 用户取消了安装');

+ 0 - 2
src/views/VideoPlayer.vue

@@ -524,8 +524,6 @@
       :share-url="shareUrl"
       :share-title="`精彩视频推荐:${videoInfo.name || '这个视频'}`"
       :resource-id="videoInfo.id"
-      @success="showSuccess"
-      @error="showError"
     />