截图模块初始化
This commit is contained in:
@@ -1,24 +1,205 @@
|
||||
<script setup lang="ts">
|
||||
import { Camera } from '@lucide/vue'
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import {
|
||||
Camera, Square, AppWindow, Maximize, Copy, Save, Trash2, Image as ImageIcon, Loader2,
|
||||
} from '@lucide/vue'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { useScreenshotStore, type CaptureMode, type RecentCapture } from '@/stores/screenshotStore'
|
||||
import { useModuleTabs } from '@/lib/useModuleTabs'
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const store = useScreenshotStore()
|
||||
|
||||
const activeTab = ref('capture')
|
||||
const tabsListRef = useModuleTabs(activeTab, [
|
||||
{ value: 'capture', label: '截图' },
|
||||
{ value: 'history', label: '历史' },
|
||||
])
|
||||
|
||||
const captureActions: { mode: CaptureMode; icon: typeof Square; label: string; desc: string }[] = [
|
||||
{ mode: 'region', icon: Square, label: '区域截图', desc: '拖动选择屏幕任意区域' },
|
||||
{ mode: 'window', icon: AppWindow, label: '窗口截图', desc: '点击捕获指定窗口' },
|
||||
{ mode: 'fullscreen', icon: Maximize, label: '全屏截图', desc: '直接捕获整个虚拟屏' },
|
||||
]
|
||||
|
||||
function formatTime(t: number): string {
|
||||
const d = new Date(t)
|
||||
const hh = String(d.getHours()).padStart(2, '0')
|
||||
const mm = String(d.getMinutes()).padStart(2, '0')
|
||||
const ss = String(d.getSeconds()).padStart(2, '0')
|
||||
return `${hh}:${mm}:${ss}`
|
||||
}
|
||||
|
||||
function thumbSrc(item: RecentCapture): string {
|
||||
return `data:image/png;base64,${item.pngBase64}`
|
||||
}
|
||||
|
||||
async function handleCapture(mode: CaptureMode) {
|
||||
await store.startCapture(mode)
|
||||
}
|
||||
|
||||
async function handleCopy(item: RecentCapture) {
|
||||
await store.copyImage(item.pngBase64)
|
||||
}
|
||||
|
||||
async function handleSave(item: RecentCapture) {
|
||||
await store.saveImage(item.pngBase64)
|
||||
}
|
||||
|
||||
function handleDelete(item: RecentCapture) {
|
||||
const idx = store.recent.findIndex(r => r.id === item.id)
|
||||
if (idx >= 0) store.recent.splice(idx, 1)
|
||||
toast.success('已从历史移除')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
store.initExportListener().catch(e => console.error('[screenshot] 导出监听初始化失败:', e))
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
store.destroyExportListener()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full p-6 overflow-y-auto">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="flex items-center gap-2">
|
||||
<Camera class="h-5 w-5 text-primary" />
|
||||
截图模块
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="flex flex-col items-center justify-center h-64 text-muted-foreground">
|
||||
<Camera class="h-16 w-16 mb-4 opacity-50" />
|
||||
<p>截图功能开发中...</p>
|
||||
<p class="text-sm mt-2">支持区域截图、窗口截图、全屏截图、滚动截图等功能</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div class="h-full overflow-hidden">
|
||||
<Tabs v-model="activeTab" class="h-full flex flex-col">
|
||||
<div ref="tabsListRef" class="px-4 pt-3 pb-2 shrink-0">
|
||||
<TabsList>
|
||||
<TabsTrigger value="capture">截图</TabsTrigger>
|
||||
<TabsTrigger value="history">
|
||||
历史
|
||||
<span v-if="store.recent.length" class="ml-1 text-xs text-muted-foreground">
|
||||
({{ store.recent.length }})
|
||||
</span>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
<!-- 截图触发 -->
|
||||
<TabsContent value="capture" class="flex-1 min-h-0 mt-0">
|
||||
<ScrollArea class="h-full">
|
||||
<div class="p-4 pt-0 space-y-4">
|
||||
<!-- 截图模式卡片 -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||
<Card
|
||||
v-for="action in captureActions"
|
||||
:key="action.mode"
|
||||
class="cursor-pointer transition-colors hover:border-primary/50 hover:bg-accent/50"
|
||||
:class="{ 'pointer-events-none opacity-60': store.capturing }"
|
||||
@click="handleCapture(action.mode)"
|
||||
>
|
||||
<CardContent class="flex flex-col items-center gap-2 py-6 text-center">
|
||||
<div class="flex items-center justify-center h-12 w-12 rounded-full bg-primary/10 text-primary">
|
||||
<component :is="action.icon" class="h-6 w-6" />
|
||||
</div>
|
||||
<div class="font-medium">{{ action.label }}</div>
|
||||
<div class="text-xs text-muted-foreground">{{ action.desc }}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- 截图中提示 -->
|
||||
<Card v-if="store.capturing">
|
||||
<CardContent class="flex items-center justify-center gap-2 py-8 text-muted-foreground">
|
||||
<Loader2 class="h-5 w-5 animate-spin text-primary" />
|
||||
<span>正在捕获屏幕…</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- 说明 -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-sm flex items-center gap-2">
|
||||
<Camera class="h-4 w-4 text-primary" />
|
||||
使用说明
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="text-sm text-muted-foreground space-y-1.5">
|
||||
<p>· <span class="text-foreground">区域截图</span>:进入覆盖层后拖动鼠标选择区域,松开后可编辑、复制或保存。</p>
|
||||
<p>· <span class="text-foreground">窗口截图</span>:移动鼠标高亮目标窗口,点击即可捕获该窗口。</p>
|
||||
<p>· <span class="text-foreground">全屏截图</span>:直接捕获所有显示器拼接画面并进入编辑器。</p>
|
||||
<p>· 选区/编辑器中按 <kbd class="px-1 py-0.5 text-xs rounded bg-muted border">Esc</kbd> 取消。</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</TabsContent>
|
||||
|
||||
<!-- 历史记录 -->
|
||||
<TabsContent value="history" class="flex-1 min-h-0 mt-0">
|
||||
<ScrollArea class="h-full">
|
||||
<div class="p-4 pt-0">
|
||||
<!-- 空状态 -->
|
||||
<div
|
||||
v-if="store.recent.length === 0"
|
||||
class="flex flex-col items-center justify-center h-64 text-muted-foreground gap-3"
|
||||
>
|
||||
<ImageIcon class="h-12 w-12 opacity-40" />
|
||||
<p>暂无截图历史</p>
|
||||
<p class="text-xs">截图并导出后会显示在这里</p>
|
||||
</div>
|
||||
|
||||
<!-- 历史网格 -->
|
||||
<div v-else class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3">
|
||||
<div
|
||||
v-for="item in store.recent"
|
||||
:key="item.id"
|
||||
class="group relative rounded-lg border bg-muted/30 overflow-hidden"
|
||||
>
|
||||
<!-- 缩略图 -->
|
||||
<div class="aspect-video flex items-center justify-center bg-zinc-900">
|
||||
<img
|
||||
:src="thumbSrc(item)"
|
||||
class="max-w-full max-h-full object-contain"
|
||||
alt="截图"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 信息 -->
|
||||
<div class="px-2 py-1.5 flex items-center justify-between text-xs text-muted-foreground">
|
||||
<span>{{ formatTime(item.time) }}</span>
|
||||
<span>{{ item.width }}×{{ item.height }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 悬浮操作 -->
|
||||
<div
|
||||
class="absolute inset-0 flex items-center justify-center gap-2 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon-sm"
|
||||
title="复制到剪贴板"
|
||||
@click.stop="handleCopy(item)"
|
||||
>
|
||||
<Copy class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon-sm"
|
||||
title="保存到文件"
|
||||
@click.stop="handleSave(item)"
|
||||
>
|
||||
<Save class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon-sm"
|
||||
class="text-destructive hover:text-destructive"
|
||||
title="从历史移除"
|
||||
@click.stop="handleDelete(item)"
|
||||
>
|
||||
<Trash2 class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user