switch to all zhcn

This commit is contained in:
Julian Freeman
2025-12-08 09:38:38 -04:00
parent f23b37a581
commit 52344892d5
5 changed files with 68 additions and 70 deletions

View File

@@ -28,7 +28,7 @@ onMounted(async () => {
<div class="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center text-white shrink-0"> <div class="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center text-white shrink-0">
<Download class="w-5 h-5" /> <Download class="w-5 h-5" />
</div> </div>
<span class="font-bold text-lg hidden lg:block">StreamCapture</span> <span class="font-bold text-lg hidden lg:block">流萤</span>
</div> </div>
<nav class="flex-1 px-4 space-y-2 mt-4 flex flex-col pb-6"> <nav class="flex-1 px-4 space-y-2 mt-4 flex flex-col pb-6">
@@ -38,7 +38,7 @@ onMounted(async () => {
:class="route.path === '/' ? 'bg-blue-50 text-blue-600 dark:bg-blue-900/20 dark:text-blue-400' : 'hover:bg-gray-100 dark:hover:bg-zinc-800'" :class="route.path === '/' ? 'bg-blue-50 text-blue-600 dark:bg-blue-900/20 dark:text-blue-400' : 'hover:bg-gray-100 dark:hover:bg-zinc-800'"
> >
<Home class="w-5 h-5 shrink-0" /> <Home class="w-5 h-5 shrink-0" />
<span class="hidden lg:block font-medium">Downloader</span> <span class="hidden lg:block font-medium">下载</span>
</RouterLink> </RouterLink>
<RouterLink to="/history" <RouterLink to="/history"
@@ -46,7 +46,7 @@ onMounted(async () => {
:class="route.path === '/history' ? 'bg-blue-50 text-blue-600 dark:bg-blue-900/20 dark:text-blue-400' : 'hover:bg-gray-100 dark:hover:bg-zinc-800'" :class="route.path === '/history' ? 'bg-blue-50 text-blue-600 dark:bg-blue-900/20 dark:text-blue-400' : 'hover:bg-gray-100 dark:hover:bg-zinc-800'"
> >
<History class="w-5 h-5 shrink-0" /> <History class="w-5 h-5 shrink-0" />
<span class="hidden lg:block font-medium">History</span> <span class="hidden lg:block font-medium">历史</span>
</RouterLink> </RouterLink>
<div class="flex-1"></div> <div class="flex-1"></div>
@@ -57,7 +57,7 @@ onMounted(async () => {
:class="route.path === '/logs' ? 'bg-blue-50 text-blue-600 dark:bg-blue-900/20 dark:text-blue-400' : 'hover:bg-gray-100 dark:hover:bg-zinc-800'" :class="route.path === '/logs' ? 'bg-blue-50 text-blue-600 dark:bg-blue-900/20 dark:text-blue-400' : 'hover:bg-gray-100 dark:hover:bg-zinc-800'"
> >
<FileText class="w-5 h-5 shrink-0" /> <FileText class="w-5 h-5 shrink-0" />
<span class="hidden lg:block font-medium">Logs</span> <span class="hidden lg:block font-medium">日志</span>
</RouterLink> </RouterLink>
<RouterLink to="/settings" <RouterLink to="/settings"
@@ -65,7 +65,7 @@ onMounted(async () => {
:class="route.path === '/settings' ? 'bg-blue-50 text-blue-600 dark:bg-blue-900/20 dark:text-blue-400' : 'hover:bg-gray-100 dark:hover:bg-zinc-800'" :class="route.path === '/settings' ? 'bg-blue-50 text-blue-600 dark:bg-blue-900/20 dark:text-blue-400' : 'hover:bg-gray-100 dark:hover:bg-zinc-800'"
> >
<SettingsIcon class="w-5 h-5 shrink-0" /> <SettingsIcon class="w-5 h-5 shrink-0" />
<span class="hidden lg:block font-medium">Settings</span> <span class="hidden lg:block font-medium">设置</span>
</RouterLink> </RouterLink>
</nav> </nav>
</aside> </aside>

View File

@@ -1,9 +1,9 @@
// filepath: src/views/History.vue
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
import { Trash2, FolderOpen } from 'lucide-vue-next' import { Trash2, FolderOpen } from 'lucide-vue-next'
import { formatDistanceToNow } from 'date-fns' import { formatDistanceToNow } from 'date-fns'
import { zhCN } from 'date-fns/locale'
interface HistoryItem { interface HistoryItem {
id: string id: string
@@ -53,8 +53,8 @@ onMounted(loadHistory)
<div class="max-w-5xl mx-auto p-8"> <div class="max-w-5xl mx-auto p-8">
<header class="mb-8 flex justify-between items-center"> <header class="mb-8 flex justify-between items-center">
<div> <div>
<h1 class="text-3xl font-bold text-zinc-900 dark:text-white">Download History</h1> <h1 class="text-3xl font-bold text-zinc-900 dark:text-white">下载历史</h1>
<p class="text-gray-500 dark:text-gray-400 mt-2">Manage your past downloads.</p> <p class="text-gray-500 dark:text-gray-400 mt-2">管理您的下载记录</p>
</div> </div>
<button <button
@click="clearHistory" @click="clearHistory"
@@ -62,7 +62,7 @@ onMounted(loadHistory)
class="text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 px-4 py-2 rounded-lg transition-colors text-sm font-medium flex items-center gap-2" class="text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 px-4 py-2 rounded-lg transition-colors text-sm font-medium flex items-center gap-2"
> >
<Trash2 class="w-4 h-4" /> <Trash2 class="w-4 h-4" />
Clear All 清空所有
</button> </button>
</header> </header>
@@ -70,8 +70,8 @@ onMounted(loadHistory)
<div class="bg-gray-100 dark:bg-zinc-900 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4"> <div class="bg-gray-100 dark:bg-zinc-900 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4">
<FolderOpen class="w-8 h-8 text-gray-400" /> <FolderOpen class="w-8 h-8 text-gray-400" />
</div> </div>
<h3 class="text-lg font-medium text-zinc-900 dark:text-white">No downloads yet</h3> <h3 class="text-lg font-medium text-zinc-900 dark:text-white">暂无下载</h3>
<p class="text-gray-500">Your download history will appear here.</p> <p class="text-gray-500">您的下载记录将显示在这里</p>
</div> </div>
<div v-else class="bg-white dark:bg-zinc-900 rounded-2xl shadow-sm border border-gray-200 dark:border-zinc-800 overflow-hidden"> <div v-else class="bg-white dark:bg-zinc-900 rounded-2xl shadow-sm border border-gray-200 dark:border-zinc-800 overflow-hidden">
@@ -79,11 +79,11 @@ onMounted(loadHistory)
<table class="w-full text-left"> <table class="w-full text-left">
<thead class="bg-gray-50 dark:bg-zinc-800/50 text-xs uppercase text-gray-500 font-medium"> <thead class="bg-gray-50 dark:bg-zinc-800/50 text-xs uppercase text-gray-500 font-medium">
<tr> <tr>
<th class="px-6 py-4">Media</th> <th class="px-6 py-4">媒体</th>
<th class="px-6 py-4">Date</th> <th class="px-6 py-4">日期</th>
<th class="px-6 py-4">Format</th> <th class="px-6 py-4">格式</th>
<th class="px-6 py-4">Status</th> <th class="px-6 py-4">状态</th>
<th class="px-6 py-4 text-right">Actions</th> <th class="px-6 py-4 text-right">操作</th>
</tr> </tr>
</thead> </thead>
<tbody class="divide-y divide-gray-100 dark:divide-zinc-800"> <tbody class="divide-y divide-gray-100 dark:divide-zinc-800">
@@ -98,7 +98,7 @@ onMounted(loadHistory)
</div> </div>
</td> </td>
<td class="px-6 py-4 text-sm text-gray-500 whitespace-nowrap"> <td class="px-6 py-4 text-sm text-gray-500 whitespace-nowrap">
{{ formatDistanceToNow(new Date(item.timestamp), { addSuffix: true }) }} {{ formatDistanceToNow(new Date(item.timestamp), { addSuffix: true, locale: zhCN }) }}
</td> </td>
<td class="px-6 py-4 text-sm text-gray-500"> <td class="px-6 py-4 text-sm text-gray-500">
<span class="bg-gray-100 dark:bg-zinc-800 px-2 py-1 rounded text-xs font-mono">{{ item.format }}</span> <span class="bg-gray-100 dark:bg-zinc-800 px-2 py-1 rounded text-xs font-mono">{{ item.format }}</span>
@@ -109,21 +109,21 @@ onMounted(loadHistory)
:class="item.status === 'success' ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400' : 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'" :class="item.status === 'success' ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400' : 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'"
> >
<span class="w-1.5 h-1.5 rounded-full" :class="item.status === 'success' ? 'bg-green-500' : 'bg-red-500'"></span> <span class="w-1.5 h-1.5 rounded-full" :class="item.status === 'success' ? 'bg-green-500' : 'bg-red-500'"></span>
{{ item.status === 'success' ? 'Completed' : 'Failed' }} {{ item.status === 'success' ? '已完成' : '失败' }}
</span> </span>
</td> </td>
<td class="px-6 py-4 text-right whitespace-nowrap"> <td class="px-6 py-4 text-right whitespace-nowrap">
<button <button
@click="openFolder(item.output_path)" @click="openFolder(item.output_path)"
class="p-2 text-gray-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-lg transition-colors" class="p-2 text-gray-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-lg transition-colors"
title="Open Output Folder" title="打开输出文件夹"
> >
<FolderOpen class="w-4 h-4" /> <FolderOpen class="w-4 h-4" />
</button> </button>
<button <button
@click="deleteItem(item.id)" @click="deleteItem(item.id)"
class="p-2 text-gray-400 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-lg transition-colors ml-1" class="p-2 text-gray-400 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-lg transition-colors ml-1"
title="Delete Record" title="删除记录"
> >
<Trash2 class="w-4 h-4" /> <Trash2 class="w-4 h-4" />
</button> </button>
@@ -134,4 +134,4 @@ onMounted(loadHistory)
</div> </div>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -1,4 +1,3 @@
// filepath: src/views/Home.vue
<script setup lang="ts"> <script setup lang="ts">
import { watch } from 'vue' import { watch } from 'vue'
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
@@ -13,7 +12,7 @@ const settingsStore = useSettingsStore()
const analysisStore = useAnalysisStore() const analysisStore = useAnalysisStore()
const qualityOptions = [ const qualityOptions = [
{ label: 'Best Quality', value: 'best' }, { label: '最佳画质', value: 'best' },
{ label: '1080p', value: '1080' }, { label: '1080p', value: '1080' },
{ label: '720p', value: '720' }, { label: '720p', value: '720' },
{ label: '480p', value: '480' }, { label: '480p', value: '480' },
@@ -95,7 +94,7 @@ async function startDownload() {
const selectedEntries = analysisStore.metadata.entries.filter((e: any) => e.selected) const selectedEntries = analysisStore.metadata.entries.filter((e: any) => e.selected)
if (selectedEntries.length === 0) { if (selectedEntries.length === 0) {
analysisStore.error = "Please select at least one video to download." analysisStore.error = "请至少选择一个要下载的视频。"
return return
} }
@@ -112,7 +111,7 @@ async function startDownload() {
title: entry.title, title: entry.title,
thumbnail: entry.thumbnail, thumbnail: entry.thumbnail,
progress: 0, progress: 0,
speed: 'Pending...', speed: '等待中...',
status: 'pending' status: 'pending'
}) })
} }
@@ -143,7 +142,7 @@ async function startDownload() {
title: analysisStore.metadata.title, title: analysisStore.metadata.title,
thumbnail: analysisStore.metadata.thumbnail, thumbnail: analysisStore.metadata.thumbnail,
progress: 0, progress: 0,
speed: 'Pending...', speed: '等待中...',
status: 'pending' status: 'pending'
}) })
} }
@@ -151,7 +150,7 @@ async function startDownload() {
// Reset state after successful download start // Reset state after successful download start
analysisStore.reset() analysisStore.reset()
} catch (e: any) { } catch (e: any) {
analysisStore.error = "Download failed to start: " + e.toString() analysisStore.error = "下载启动失败: " + e.toString()
} }
} }
</script> </script>
@@ -159,8 +158,8 @@ async function startDownload() {
<template> <template>
<div class="max-w-5xl mx-auto p-8"> <div class="max-w-5xl mx-auto p-8">
<header class="mb-8"> <header class="mb-8">
<h1 class="text-3xl font-bold text-zinc-900 dark:text-white">New Download</h1> <h1 class="text-3xl font-bold text-zinc-900 dark:text-white">新建下载</h1>
<p class="text-gray-500 dark:text-gray-400 mt-2">Paste a URL to start downloading media.</p> <p class="text-gray-500 dark:text-gray-400 mt-2">粘贴 URL 开始下载媒体</p>
</header> </header>
<!-- Input Section --> <!-- Input Section -->
@@ -179,7 +178,7 @@ async function startDownload() {
class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-xl font-medium transition-colors disabled:opacity-50 flex items-center gap-2 shrink-0" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-xl font-medium transition-colors disabled:opacity-50 flex items-center gap-2 shrink-0"
> >
<Loader2 v-if="analysisStore.loading" class="animate-spin w-5 h-5" /> <Loader2 v-if="analysisStore.loading" class="animate-spin w-5 h-5" />
<span v-else>Analyze</span> <span v-else>解析</span>
</button> </button>
</div> </div>
@@ -195,7 +194,7 @@ async function startDownload() {
:class="analysisStore.scanMix ? 'translate-x-6' : 'translate-x-0'" :class="analysisStore.scanMix ? 'translate-x-6' : 'translate-x-0'"
/> />
</button> </button>
<span class="text-sm font-medium text-zinc-700 dark:text-gray-300">Scan Playlist (Max 20)</span> <span class="text-sm font-medium text-zinc-700 dark:text-gray-300">解析播放列表 (20)</span>
</div> </div>
<p v-if="analysisStore.error" class="mt-3 text-red-500 text-sm">{{ analysisStore.error }}</p> <p v-if="analysisStore.error" class="mt-3 text-red-500 text-sm">{{ analysisStore.error }}</p>
@@ -209,7 +208,7 @@ async function startDownload() {
<div class="flex items-start justify-between mb-4"> <div class="flex items-start justify-between mb-4">
<div> <div>
<h2 class="text-xl font-bold text-zinc-900 dark:text-white">{{ analysisStore.metadata.title }}</h2> <h2 class="text-xl font-bold text-zinc-900 dark:text-white">{{ analysisStore.metadata.title }}</h2>
<p class="text-blue-500 mt-1 font-medium">{{ analysisStore.metadata.entries.length }} videos found</p> <p class="text-blue-500 mt-1 font-medium">{{ analysisStore.metadata.entries.length }} 个视频</p>
</div> </div>
</div> </div>
@@ -218,16 +217,16 @@ async function startDownload() {
<!-- Left: Selection Controls --> <!-- Left: Selection Controls -->
<div class="flex items-center gap-2 w-full md:w-auto"> <div class="flex items-center gap-2 w-full md:w-auto">
<button @click="analysisStore.setAllEntries(true)" class="text-xs font-medium px-3 py-1.5 rounded-lg bg-white dark:bg-zinc-700 hover:bg-gray-100 dark:hover:bg-zinc-600 text-zinc-700 dark:text-gray-200 border border-gray-200 dark:border-zinc-600 transition-colors shadow-sm">All</button> <button @click="analysisStore.setAllEntries(true)" class="text-xs font-medium px-3 py-1.5 rounded-lg bg-white dark:bg-zinc-700 hover:bg-gray-100 dark:hover:bg-zinc-600 text-zinc-700 dark:text-gray-200 border border-gray-200 dark:border-zinc-600 transition-colors shadow-sm">全选</button>
<button @click="analysisStore.setAllEntries(false)" class="text-xs font-medium px-3 py-1.5 rounded-lg bg-white dark:bg-zinc-700 hover:bg-gray-100 dark:hover:bg-zinc-600 text-zinc-700 dark:text-gray-200 border border-gray-200 dark:border-zinc-600 transition-colors shadow-sm">None</button> <button @click="analysisStore.setAllEntries(false)" class="text-xs font-medium px-3 py-1.5 rounded-lg bg-white dark:bg-zinc-700 hover:bg-gray-100 dark:hover:bg-zinc-600 text-zinc-700 dark:text-gray-200 border border-gray-200 dark:border-zinc-600 transition-colors shadow-sm">取消全选</button>
<button @click="analysisStore.invertSelection()" class="text-xs font-medium px-3 py-1.5 rounded-lg bg-white dark:bg-zinc-700 hover:bg-gray-100 dark:hover:bg-zinc-600 text-zinc-700 dark:text-gray-200 border border-gray-200 dark:border-zinc-600 transition-colors shadow-sm">Invert</button> <button @click="analysisStore.invertSelection()" class="text-xs font-medium px-3 py-1.5 rounded-lg bg-white dark:bg-zinc-700 hover:bg-gray-100 dark:hover:bg-zinc-600 text-zinc-700 dark:text-gray-200 border border-gray-200 dark:border-zinc-600 transition-colors shadow-sm">反选</button>
</div> </div>
<!-- Right: Settings --> <!-- Right: Settings -->
<div class="flex items-center gap-6 w-full md:w-auto justify-end"> <div class="flex items-center gap-6 w-full md:w-auto justify-end">
<!-- Audio Only Toggle --> <!-- Audio Only Toggle -->
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<span class="font-medium text-sm text-zinc-700 dark:text-gray-300">Audio Only</span> <span class="font-medium text-sm text-zinc-700 dark:text-gray-300">仅音频</span>
<button <button
@click="analysisStore.options.is_audio_only = !analysisStore.options.is_audio_only" @click="analysisStore.options.is_audio_only = !analysisStore.options.is_audio_only"
class="w-10 h-5 rounded-full relative transition-colors duration-200 ease-in-out shrink-0" class="w-10 h-5 rounded-full relative transition-colors duration-200 ease-in-out shrink-0"
@@ -242,7 +241,7 @@ async function startDownload() {
<!-- Quality Dropdown --> <!-- Quality Dropdown -->
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<span class="font-medium text-sm text-zinc-700 dark:text-gray-300">Quality</span> <span class="font-medium text-sm text-zinc-700 dark:text-gray-300">画质</span>
<div class="w-44"> <div class="w-44">
<AppSelect <AppSelect
v-model="analysisStore.options.quality" v-model="analysisStore.options.quality"
@@ -297,12 +296,13 @@ async function startDownload() {
<div class="flex-1"> <div class="flex-1">
<h2 class="text-xl font-bold text-zinc-900 dark:text-white line-clamp-2">{{ analysisStore.metadata.title }}</h2> <h2 class="text-xl font-bold text-zinc-900 dark:text-white line-clamp-2">{{ analysisStore.metadata.title }}</h2>
<p v-if="analysisStore.metadata.uploader" class="text-gray-500 dark:text-gray-400 mt-1">{{ analysisStore.metadata.uploader }}</p> <p v-if="analysisStore.metadata.uploader" class="text-gray-500 dark:text-gray-400 mt-1">{{ analysisStore.metadata.uploader }}</p>
<p v-if="analysisStore.metadata.entries" class="text-blue-500 mt-1 font-medium">{{ analysisStore.metadata.entries.length }} 个视频 (播放列表)</p>
<!-- Options --> <!-- Options -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6">
<!-- Audio Only Toggle --> <!-- Audio Only Toggle -->
<div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-zinc-800 rounded-xl"> <div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-zinc-800 rounded-xl">
<span class="font-medium text-sm">Audio Only</span> <span class="font-medium text-sm">仅音频</span>
<button <button
@click="analysisStore.options.is_audio_only = !analysisStore.options.is_audio_only" @click="analysisStore.options.is_audio_only = !analysisStore.options.is_audio_only"
class="w-12 h-6 rounded-full relative transition-colors duration-200 ease-in-out" class="w-12 h-6 rounded-full relative transition-colors duration-200 ease-in-out"
@@ -332,14 +332,14 @@ async function startDownload() {
@click="startDownload" @click="startDownload"
class="bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 rounded-xl font-bold transition-colors shadow-lg shadow-blue-600/20" class="bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 rounded-xl font-bold transition-colors shadow-lg shadow-blue-600/20"
> >
Download Now {{ analysisStore.metadata.entries ? `(${analysisStore.metadata.entries.filter((e: any) => e.selected).length})` : '' }} 立即下载 {{ analysisStore.metadata.entries ? `(${analysisStore.metadata.entries.filter((e: any) => e.selected).length})` : '' }}
</button> </button>
</div> </div>
</div> </div>
<!-- Active Downloads --> <!-- Active Downloads -->
<div v-if="queueStore.tasks.length > 0"> <div v-if="queueStore.tasks.length > 0">
<h3 class="text-lg font-bold mb-4">Active Downloads</h3> <h3 class="text-lg font-bold mb-4">进行中的任务</h3>
<div class="space-y-3"> <div class="space-y-3">
<!-- Reversed to show newest first --> <!-- Reversed to show newest first -->
<div v-for="task in queueStore.tasks.slice().reverse()" :key="task.id" class="bg-white dark:bg-zinc-900 p-4 rounded-xl border border-gray-200 dark:border-zinc-800 flex items-center gap-4"> <div v-for="task in queueStore.tasks.slice().reverse()" :key="task.id" class="bg-white dark:bg-zinc-900 p-4 rounded-xl border border-gray-200 dark:border-zinc-800 flex items-center gap-4">
@@ -348,7 +348,7 @@ async function startDownload() {
<div class="flex justify-between mb-1"> <div class="flex justify-between mb-1">
<h4 class="font-medium truncate pr-4">{{ task.title }}</h4> <h4 class="font-medium truncate pr-4">{{ task.title }}</h4>
<span class="text-xs font-mono text-gray-500 whitespace-nowrap"> <span class="text-xs font-mono text-gray-500 whitespace-nowrap">
{{ task.status === 'finished' ? 'Completed' : (task.status === 'error' ? 'Failed' : task.speed) }} {{ task.status === 'finished' ? '已完成' : (task.status === 'error' ? '失败' : task.speed) }}
</span> </span>
</div> </div>
<div class="h-2 bg-gray-100 dark:bg-zinc-800 rounded-full overflow-hidden"> <div class="h-2 bg-gray-100 dark:bg-zinc-800 rounded-full overflow-hidden">
@@ -364,4 +364,4 @@ async function startDownload() {
</div> </div>
</div> </div>
</template> </template>

View File

@@ -1,4 +1,3 @@
// filepath: src/views/Logs.vue
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, nextTick, watch, onMounted } from 'vue' import { ref, computed, nextTick, watch, onMounted } from 'vue'
import { useLogsStore } from '../stores/logs' import { useLogsStore } from '../stores/logs'
@@ -65,8 +64,8 @@ function formatTime(ts: number) {
<!-- Header --> <!-- Header -->
<div class="flex justify-between items-center mb-6"> <div class="flex justify-between items-center mb-6">
<div> <div>
<h1 class="text-3xl font-bold text-zinc-900 dark:text-white">Execution Logs</h1> <h1 class="text-3xl font-bold text-zinc-900 dark:text-white">运行日志</h1>
<p class="text-gray-500 dark:text-gray-400 mt-2">Real-time output from download processes.</p> <p class="text-gray-500 dark:text-gray-400 mt-2">下载任务的实时输出</p>
</div> </div>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
@@ -76,7 +75,7 @@ function formatTime(ts: number) {
<input <input
v-model="searchQuery" v-model="searchQuery"
type="text" type="text"
placeholder="Search logs..." placeholder="搜索日志..."
class="pl-9 pr-4 py-2 bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-800 rounded-xl text-sm outline-none focus:ring-2 focus:ring-blue-500 text-zinc-900 dark:text-white w-48" class="pl-9 pr-4 py-2 bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-800 rounded-xl text-sm outline-none focus:ring-2 focus:ring-blue-500 text-zinc-900 dark:text-white w-48"
/> />
</div> </div>
@@ -87,23 +86,23 @@ function formatTime(ts: number) {
@click="filterLevel = 'all'" @click="filterLevel = 'all'"
class="px-3 py-1.5 rounded-lg text-xs font-medium transition-colors" class="px-3 py-1.5 rounded-lg text-xs font-medium transition-colors"
:class="filterLevel === 'all' ? 'bg-gray-100 dark:bg-zinc-800 text-zinc-900 dark:text-white' : 'text-gray-500 hover:text-zinc-900 dark:hover:text-white'" :class="filterLevel === 'all' ? 'bg-gray-100 dark:bg-zinc-800 text-zinc-900 dark:text-white' : 'text-gray-500 hover:text-zinc-900 dark:hover:text-white'"
>All</button> >全部</button>
<button <button
@click="filterLevel = 'info'" @click="filterLevel = 'info'"
class="px-3 py-1.5 rounded-lg text-xs font-medium transition-colors" class="px-3 py-1.5 rounded-lg text-xs font-medium transition-colors"
:class="filterLevel === 'info' ? 'bg-gray-100 dark:bg-zinc-800 text-zinc-900 dark:text-white' : 'text-gray-500 hover:text-zinc-900 dark:hover:text-white'" :class="filterLevel === 'info' ? 'bg-gray-100 dark:bg-zinc-800 text-zinc-900 dark:text-white' : 'text-gray-500 hover:text-zinc-900 dark:hover:text-white'"
>Info</button> >信息</button>
<button <button
@click="filterLevel = 'error'" @click="filterLevel = 'error'"
class="px-3 py-1.5 rounded-lg text-xs font-medium transition-colors" class="px-3 py-1.5 rounded-lg text-xs font-medium transition-colors"
:class="filterLevel === 'error' ? 'bg-gray-100 dark:bg-zinc-800 text-zinc-900 dark:text-white' : 'text-gray-500 hover:text-zinc-900 dark:hover:text-white'" :class="filterLevel === 'error' ? 'bg-gray-100 dark:bg-zinc-800 text-zinc-900 dark:text-white' : 'text-gray-500 hover:text-zinc-900 dark:hover:text-white'"
>Error</button> >错误</button>
</div> </div>
<button <button
@click="logsStore.clearLogs" @click="logsStore.clearLogs"
class="p-2 text-gray-500 hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20 rounded-xl transition-colors border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-900" class="p-2 text-gray-500 hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20 rounded-xl transition-colors border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-900"
title="Clear Logs" title="清空日志"
> >
<Trash2 class="w-4 h-4" /> <Trash2 class="w-4 h-4" />
</button> </button>
@@ -118,7 +117,7 @@ function formatTime(ts: number) {
@scroll="handleScroll" @scroll="handleScroll"
> >
<div v-if="filteredLogs.length === 0" class="h-full flex items-center justify-center text-gray-400 dark:text-gray-600"> <div v-if="filteredLogs.length === 0" class="h-full flex items-center justify-center text-gray-400 dark:text-gray-600">
No logs to display 暂无日志
</div> </div>
<div v-for="log in filteredLogs" :key="log.id" class="flex gap-4 hover:bg-gray-50 dark:hover:bg-zinc-800/50 px-2 py-1 rounded transition-colors"> <div v-for="log in filteredLogs" :key="log.id" class="flex gap-4 hover:bg-gray-50 dark:hover:bg-zinc-800/50 px-2 py-1 rounded transition-colors">
<span class="text-gray-400 dark:text-zinc-600 shrink-0 select-none w-36">{{ formatTime(log.timestamp) }}</span> <span class="text-gray-400 dark:text-zinc-600 shrink-0 select-none w-36">{{ formatTime(log.timestamp) }}</span>
@@ -135,7 +134,7 @@ function formatTime(ts: number) {
@click="() => { if(logsContainer) { logsContainer.scrollTop = logsContainer.scrollHeight; logsStore.autoScroll = true; } }" @click="() => { if(logsContainer) { logsContainer.scrollTop = logsContainer.scrollHeight; logsStore.autoScroll = true; } }"
class="bg-blue-600 hover:bg-blue-700 text-white text-xs px-3 py-1.5 rounded-full shadow-lg transition-colors" class="bg-blue-600 hover:bg-blue-700 text-white text-xs px-3 py-1.5 rounded-full shadow-lg transition-colors"
> >
Resume Auto-scroll 恢复自动滚动
</button> </button>
</div> </div>
</div> </div>

View File

@@ -1,4 +1,3 @@
// filepath: src/views/Settings.vue
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import { useSettingsStore } from '../stores/settings' import { useSettingsStore } from '../stores/settings'
@@ -26,13 +25,13 @@ async function browsePath() {
async function updateYtdlp() { async function updateYtdlp() {
updatingYtdlp.value = true updatingYtdlp.value = true
updateStatus.value = 'Updating yt-dlp...' updateStatus.value = '正在更新 yt-dlp...'
try { try {
const res = await invoke<string>('update_ytdlp') const res = await invoke<string>('update_ytdlp')
updateStatus.value = res updateStatus.value = res
await settingsStore.refreshVersions() await settingsStore.refreshVersions()
} catch (e: any) { } catch (e: any) {
updateStatus.value = 'yt-dlp Error: ' + e.toString() updateStatus.value = 'yt-dlp 错误:' + e.toString()
} finally { } finally {
updatingYtdlp.value = false updatingYtdlp.value = false
} }
@@ -40,13 +39,13 @@ async function updateYtdlp() {
async function updateQuickjs() { async function updateQuickjs() {
updatingQuickjs.value = true updatingQuickjs.value = true
updateStatus.value = 'Updating QuickJS...' updateStatus.value = '正在更新 QuickJS...'
try { try {
const res = await invoke<string>('update_quickjs') const res = await invoke<string>('update_quickjs')
updateStatus.value = res updateStatus.value = res
await settingsStore.refreshVersions() await settingsStore.refreshVersions()
} catch (e: any) { } catch (e: any) {
updateStatus.value = 'QuickJS Error: ' + e.toString() updateStatus.value = 'QuickJS 错误:' + e.toString()
} finally { } finally {
updatingQuickjs.value = false updatingQuickjs.value = false
} }
@@ -61,31 +60,31 @@ function setTheme(theme: 'light' | 'dark' | 'system') {
<template> <template>
<div class="max-w-3xl mx-auto p-8"> <div class="max-w-3xl mx-auto p-8">
<header class="mb-8"> <header class="mb-8">
<h1 class="text-3xl font-bold text-zinc-900 dark:text-white">Settings</h1> <h1 class="text-3xl font-bold text-zinc-900 dark:text-white">设置</h1>
<p class="text-gray-500 dark:text-gray-400 mt-2">Configure your download preferences.</p> <p class="text-gray-500 dark:text-gray-400 mt-2">配置您的下载偏好</p>
</header> </header>
<div class="space-y-6"> <div class="space-y-6">
<!-- Download Path --> <!-- Download Path -->
<section class="bg-white dark:bg-zinc-900 p-6 rounded-2xl shadow-sm border border-gray-200 dark:border-zinc-800"> <section class="bg-white dark:bg-zinc-900 p-6 rounded-2xl shadow-sm border border-gray-200 dark:border-zinc-800">
<h2 class="text-lg font-bold mb-4 text-zinc-900 dark:text-white">Download Location</h2> <h2 class="text-lg font-bold mb-4 text-zinc-900 dark:text-white">下载位置</h2>
<div class="flex gap-3"> <div class="flex gap-3">
<div class="flex-1 bg-gray-50 dark:bg-zinc-800 rounded-xl px-4 py-3 text-sm text-gray-600 dark:text-gray-300 font-mono truncate border border-transparent focus-within:border-blue-500 transition-colors"> <div class="flex-1 bg-gray-50 dark:bg-zinc-800 rounded-xl px-4 py-3 text-sm text-gray-600 dark:text-gray-300 font-mono truncate border border-transparent focus-within:border-blue-500 transition-colors">
{{ settingsStore.settings.download_path || 'Not set (using defaults)' }} {{ settingsStore.settings.download_path || '未设置 (使用默认)' }}
</div> </div>
<button <button
@click="browsePath" @click="browsePath"
class="bg-gray-100 hover:bg-gray-200 dark:bg-zinc-800 dark:hover:bg-zinc-700 text-zinc-900 dark:text-white px-4 py-3 rounded-xl font-medium transition-colors flex items-center gap-2" class="bg-gray-100 hover:bg-gray-200 dark:bg-zinc-800 dark:hover:bg-zinc-700 text-zinc-900 dark:text-white px-4 py-3 rounded-xl font-medium transition-colors flex items-center gap-2"
> >
<Folder class="w-5 h-5" /> <Folder class="w-5 h-5" />
Browse 浏览
</button> </button>
</div> </div>
</section> </section>
<!-- Theme --> <!-- Theme -->
<section class="bg-white dark:bg-zinc-900 p-6 rounded-2xl shadow-sm border border-gray-200 dark:border-zinc-800"> <section class="bg-white dark:bg-zinc-900 p-6 rounded-2xl shadow-sm border border-gray-200 dark:border-zinc-800">
<h2 class="text-lg font-bold mb-4 text-zinc-900 dark:text-white">Appearance</h2> <h2 class="text-lg font-bold mb-4 text-zinc-900 dark:text-white">外观</h2>
<div class="grid grid-cols-3 gap-4"> <div class="grid grid-cols-3 gap-4">
<button <button
@click="setTheme('light')" @click="setTheme('light')"
@@ -93,7 +92,7 @@ function setTheme(theme: 'light' | 'dark' | 'system') {
:class="settingsStore.settings.theme === 'light' ? 'border-blue-600 bg-blue-50 dark:bg-blue-900/20 text-blue-600' : 'border-transparent bg-gray-50 dark:bg-zinc-800 text-gray-500 hover:bg-gray-100 dark:hover:bg-zinc-700'" :class="settingsStore.settings.theme === 'light' ? 'border-blue-600 bg-blue-50 dark:bg-blue-900/20 text-blue-600' : 'border-transparent bg-gray-50 dark:bg-zinc-800 text-gray-500 hover:bg-gray-100 dark:hover:bg-zinc-700'"
> >
<Sun class="w-6 h-6" /> <Sun class="w-6 h-6" />
<span class="font-medium">Light</span> <span class="font-medium">浅色</span>
</button> </button>
<button <button
@click="setTheme('dark')" @click="setTheme('dark')"
@@ -101,7 +100,7 @@ function setTheme(theme: 'light' | 'dark' | 'system') {
:class="settingsStore.settings.theme === 'dark' ? 'border-blue-600 bg-blue-50 dark:bg-blue-900/20 text-blue-600' : 'border-transparent bg-gray-50 dark:bg-zinc-800 text-gray-500 hover:bg-gray-100 dark:hover:bg-zinc-700'" :class="settingsStore.settings.theme === 'dark' ? 'border-blue-600 bg-blue-50 dark:bg-blue-900/20 text-blue-600' : 'border-transparent bg-gray-50 dark:bg-zinc-800 text-gray-500 hover:bg-gray-100 dark:hover:bg-zinc-700'"
> >
<Moon class="w-6 h-6" /> <Moon class="w-6 h-6" />
<span class="font-medium">Dark</span> <span class="font-medium">深色</span>
</button> </button>
<button <button
@click="setTheme('system')" @click="setTheme('system')"
@@ -109,14 +108,14 @@ function setTheme(theme: 'light' | 'dark' | 'system') {
:class="settingsStore.settings.theme === 'system' ? 'border-blue-600 bg-blue-50 dark:bg-blue-900/20 text-blue-600' : 'border-transparent bg-gray-50 dark:bg-zinc-800 text-gray-500 hover:bg-gray-100 dark:hover:bg-zinc-700'" :class="settingsStore.settings.theme === 'system' ? 'border-blue-600 bg-blue-50 dark:bg-blue-900/20 text-blue-600' : 'border-transparent bg-gray-50 dark:bg-zinc-800 text-gray-500 hover:bg-gray-100 dark:hover:bg-zinc-700'"
> >
<Monitor class="w-6 h-6" /> <Monitor class="w-6 h-6" />
<span class="font-medium">System</span> <span class="font-medium">跟随系统</span>
</button> </button>
</div> </div>
</section> </section>
<!-- Binary Management --> <!-- Binary Management -->
<section class="bg-white dark:bg-zinc-900 p-6 rounded-2xl shadow-sm border border-gray-200 dark:border-zinc-800"> <section class="bg-white dark:bg-zinc-900 p-6 rounded-2xl shadow-sm border border-gray-200 dark:border-zinc-800">
<h2 class="text-lg font-bold mb-4 text-zinc-900 dark:text-white">External Binaries</h2> <h2 class="text-lg font-bold mb-4 text-zinc-900 dark:text-white">外部二进制文件</h2>
<div class="space-y-4"> <div class="space-y-4">
<!-- yt-dlp --> <!-- yt-dlp -->
@@ -136,7 +135,7 @@ function setTheme(theme: 'light' | 'dark' | 'system') {
class="text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/20 px-4 py-2 rounded-lg transition-colors text-sm font-medium flex items-center gap-2 disabled:opacity-50" class="text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/20 px-4 py-2 rounded-lg transition-colors text-sm font-medium flex items-center gap-2 disabled:opacity-50"
> >
<RefreshCw class="w-4 h-4" :class="{ 'animate-spin': updatingYtdlp }" /> <RefreshCw class="w-4 h-4" :class="{ 'animate-spin': updatingYtdlp }" />
Update 更新
</button> </button>
</div> </div>
@@ -157,7 +156,7 @@ function setTheme(theme: 'light' | 'dark' | 'system') {
class="text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/20 px-4 py-2 rounded-lg transition-colors text-sm font-medium flex items-center gap-2 disabled:opacity-50" class="text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/20 px-4 py-2 rounded-lg transition-colors text-sm font-medium flex items-center gap-2 disabled:opacity-50"
> >
<RefreshCw class="w-4 h-4" :class="{ 'animate-spin': updatingQuickjs }" /> <RefreshCw class="w-4 h-4" :class="{ 'animate-spin': updatingQuickjs }" />
Update 更新
</button> </button>
</div> </div>
</div> </div>
@@ -165,7 +164,7 @@ function setTheme(theme: 'light' | 'dark' | 'system') {
<div v-if="updateStatus" class="mt-4 p-3 bg-gray-50 dark:bg-zinc-800 rounded-lg text-xs font-mono text-gray-600 dark:text-gray-400 whitespace-pre-wrap border border-gray-200 dark:border-zinc-700"> <div v-if="updateStatus" class="mt-4 p-3 bg-gray-50 dark:bg-zinc-800 rounded-lg text-xs font-mono text-gray-600 dark:text-gray-400 whitespace-pre-wrap border border-gray-200 dark:border-zinc-700">
{{ updateStatus }} {{ updateStatus }}
</div> </div>
<p class="text-xs text-gray-400 mt-4 text-right">Last Checked: {{ settingsStore.settings.last_updated ? new Date(settingsStore.settings.last_updated).toLocaleString() : 'Never' }}</p> <p class="text-xs text-gray-400 mt-4 text-right">上次检查 {{ settingsStore.settings.last_updated ? new Date(settingsStore.settings.last_updated).toLocaleString() : '从未' }}</p>
</section> </section>
</div> </div>
</div> </div>