初步完成框架
This commit is contained in:
@@ -1,73 +1,14 @@
|
||||
export default defineAppConfig({
|
||||
app: {
|
||||
name: "Nuxtor",
|
||||
author: "Nicola Spadari",
|
||||
repo: "https://github.com/NicolaSpadari/nuxtor",
|
||||
tauriSite: "https://tauri.app",
|
||||
nuxtSite: "https://nuxt.com",
|
||||
nuxtUiSite: "https://ui.nuxt.dev"
|
||||
},
|
||||
pageCategories: {
|
||||
system: {
|
||||
label: "System",
|
||||
icon: "lucide:square-terminal"
|
||||
},
|
||||
storage: {
|
||||
label: "Storage",
|
||||
icon: "lucide:archive"
|
||||
},
|
||||
interface: {
|
||||
label: "Interface",
|
||||
icon: "lucide:app-window-mac"
|
||||
},
|
||||
other: {
|
||||
label: "Other",
|
||||
icon: "lucide:folder"
|
||||
}
|
||||
name: "视频控制器",
|
||||
version: "1.0.0",
|
||||
author: "estel",
|
||||
description: "基于 Nuxt 4 + Tauri 2 的视频播放控制应用"
|
||||
},
|
||||
ui: {
|
||||
colors: {
|
||||
primary: "green",
|
||||
primary: "blue",
|
||||
neutral: "zinc"
|
||||
},
|
||||
button: {
|
||||
slots: {
|
||||
base: "cursor-pointer"
|
||||
}
|
||||
},
|
||||
formField: {
|
||||
slots: {
|
||||
root: "w-full"
|
||||
}
|
||||
},
|
||||
input: {
|
||||
slots: {
|
||||
root: "w-full"
|
||||
}
|
||||
},
|
||||
textarea: {
|
||||
slots: {
|
||||
root: "w-full",
|
||||
base: "resize-none"
|
||||
}
|
||||
},
|
||||
accordion: {
|
||||
slots: {
|
||||
trigger: "cursor-pointer",
|
||||
item: "md:py-2"
|
||||
}
|
||||
},
|
||||
navigationMenu: {
|
||||
slots: {
|
||||
link: "cursor-pointer"
|
||||
},
|
||||
variants: {
|
||||
disabled: {
|
||||
true: {
|
||||
link: "cursor-text"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -1,11 +0,0 @@
|
||||
<template>
|
||||
<div class="top=1/5 pointer-events-none absolute inset-x-0 transform-gpu blur-3xl -z-10" aria-hidden="true">
|
||||
<div class="blob relative left-[calc(50%+36rem)] aspect-[1155/678] w-[72.1875rem] from-(--color-warning) to-(--color-success) bg-gradient-to-br opacity-30 -translate-x-1/2" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.blob{
|
||||
clip-path: polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%);
|
||||
}
|
||||
</style>
|
@@ -1,11 +0,0 @@
|
||||
<template>
|
||||
<div class="pointer-events-none absolute inset-x-0 transform-gpu blur-3xl -top-1/4 -z-10" aria-hidden="true">
|
||||
<div class="blob relative left-[calc(50%-30rem)] aspect-[1155/678] w-[72.1875rem] rotate-30 from-(--color-warning) to-(--color-success) bg-gradient-to-tr opacity-30 -translate-x-1/2" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.blob{
|
||||
clip-path: polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%);
|
||||
}
|
||||
</style>
|
@@ -1,72 +0,0 @@
|
||||
<template>
|
||||
<header class="top-0 z-10">
|
||||
<UContainer class="md:py-2">
|
||||
<UNavigationMenu
|
||||
:items="mobileItems"
|
||||
variant="link"
|
||||
:ui="{
|
||||
root: 'md:hidden'
|
||||
}"
|
||||
/>
|
||||
<UNavigationMenu
|
||||
:items="desktopItems"
|
||||
variant="link"
|
||||
:ui="{
|
||||
root: 'hidden md:flex',
|
||||
viewportWrapper: 'max-w-2xl absolute-center-h',
|
||||
list: 'md:gap-x-2'
|
||||
}"
|
||||
/>
|
||||
</UContainer>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { pages } = usePages();
|
||||
const { showSidebar } = useSidebar();
|
||||
const tauriVersion = await useTauriAppGetTauriVersion();
|
||||
|
||||
const mobileItems = ref<any[]>([
|
||||
[
|
||||
{
|
||||
avatar: {
|
||||
icon: "local:logo",
|
||||
size: "xl",
|
||||
ui: {
|
||||
root: "bg-transparent"
|
||||
}
|
||||
},
|
||||
to: "/"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
icon: "lucide:menu",
|
||||
onSelect: () => showSidebar.value = true
|
||||
}
|
||||
]
|
||||
]);
|
||||
|
||||
const desktopItems = ref<any[]>([
|
||||
[
|
||||
{
|
||||
avatar: {
|
||||
icon: "local:logo",
|
||||
size: "3xl",
|
||||
ui: {
|
||||
root: "group bg-transparent",
|
||||
icon: "opacity-70 group-hover:opacity-100"
|
||||
}
|
||||
},
|
||||
to: "/"
|
||||
}
|
||||
],
|
||||
pages,
|
||||
[
|
||||
{
|
||||
label: `v${tauriVersion}`,
|
||||
disabled: true
|
||||
}
|
||||
]
|
||||
]);
|
||||
</script>
|
@@ -1,37 +0,0 @@
|
||||
<template>
|
||||
<USlideover :open="showSidebar" @update:open="showSidebar = false">
|
||||
<template #title>
|
||||
<div class="flex gap-x-3">
|
||||
<Icon name="local:logo" class="size-6" />
|
||||
<span class="uppercase">{{ name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #description>
|
||||
<VisuallyHidden>Description</VisuallyHidden>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<UNavigationMenu
|
||||
orientation="vertical"
|
||||
:items="items"
|
||||
/>
|
||||
</template>
|
||||
</USlideover>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { app: { name } } = useAppConfig();
|
||||
const { pages } = usePages();
|
||||
const { showSidebar } = useSidebar();
|
||||
const tauriVersion = await useTauriAppGetTauriVersion();
|
||||
|
||||
const items = ref<any[]>([
|
||||
pages,
|
||||
[
|
||||
{
|
||||
label: `v${tauriVersion}`,
|
||||
disabled: true
|
||||
}
|
||||
]
|
||||
]);
|
||||
</script>
|
@@ -1,8 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<SiteNavbar class="sticky bg-(--ui-bg)/75 backdrop-blur" />
|
||||
<SiteSidebar />
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
@@ -1,10 +1,60 @@
|
||||
<template>
|
||||
<div>
|
||||
<SiteNavbar class="sticky bg-(--ui-bg)/75 backdrop-blur" />
|
||||
<SiteSidebar />
|
||||
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 flex">
|
||||
<!-- 左侧边栏 -->
|
||||
<aside class="w-64 bg-white dark:bg-gray-800 shadow-sm border-r border-gray-200 dark:border-gray-700 flex flex-col">
|
||||
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
|
||||
<h1 class="text-xl font-semibold text-gray-900 dark:text-white">视频控制器</h1>
|
||||
</div>
|
||||
|
||||
<nav class="flex-1 p-4">
|
||||
<ul class="space-y-2">
|
||||
<li>
|
||||
<NuxtLink
|
||||
to="/"
|
||||
class="flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors"
|
||||
:class="$route.path === '/'
|
||||
? 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-200'
|
||||
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'"
|
||||
>
|
||||
<UIcon name="i-heroicons-home" class="mr-3 h-5 w-5" />
|
||||
首页
|
||||
</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink
|
||||
to="/settings"
|
||||
class="flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors"
|
||||
:class="$route.path === '/settings'
|
||||
? 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-200'
|
||||
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'"
|
||||
>
|
||||
<UIcon name="i-heroicons-cog-6-tooth" class="mr-3 h-5 w-5" />
|
||||
设置
|
||||
</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- 关于 -->
|
||||
<div class="p-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<NuxtLink
|
||||
to="/about"
|
||||
class="flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors"
|
||||
:class="$route.path === '/about'
|
||||
? 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-200'
|
||||
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'"
|
||||
>
|
||||
<UIcon name="i-heroicons-information-circle" class="mr-3 h-5 w-5" />
|
||||
关于
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<UContainer>
|
||||
<slot />
|
||||
</UContainer>
|
||||
<!-- 主体区域 -->
|
||||
<main class="flex-1 overflow-auto">
|
||||
<div class="p-6">
|
||||
<slot />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
@@ -1,13 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<SiteNavbar class="fixed w-full" />
|
||||
<SiteSidebar />
|
||||
|
||||
<div class="relative overflow-hidden px-6 lg:px-8">
|
||||
<DesignTopBlob />
|
||||
<DesignBottomBlob />
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@@ -1,26 +0,0 @@
|
||||
<template>
|
||||
<div class="grid place-items-center py-12 md:py-24">
|
||||
<div class="flex flex-col items-center gap-y-4 md:gap-y-8">
|
||||
<p class="text-(--ui-success) font-semibold">
|
||||
404
|
||||
</p>
|
||||
<div class="text-center space-y-3">
|
||||
<h1 class="text-3xl font-bold tracking-tight" sm="text-5xl">
|
||||
Page not found
|
||||
</h1>
|
||||
<p class="text-base text-(--ui-muted) leading-7">
|
||||
Sorry, we couldn't find the page you're looking for.
|
||||
</p>
|
||||
</div>
|
||||
<UButton to="/" variant="outline" size="lg" :ui="{ base: 'px-5' }">
|
||||
Go home
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
layout: "blank"
|
||||
});
|
||||
</script>
|
252
app/pages/about.vue
Normal file
252
app/pages/about.vue
Normal file
@@ -0,0 +1,252 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- 应用信息 -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">应用信息</h2>
|
||||
</template>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="w-16 h-16 bg-blue-100 dark:bg-blue-900 rounded-lg flex items-center justify-center">
|
||||
<UIcon name="i-heroicons-tv" class="w-8 h-8 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-white">{{ appInfo.name }}</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400">版本 {{ appInfo.version }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<p class="text-gray-700 dark:text-gray-300">{{ appInfo.description }}</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
构建时间: {{ appInfo.buildDate }}
|
||||
</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
作者: {{ appInfo.author }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<!-- 技术栈 -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">技术栈</h2>
|
||||
</template>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="space-y-3">
|
||||
<h4 class="font-medium text-gray-900 dark:text-white">前端技术</h4>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-6 h-6 bg-green-100 dark:bg-green-900 rounded flex items-center justify-center">
|
||||
<div class="w-3 h-3 bg-green-500 rounded"></div>
|
||||
</div>
|
||||
<span class="text-sm">Nuxt 4 - 全栈 Vue.js 框架</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-6 h-6 bg-blue-100 dark:bg-blue-900 rounded flex items-center justify-center">
|
||||
<div class="w-3 h-3 bg-blue-500 rounded"></div>
|
||||
</div>
|
||||
<span class="text-sm">Vue 3 - 渐进式 JavaScript 框架</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-6 h-6 bg-cyan-100 dark:bg-cyan-900 rounded flex items-center justify-center">
|
||||
<div class="w-3 h-3 bg-cyan-500 rounded"></div>
|
||||
</div>
|
||||
<span class="text-sm">Tailwind CSS - 原子化 CSS 框架</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-6 h-6 bg-emerald-100 dark:bg-emerald-900 rounded flex items-center justify-center">
|
||||
<div class="w-3 h-3 bg-emerald-500 rounded"></div>
|
||||
</div>
|
||||
<span class="text-sm">Nuxt UI - 现代化 UI 组件库</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<h4 class="font-medium text-gray-900 dark:text-white">后端技术</h4>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-6 h-6 bg-orange-100 dark:bg-orange-900 rounded flex items-center justify-center">
|
||||
<div class="w-3 h-3 bg-orange-500 rounded"></div>
|
||||
</div>
|
||||
<span class="text-sm">Tauri 2 - 跨平台桌面应用框架</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-6 h-6 bg-red-100 dark:bg-red-900 rounded flex items-center justify-center">
|
||||
<div class="w-3 h-3 bg-red-500 rounded"></div>
|
||||
</div>
|
||||
<span class="text-sm">Rust - 系统级编程语言</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-6 h-6 bg-purple-100 dark:bg-purple-900 rounded flex items-center justify-center">
|
||||
<div class="w-3 h-3 bg-purple-500 rounded"></div>
|
||||
</div>
|
||||
<span class="text-sm">WebSocket - 实时通信协议</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<!-- 功能特性 -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">功能特性</h2>
|
||||
</template>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<UIcon name="i-heroicons-check-circle" class="w-5 h-5 text-green-500" />
|
||||
<span class="text-sm">远程视频播放控制</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<UIcon name="i-heroicons-check-circle" class="w-5 h-5 text-green-500" />
|
||||
<span class="text-sm">实时播放进度同步</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<UIcon name="i-heroicons-check-circle" class="w-5 h-5 text-green-500" />
|
||||
<span class="text-sm">音量调节控制</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<UIcon name="i-heroicons-check-circle" class="w-5 h-5 text-green-500" />
|
||||
<span class="text-sm">播放列表管理</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<UIcon name="i-heroicons-check-circle" class="w-5 h-5 text-green-500" />
|
||||
<span class="text-sm">循环播放设置</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<UIcon name="i-heroicons-check-circle" class="w-5 h-5 text-green-500" />
|
||||
<span class="text-sm">全屏控制</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<UIcon name="i-heroicons-check-circle" class="w-5 h-5 text-green-500" />
|
||||
<span class="text-sm">自动重连功能</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<UIcon name="i-heroicons-check-circle" class="w-5 h-5 text-green-500" />
|
||||
<span class="text-sm">响应式界面设计</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<!-- 系统要求 -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">系统要求</h2>
|
||||
</template>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h4 class="font-medium text-gray-900 dark:text-white mb-2">支持的操作系统</h4>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="flex items-center space-x-2 text-sm">
|
||||
<UIcon name="i-heroicons-computer-desktop" class="w-5 h-5 text-gray-500" />
|
||||
<span>Windows 10/11</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 text-sm">
|
||||
<UIcon name="i-heroicons-computer-desktop" class="w-5 h-5 text-gray-500" />
|
||||
<span>macOS 10.15+</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 text-sm">
|
||||
<UIcon name="i-heroicons-computer-desktop" class="w-5 h-5 text-gray-500" />
|
||||
<span>Linux (x64)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="font-medium text-gray-900 dark:text-white mb-2">网络要求</h4>
|
||||
<ul class="text-sm text-gray-600 dark:text-gray-400 space-y-1">
|
||||
<li>• 与视频播放器处于同一局域网</li>
|
||||
<li>• TCP/IP 网络连接</li>
|
||||
<li>• 端口访问权限 (默认 8080)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<!-- 开源许可 -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">开源许可</h2>
|
||||
</template>
|
||||
<div class="space-y-3">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
本软件基于 MIT 许可证开源,您可以自由使用、修改和分发。
|
||||
</p>
|
||||
<div class="flex space-x-4">
|
||||
<UButton variant="outline" size="sm">
|
||||
<UIcon name="i-heroicons-code-bracket" class="w-4 h-4 mr-2" />
|
||||
查看源码
|
||||
</UButton>
|
||||
<UButton variant="outline" size="sm">
|
||||
<UIcon name="i-heroicons-document-text" class="w-4 h-4 mr-2" />
|
||||
许可协议
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<!-- 反馈和支持 -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">反馈和支持</h2>
|
||||
</template>
|
||||
<div class="space-y-4">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
遇到问题或有建议?欢迎通过以下方式联系我们:
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<UButton variant="outline" size="sm">
|
||||
<UIcon name="i-heroicons-bug-ant" class="w-4 h-4 mr-2" />
|
||||
报告问题
|
||||
</UButton>
|
||||
<UButton variant="outline" size="sm">
|
||||
<UIcon name="i-heroicons-light-bulb" class="w-4 h-4 mr-2" />
|
||||
功能建议
|
||||
</UButton>
|
||||
<UButton variant="outline" size="sm">
|
||||
<UIcon name="i-heroicons-question-mark-circle" class="w-4 h-4 mr-2" />
|
||||
使用帮助
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
interface AppInfo {
|
||||
name: string
|
||||
version: string
|
||||
description: string
|
||||
author: string
|
||||
buildDate: string
|
||||
}
|
||||
|
||||
const { app } = useAppConfig()
|
||||
|
||||
// 应用信息
|
||||
const appInfo = ref<AppInfo>({
|
||||
name: app.name,
|
||||
version: app.version,
|
||||
description: app.description,
|
||||
author: app.author,
|
||||
buildDate: new Date().toLocaleDateString('zh-CN')
|
||||
})
|
||||
|
||||
// 页面加载时获取构建信息
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// TODO: 调用 Tauri API 获取应用构建信息
|
||||
console.log('获取应用信息')
|
||||
} catch (error) {
|
||||
console.error('获取应用信息失败:', error)
|
||||
}
|
||||
})
|
||||
</script>
|
@@ -1,63 +0,0 @@
|
||||
<template>
|
||||
<LayoutTile
|
||||
title="Commands"
|
||||
description="Access the system shell. Allows you to spawn child processes and manage files and URLs using their default application."
|
||||
>
|
||||
<div class="space-y-6 md:space-y-8">
|
||||
<UForm :state="inputState" :schema="schema" class="flex flex-col gap-y-4 items-end" @submit="sendCommand">
|
||||
<UFormField label="Command input" name="input">
|
||||
<UInput v-model="inputState.input" variant="subtle" size="lg" />
|
||||
</UFormField>
|
||||
|
||||
<UButton type="submit" size="lg">
|
||||
Send command
|
||||
</UButton>
|
||||
</UForm>
|
||||
|
||||
<UForm :state="outputState" class="flex flex-col gap-y-4 items-end">
|
||||
<UFormField label="Command output" name="command-output">
|
||||
<UTextarea v-model="outputState.output" variant="subtle" size="lg" :rows="8" readonly />
|
||||
</UFormField>
|
||||
</UForm>
|
||||
</div>
|
||||
</LayoutTile>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
name: "Shell commands",
|
||||
icon: "lucide:terminal",
|
||||
description: "Execute shell commands",
|
||||
category: "system"
|
||||
});
|
||||
|
||||
const schema = z.object({
|
||||
input: z.string({
|
||||
error: "Input is required"
|
||||
}).nonempty()
|
||||
});
|
||||
|
||||
type Schema = zInfer<typeof schema>;
|
||||
|
||||
const inputState = ref<Partial<Schema>>({
|
||||
input: undefined
|
||||
});
|
||||
const outputState = ref({
|
||||
output: ""
|
||||
});
|
||||
|
||||
const sendCommand = async () => {
|
||||
try {
|
||||
const response = await useTauriShellCommand.create("exec-sh", [
|
||||
"-c",
|
||||
inputState.value.input!
|
||||
]).execute();
|
||||
|
||||
outputState.value.output = JSON.stringify(response, null, 4);
|
||||
} catch (error) {
|
||||
outputState.value.output = JSON.stringify(error, null, 4);
|
||||
} finally {
|
||||
inputState.value.input = undefined;
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -1,83 +0,0 @@
|
||||
<template>
|
||||
<LayoutTile
|
||||
title="File System"
|
||||
description="Access the file system. For this demo the only allowed permission is read/write to the Documents folder (no sub directories)."
|
||||
>
|
||||
<UForm :state="inputState" :schema="schema" class="flex flex-col gap-y-4 items-end" @submit="createFile">
|
||||
<UFormField label="Text file name (with extension)" name="fileName">
|
||||
<UInput v-model="inputState.fileName" variant="subtle" size="lg" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="File content" name="fileContent">
|
||||
<UTextarea v-model="inputState.fileContent" variant="subtle" size="lg" :rows="10" />
|
||||
</UFormField>
|
||||
|
||||
<UButton type="submit" size="lg">
|
||||
Create file
|
||||
</UButton>
|
||||
</UForm>
|
||||
</LayoutTile>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
name: "Files",
|
||||
icon: "lucide:file-text",
|
||||
category: "storage",
|
||||
description: "Create and manage files"
|
||||
});
|
||||
|
||||
const schema = z.object({
|
||||
fileName: z.string({
|
||||
error: "File name is required"
|
||||
}).nonempty().regex(/^[\w,\s-]+\.[A-Z0-9]+$/i, {
|
||||
message: "Invalid filename format"
|
||||
}),
|
||||
fileContent: z.string({
|
||||
error: "File content is required"
|
||||
}).nonempty()
|
||||
});
|
||||
|
||||
type Schema = zInfer<typeof schema>;
|
||||
|
||||
const inputState = ref<Partial<Schema>>({
|
||||
fileName: undefined,
|
||||
fileContent: undefined
|
||||
});
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
const createFile = async () => {
|
||||
try {
|
||||
const fileExists = await useTauriFsExists(inputState.value.fileName!, {
|
||||
baseDir: useTauriFsBaseDirectory.Document
|
||||
});
|
||||
|
||||
if (fileExists) {
|
||||
toast.add({
|
||||
title: "Error",
|
||||
description: "The file already exists",
|
||||
color: "error"
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await useTauriFsWriteTextFile(inputState.value.fileName!, inputState.value.fileContent!, {
|
||||
baseDir: useTauriFsBaseDirectory.Document
|
||||
});
|
||||
toast.add({
|
||||
title: "Success",
|
||||
description: "The file has been created",
|
||||
color: "success"
|
||||
});
|
||||
inputState.value.fileName = inputState.value.fileContent = undefined;
|
||||
} catch (err) {
|
||||
toast.add({
|
||||
title: "Error",
|
||||
description: String(err),
|
||||
color: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -1,73 +1,252 @@
|
||||
<template>
|
||||
<UContainer class="relative overflow-hidden h-screen">
|
||||
<div class="grid size-full place-content-center gap-y-8">
|
||||
<SvgoLogo :filled="true" :font-controlled="false" class="mx-auto size-40" />
|
||||
<div class="space-y-6">
|
||||
<!-- 连接状态 -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">连接状态</h2>
|
||||
</template>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div :class="connectionStatus === 'connected' ? 'bg-green-500' : connectionStatus === 'connecting' ? 'bg-yellow-500' : 'bg-red-500'" class="w-3 h-3 rounded-full"></div>
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ connectionStatusText }}
|
||||
</span>
|
||||
</div>
|
||||
<UButton
|
||||
@click="toggleConnection"
|
||||
:variant="connectionStatus === 'connected' ? 'soft' : 'solid'"
|
||||
:color="connectionStatus === 'connected' ? 'red' : 'blue'"
|
||||
size="sm"
|
||||
>
|
||||
{{ connectionStatus === 'connected' ? '断开连接' : '连接' }}
|
||||
</UButton>
|
||||
</div>
|
||||
<div class="mt-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
<p>视频播放器地址: {{ playerAddress }}</p>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<div class="flex flex-col items-center gap-y-3">
|
||||
<h1 class="animate-pulse text-3xl sm:text-4xl text-pretty font-bold font-heading md:mb-5">
|
||||
{{ app.name.toUpperCase() }}
|
||||
</h1>
|
||||
<p class="leading-7 text-pretty">
|
||||
Powered by
|
||||
</p>
|
||||
<!-- 视频预览区域 -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">视频预览</h2>
|
||||
</template>
|
||||
<div class="bg-black rounded-lg aspect-video flex items-center justify-center">
|
||||
<div v-if="currentVideo" class="text-center">
|
||||
<UIcon name="i-heroicons-film" class="w-16 h-16 text-gray-400 mx-auto mb-2" />
|
||||
<p class="text-white text-sm">{{ currentVideo }}</p>
|
||||
</div>
|
||||
<div v-else class="text-center">
|
||||
<UIcon name="i-heroicons-video-camera-slash" class="w-16 h-16 text-gray-600 mx-auto mb-2" />
|
||||
<p class="text-gray-400 text-sm">暂无视频</p>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<div class="flex flex-wrap justify-center gap-1 md:gap-3">
|
||||
<UButton
|
||||
variant="ghost"
|
||||
size="xl"
|
||||
:to="app.nuxtSite"
|
||||
target="_blank"
|
||||
external
|
||||
>
|
||||
Nuxt 4
|
||||
<!-- 播放控制 -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">播放控制</h2>
|
||||
</template>
|
||||
<div class="space-y-4">
|
||||
<!-- 主要控制按钮 -->
|
||||
<div class="flex justify-center space-x-4">
|
||||
<UButton @click="playVideo" :disabled="!isConnected" color="green" size="lg">
|
||||
<UIcon name="i-heroicons-play" class="w-5 h-5 mr-2" />
|
||||
播放
|
||||
</UButton>
|
||||
<UButton
|
||||
variant="ghost"
|
||||
size="xl"
|
||||
:to="app.tauriSite"
|
||||
target="_blank"
|
||||
external
|
||||
>
|
||||
Tauri 2
|
||||
<UButton @click="pauseVideo" :disabled="!isConnected" color="orange" size="lg">
|
||||
<UIcon name="i-heroicons-pause" class="w-5 h-5 mr-2" />
|
||||
暂停
|
||||
</UButton>
|
||||
<UButton
|
||||
variant="ghost"
|
||||
size="xl"
|
||||
:to="app.nuxtUiSite"
|
||||
target="_blank"
|
||||
external
|
||||
>
|
||||
NuxtUI 3
|
||||
<UButton @click="stopVideo" :disabled="!isConnected" color="red" size="lg">
|
||||
<UIcon name="i-heroicons-stop" class="w-5 h-5 mr-2" />
|
||||
停止
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<!-- 进度控制 -->
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">播放进度</label>
|
||||
<div class="flex items-center space-x-4">
|
||||
<URange v-model="progress" :min="0" :max="100" :disabled="!isConnected" class="flex-1" />
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400 min-w-[3rem]">{{ progress }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 音量控制 -->
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">音量</label>
|
||||
<div class="flex items-center space-x-4">
|
||||
<UIcon name="i-heroicons-speaker-x-mark" class="w-5 h-5 text-gray-400" />
|
||||
<URange v-model="volume" :min="0" :max="100" :disabled="!isConnected" class="flex-1" />
|
||||
<UIcon name="i-heroicons-speaker-wave" class="w-5 h-5 text-gray-400" />
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400 min-w-[3rem]">{{ volume }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 额外功能 -->
|
||||
<div class="flex flex-wrap gap-2 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<UButton @click="toggleLoop" :disabled="!isConnected" :variant="isLooping ? 'solid' : 'outline'" size="sm">
|
||||
<UIcon name="i-heroicons-arrow-path" class="w-4 h-4 mr-1" />
|
||||
循环播放
|
||||
</UButton>
|
||||
<UButton @click="toggleFullscreen" :disabled="!isConnected" variant="outline" size="sm">
|
||||
<UIcon name="i-heroicons-arrows-pointing-out" class="w-4 h-4 mr-1" />
|
||||
全屏
|
||||
</UButton>
|
||||
<UButton @click="openVideoFile" variant="outline" size="sm">
|
||||
<UIcon name="i-heroicons-folder-open" class="w-4 h-4 mr-1" />
|
||||
打开文件
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<UButton
|
||||
:to="app.repo"
|
||||
>
|
||||
Star on GitHub
|
||||
</UButton>
|
||||
<!-- 播放列表 -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">播放列表</h2>
|
||||
<UButton @click="clearPlaylist" variant="ghost" size="sm" color="red">
|
||||
<UIcon name="i-heroicons-trash" class="w-4 h-4 mr-1" />
|
||||
清空
|
||||
</UButton>
|
||||
</div>
|
||||
</template>
|
||||
<div class="space-y-2">
|
||||
<div v-if="playlist.length === 0" class="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||
<UIcon name="i-heroicons-queue-list" class="w-12 h-12 mx-auto mb-2 text-gray-300" />
|
||||
<p>播放列表为空</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div
|
||||
v-for="(item, index) in playlist"
|
||||
:key="index"
|
||||
class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800 rounded-lg"
|
||||
>
|
||||
<div class="flex items-center space-x-3">
|
||||
<UIcon name="i-heroicons-film" class="w-5 h-5 text-gray-400" />
|
||||
<span class="text-sm font-medium">{{ item }}</span>
|
||||
</div>
|
||||
<div class="flex space-x-1">
|
||||
<UButton @click="playVideoFromPlaylist(index)" size="xs" variant="ghost">
|
||||
<UIcon name="i-heroicons-play" class="w-4 h-4" />
|
||||
</UButton>
|
||||
<UButton @click="removeFromPlaylist(index)" size="xs" variant="ghost" color="red">
|
||||
<UIcon name="i-heroicons-x-mark" class="w-4 h-4" />
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fixed bottom-6 text-sm absolute-center-h">
|
||||
<div class="flex items-center gap-1 text-(--ui-text-muted)">
|
||||
<p class="text-sm">
|
||||
Made by
|
||||
</p>
|
||||
<ULink :to="app.repo" external target="_blank">
|
||||
{{ app.author }}
|
||||
</ULink>
|
||||
</div>
|
||||
</div>
|
||||
</UContainer>
|
||||
</UCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { app } = useAppConfig();
|
||||
interface VideoControllerState {
|
||||
connectionStatus: 'connected' | 'connecting' | 'disconnected'
|
||||
currentVideo: string | null
|
||||
progress: number
|
||||
volume: number
|
||||
isLooping: boolean
|
||||
playlist: string[]
|
||||
playerAddress: string
|
||||
}
|
||||
|
||||
definePageMeta({
|
||||
layout: "home"
|
||||
});
|
||||
// 响应式状态
|
||||
const connectionStatus = ref<VideoControllerState['connectionStatus']>('disconnected')
|
||||
const currentVideo = ref<string | null>(null)
|
||||
const progress = ref(0)
|
||||
const volume = ref(50)
|
||||
const isLooping = ref(false)
|
||||
const playlist = ref<string[]>([])
|
||||
const playerAddress = ref('192.168.1.100:8080')
|
||||
|
||||
// 计算属性
|
||||
const isConnected = computed(() => connectionStatus.value === 'connected')
|
||||
|
||||
const connectionStatusText = computed(() => {
|
||||
switch (connectionStatus.value) {
|
||||
case 'connected': return '已连接'
|
||||
case 'connecting': return '连接中...'
|
||||
default: return '未连接'
|
||||
}
|
||||
})
|
||||
|
||||
// 方法
|
||||
const toggleConnection = async () => {
|
||||
if (connectionStatus.value === 'connected') {
|
||||
// 断开连接
|
||||
connectionStatus.value = 'disconnected'
|
||||
} else {
|
||||
// 尝试连接
|
||||
connectionStatus.value = 'connecting'
|
||||
// TODO: 在这里调用 Tauri API 进行实际连接
|
||||
setTimeout(() => {
|
||||
connectionStatus.value = 'connected' // 模拟连接成功
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
const playVideo = async () => {
|
||||
// TODO: 调用 Tauri API
|
||||
console.log('播放视频')
|
||||
}
|
||||
|
||||
const pauseVideo = async () => {
|
||||
// TODO: 调用 Tauri API
|
||||
console.log('暂停视频')
|
||||
}
|
||||
|
||||
const stopVideo = async () => {
|
||||
// TODO: 调用 Tauri API
|
||||
console.log('停止视频')
|
||||
}
|
||||
|
||||
const toggleLoop = async () => {
|
||||
isLooping.value = !isLooping.value
|
||||
// TODO: 调用 Tauri API
|
||||
console.log('循环播放:', isLooping.value)
|
||||
}
|
||||
|
||||
const toggleFullscreen = async () => {
|
||||
// TODO: 调用 Tauri API
|
||||
console.log('切换全屏')
|
||||
}
|
||||
|
||||
const openVideoFile = async () => {
|
||||
// TODO: 调用 Tauri API 打开文件选择器
|
||||
console.log('打开文件')
|
||||
// 模拟添加到播放列表
|
||||
playlist.value.push(`示例视频${playlist.value.length + 1}.mp4`)
|
||||
}
|
||||
|
||||
const playVideoFromPlaylist = async (index: number) => {
|
||||
currentVideo.value = playlist.value[index]
|
||||
// TODO: 调用 Tauri API 播放指定视频
|
||||
console.log('播放:', currentVideo.value)
|
||||
}
|
||||
|
||||
const removeFromPlaylist = (index: number) => {
|
||||
playlist.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const clearPlaylist = () => {
|
||||
playlist.value = []
|
||||
currentVideo.value = null
|
||||
}
|
||||
|
||||
// 监听进度变化
|
||||
watch(progress, (newProgress) => {
|
||||
// TODO: 调用 Tauri API 设置播放进度
|
||||
console.log('设置进度:', newProgress)
|
||||
})
|
||||
|
||||
// 监听音量变化
|
||||
watch(volume, (newVolume) => {
|
||||
// TODO: 调用 Tauri API 设置音量
|
||||
console.log('设置音量:', newVolume)
|
||||
})
|
||||
</script>
|
||||
|
@@ -1,70 +0,0 @@
|
||||
<template>
|
||||
<LayoutTile
|
||||
title="Notifications"
|
||||
description="Send native notifications to the client using the notification plugin."
|
||||
>
|
||||
<UForm :state="inputState" :schema="schema" class="flex flex-col gap-y-4 items-end" @submit="createNotification">
|
||||
<UFormField label="Notification title" name="notificationTitle">
|
||||
<UInput v-model="inputState.notificationTitle" variant="subtle" size="lg" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="Notification body (optional)" name="notificationBody">
|
||||
<UInput v-model="inputState.notificationBody" variant="subtle" size="lg" />
|
||||
</UFormField>
|
||||
|
||||
<UButton type="submit" size="lg">
|
||||
Send notification
|
||||
</UButton>
|
||||
</UForm>
|
||||
</LayoutTile>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
name: "Notifications",
|
||||
icon: "lucide:message-square-more",
|
||||
category: "interface",
|
||||
description: "Send native notifications"
|
||||
});
|
||||
|
||||
const schema = z.object({
|
||||
notificationTitle: z.string({
|
||||
error: "Title is required"
|
||||
}).nonempty(),
|
||||
notificationBody: z.string().optional()
|
||||
});
|
||||
|
||||
type Schema = zInfer<typeof schema>;
|
||||
|
||||
const inputState = ref<Partial<Schema>>({
|
||||
notificationTitle: undefined,
|
||||
notificationBody: undefined
|
||||
});
|
||||
|
||||
const toast = useToast();
|
||||
const permissionGranted = ref(false);
|
||||
|
||||
const createNotification = async () => {
|
||||
permissionGranted.value = await useTauriNotificationIsPermissionGranted();
|
||||
|
||||
if (!permissionGranted.value) {
|
||||
const permission = await useTauriNotificationRequestPermission();
|
||||
permissionGranted.value = permission === "granted";
|
||||
}
|
||||
|
||||
if (permissionGranted.value) {
|
||||
useTauriNotificationSendNotification({
|
||||
title: inputState.value.notificationTitle!,
|
||||
body: inputState.value.notificationBody
|
||||
});
|
||||
|
||||
inputState.value.notificationTitle = inputState.value.notificationBody = undefined;
|
||||
} else {
|
||||
toast.add({
|
||||
title: "Error",
|
||||
description: "Missing notifications permission",
|
||||
color: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -1,35 +0,0 @@
|
||||
<template>
|
||||
<LayoutTile
|
||||
title="OS Information"
|
||||
description="Read information about the operating system using the OS Information plugin."
|
||||
>
|
||||
<UAccordion :items="items" type="multiple" />
|
||||
</LayoutTile>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
name: "OS Informations",
|
||||
icon: "lucide:info",
|
||||
category: "system",
|
||||
description: "Read operating system informations."
|
||||
});
|
||||
|
||||
const items = ref([
|
||||
{
|
||||
label: "System",
|
||||
icon: "lucide:monitor",
|
||||
content: `${useTauriOsPlatform()} ${useTauriOsVersion()}`
|
||||
},
|
||||
{
|
||||
label: "Arch",
|
||||
icon: "lucide:microchip",
|
||||
content: useTauriOsArch()
|
||||
},
|
||||
{
|
||||
label: "Locale",
|
||||
icon: "lucide:globe",
|
||||
content: await useTauriOsLocale() || "Not detectable"
|
||||
}
|
||||
]);
|
||||
</script>
|
268
app/pages/settings.vue
Normal file
268
app/pages/settings.vue
Normal file
@@ -0,0 +1,268 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- 连接设置 -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">连接设置</h2>
|
||||
</template>
|
||||
<div class="space-y-4">
|
||||
<UFormGroup label="视频播放器地址" description="设置要连接的视频播放器的IP地址和端口">
|
||||
<div class="flex space-x-2">
|
||||
<UInput v-model="settings.playerHost" placeholder="192.168.1.100" class="flex-1" />
|
||||
<span class="self-center text-gray-500">:</span>
|
||||
<UInput v-model="settings.playerPort" type="number" placeholder="8080" class="w-24" />
|
||||
</div>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="连接超时时间 (秒)" description="连接超时的时间设置">
|
||||
<UInput v-model.number="settings.connectionTimeout" type="number" min="1" max="60" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="自动重连" description="连接断开后是否自动尝试重连">
|
||||
<UToggle v-model="settings.autoReconnect" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="重连间隔 (秒)" description="自动重连的间隔时间">
|
||||
<UInput
|
||||
v-model.number="settings.reconnectInterval"
|
||||
type="number"
|
||||
min="1"
|
||||
max="300"
|
||||
:disabled="!settings.autoReconnect"
|
||||
/>
|
||||
</UFormGroup>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<!-- 播放设置 -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">播放设置</h2>
|
||||
</template>
|
||||
<div class="space-y-4">
|
||||
<UFormGroup label="默认音量" description="新视频播放时的默认音量">
|
||||
<div class="flex items-center space-x-4">
|
||||
<URange v-model="settings.defaultVolume" :min="0" :max="100" class="flex-1" />
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400 min-w-[3rem]">{{ settings.defaultVolume }}%</span>
|
||||
</div>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="默认循环播放" description="是否默认启用循环播放">
|
||||
<UToggle v-model="settings.defaultLoop" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="自动全屏" description="播放视频时是否自动全屏">
|
||||
<UToggle v-model="settings.autoFullscreen" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="播放完成后行为">
|
||||
<USelectMenu
|
||||
v-model="settings.playbackEndBehavior"
|
||||
:options="playbackEndOptions"
|
||||
option-attribute="label"
|
||||
value-attribute="value"
|
||||
/>
|
||||
</UFormGroup>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<!-- 界面设置 -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">界面设置</h2>
|
||||
</template>
|
||||
<div class="space-y-4">
|
||||
<UFormGroup label="主题模式">
|
||||
<USelectMenu
|
||||
v-model="settings.theme"
|
||||
:options="themeOptions"
|
||||
option-attribute="label"
|
||||
value-attribute="value"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="语言">
|
||||
<USelectMenu
|
||||
v-model="settings.language"
|
||||
:options="languageOptions"
|
||||
option-attribute="label"
|
||||
value-attribute="value"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="显示通知" description="是否显示操作通知">
|
||||
<UToggle v-model="settings.showNotifications" />
|
||||
</UFormGroup>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<!-- 高级设置 -->
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">高级设置</h2>
|
||||
</template>
|
||||
<div class="space-y-4">
|
||||
<UFormGroup label="调试模式" description="启用详细的日志记录">
|
||||
<UToggle v-model="settings.debugMode" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="缓存大小 (MB)" description="播放列表和缩略图缓存大小">
|
||||
<UInput v-model.number="settings.cacheSize" type="number" min="10" max="1000" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="网络代理" description="设置网络代理(可选)">
|
||||
<UInput v-model="settings.proxy" placeholder="http://proxy.example.com:8080" />
|
||||
</UFormGroup>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="flex justify-between">
|
||||
<UButton @click="resetSettings" variant="outline" color="gray">
|
||||
<UIcon name="i-heroicons-arrow-path" class="w-4 h-4 mr-2" />
|
||||
重置设置
|
||||
</UButton>
|
||||
<div class="space-x-2">
|
||||
<UButton @click="exportSettings" variant="outline">
|
||||
<UIcon name="i-heroicons-arrow-up-tray" class="w-4 h-4 mr-2" />
|
||||
导出配置
|
||||
</UButton>
|
||||
<UButton @click="importSettings" variant="outline">
|
||||
<UIcon name="i-heroicons-arrow-down-tray" class="w-4 h-4 mr-2" />
|
||||
导入配置
|
||||
</UButton>
|
||||
<UButton @click="saveSettings" color="blue">
|
||||
<UIcon name="i-heroicons-check" class="w-4 h-4 mr-2" />
|
||||
保存设置
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
interface AppSettings {
|
||||
playerHost: string
|
||||
playerPort: number
|
||||
connectionTimeout: number
|
||||
autoReconnect: boolean
|
||||
reconnectInterval: number
|
||||
defaultVolume: number
|
||||
defaultLoop: boolean
|
||||
autoFullscreen: boolean
|
||||
playbackEndBehavior: string
|
||||
theme: string
|
||||
language: string
|
||||
showNotifications: boolean
|
||||
debugMode: boolean
|
||||
cacheSize: number
|
||||
proxy: string
|
||||
}
|
||||
|
||||
// 响应式设置数据
|
||||
const settings = ref<AppSettings>({
|
||||
playerHost: '192.168.1.100',
|
||||
playerPort: 8080,
|
||||
connectionTimeout: 10,
|
||||
autoReconnect: true,
|
||||
reconnectInterval: 5,
|
||||
defaultVolume: 50,
|
||||
defaultLoop: false,
|
||||
autoFullscreen: false,
|
||||
playbackEndBehavior: 'stop',
|
||||
theme: 'system',
|
||||
language: 'zh-CN',
|
||||
showNotifications: true,
|
||||
debugMode: false,
|
||||
cacheSize: 100,
|
||||
proxy: ''
|
||||
})
|
||||
|
||||
// 选项数据
|
||||
const playbackEndOptions = [
|
||||
{ label: '停止播放', value: 'stop' },
|
||||
{ label: '播放下一个', value: 'next' },
|
||||
{ label: '重复播放', value: 'repeat' }
|
||||
]
|
||||
|
||||
const themeOptions = [
|
||||
{ label: '跟随系统', value: 'system' },
|
||||
{ label: '浅色模式', value: 'light' },
|
||||
{ label: '深色模式', value: 'dark' }
|
||||
]
|
||||
|
||||
const languageOptions = [
|
||||
{ label: '简体中文', value: 'zh-CN' },
|
||||
{ label: 'English', value: 'en' },
|
||||
{ label: '日本語', value: 'ja' }
|
||||
]
|
||||
|
||||
// 方法
|
||||
const saveSettings = async () => {
|
||||
try {
|
||||
// TODO: 调用 Tauri API 保存设置
|
||||
console.log('保存设置:', settings.value)
|
||||
// 显示成功通知
|
||||
// useToast().add({ title: '设置已保存', color: 'green' })
|
||||
} catch (error) {
|
||||
console.error('保存设置失败:', error)
|
||||
// 显示错误通知
|
||||
// useToast().add({ title: '保存失败', color: 'red' })
|
||||
}
|
||||
}
|
||||
|
||||
const resetSettings = async () => {
|
||||
// 重置为默认设置
|
||||
settings.value = {
|
||||
playerHost: '192.168.1.100',
|
||||
playerPort: 8080,
|
||||
connectionTimeout: 10,
|
||||
autoReconnect: true,
|
||||
reconnectInterval: 5,
|
||||
defaultVolume: 50,
|
||||
defaultLoop: false,
|
||||
autoFullscreen: false,
|
||||
playbackEndBehavior: 'stop',
|
||||
theme: 'system',
|
||||
language: 'zh-CN',
|
||||
showNotifications: true,
|
||||
debugMode: false,
|
||||
cacheSize: 100,
|
||||
proxy: ''
|
||||
}
|
||||
}
|
||||
|
||||
const exportSettings = async () => {
|
||||
try {
|
||||
// TODO: 调用 Tauri API 导出设置到文件
|
||||
console.log('导出设置')
|
||||
} catch (error) {
|
||||
console.error('导出设置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const importSettings = async () => {
|
||||
try {
|
||||
// TODO: 调用 Tauri API 从文件导入设置
|
||||
console.log('导入设置')
|
||||
} catch (error) {
|
||||
console.error('导入设置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时读取设置
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// TODO: 调用 Tauri API 读取保存的设置
|
||||
console.log('加载设置')
|
||||
} catch (error) {
|
||||
console.error('加载设置失败:', error)
|
||||
}
|
||||
})
|
||||
|
||||
// 监听设置变化并自动保存关键设置
|
||||
watch(() => [settings.value.playerHost, settings.value.playerPort], async () => {
|
||||
// 自动保存连接相关设置
|
||||
await saveSettings()
|
||||
}, { debounce: 1000 })
|
||||
</script>
|
@@ -1,91 +0,0 @@
|
||||
<template>
|
||||
<LayoutTile
|
||||
title="Store"
|
||||
description="Persistent key-value store. Allows you to handle state to a file which can be saved and loaded on demand including between app restarts."
|
||||
>
|
||||
<div class="space-y-6 md:space-y-8">
|
||||
<UForm :state="inputState" :schema="schema" class="flex flex-col gap-y-4 items-end" @submit="setStoreValue">
|
||||
<UFormField label="Store value" name="value">
|
||||
<UInput v-model="inputState.value" variant="subtle" size="lg" />
|
||||
</UFormField>
|
||||
|
||||
<UButton type="submit" size="lg">
|
||||
Set value
|
||||
</UButton>
|
||||
</UForm>
|
||||
|
||||
<UForm :state="outputState" class="flex flex-col gap-y-4 items-end">
|
||||
<UFormField label="Store content" name="content">
|
||||
<UTextarea v-model="outputState.content" variant="subtle" size="lg" :rows="8" readonly />
|
||||
</UFormField>
|
||||
</UForm>
|
||||
</div>
|
||||
</LayoutTile>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
name: "Store",
|
||||
icon: "lucide:database",
|
||||
category: "storage",
|
||||
description: "Handle file creation in the file system"
|
||||
});
|
||||
|
||||
const schema = z.object({
|
||||
value: z.string({
|
||||
error: "Store key is required"
|
||||
}).nonempty()
|
||||
});
|
||||
|
||||
type Schema = zInfer<typeof schema>;
|
||||
|
||||
const inputState = ref<Partial<Schema>>({
|
||||
value: undefined
|
||||
});
|
||||
const outputState = ref({
|
||||
content: ""
|
||||
});
|
||||
|
||||
const toast = useToast();
|
||||
const autosave = ref(false);
|
||||
|
||||
const store = await useTauriStoreLoad("store.bin", {
|
||||
autoSave: autosave.value
|
||||
});
|
||||
|
||||
const getStoreValue = async () => {
|
||||
try {
|
||||
outputState.value.content = await store.get<string>("myData") || "";
|
||||
} catch (error) {
|
||||
toast.add({
|
||||
title: "Error",
|
||||
description: String(error),
|
||||
color: "error"
|
||||
});
|
||||
outputState.value.content = JSON.stringify(error, null, 4);
|
||||
}
|
||||
};
|
||||
|
||||
await getStoreValue();
|
||||
|
||||
const setStoreValue = async () => {
|
||||
try {
|
||||
await store.set("myData", inputState.value!.value);
|
||||
await getStoreValue();
|
||||
toast.add({
|
||||
title: "Success",
|
||||
description: "Store value retieved",
|
||||
color: "success"
|
||||
});
|
||||
} catch (error) {
|
||||
toast.add({
|
||||
title: "Error",
|
||||
description: String(error),
|
||||
color: "error"
|
||||
});
|
||||
outputState.value.content = JSON.stringify(error, null, 4);
|
||||
} finally {
|
||||
inputState.value.value = undefined;
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -1,51 +0,0 @@
|
||||
<template>
|
||||
<LayoutTile
|
||||
title="Webview window"
|
||||
description="Create new webview in a detached window. This will create a new window flagged 'secondary' that has the same permissions as the main one. If you need more windows, update the permissions under capabilities > main or create a new capabilities file for the new window only."
|
||||
>
|
||||
<div class="flex flex-col items-center gap-6">
|
||||
<UButton variant="subtle" @click="openWindow((new Date).valueOf().toString(), app.repo)">
|
||||
Create external Webview
|
||||
</UButton>
|
||||
<UButton variant="subtle" @click="openWindow('secondary', '/os')">
|
||||
Create internal Webview
|
||||
</UButton>
|
||||
</div>
|
||||
</LayoutTile>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
name: "Webview",
|
||||
icon: "lucide:app-window",
|
||||
category: "interface",
|
||||
description: "Create new webview in a detached window"
|
||||
});
|
||||
|
||||
const { app } = useAppConfig();
|
||||
const toast = useToast();
|
||||
|
||||
const openWindow = async (id: string, page: string) => {
|
||||
const webview = new useTauriWebviewWindowWebviewWindow(id, {
|
||||
title: "Nuxtor webview",
|
||||
url: page,
|
||||
width: 1280,
|
||||
height: 720
|
||||
});
|
||||
|
||||
webview.once("tauri://created", () => {
|
||||
toast.add({
|
||||
title: "Success",
|
||||
description: "Webview created",
|
||||
color: "success"
|
||||
});
|
||||
});
|
||||
webview.once("tauri://error", (error) => {
|
||||
toast.add({
|
||||
title: "Error",
|
||||
description: (error as any).payload,
|
||||
color: "error"
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
Reference in New Issue
Block a user