修改部分样式

This commit is contained in:
2025-08-17 13:25:15 +08:00
parent aa2a552820
commit f45aa01cf0
17 changed files with 14003 additions and 13556 deletions

216
API.md
View File

@@ -33,31 +33,31 @@ connect_to_player(config: ConnectionConfig) -> Result<ApiResponse<PlayerState>,
**参数:**
```json
{
"host": "192.168.1.100",
"port": 8080,
"timeout": 10,
"autoReconnect": true,
"reconnectInterval": 5
"host": "192.168.1.100",
"port": 8080,
"timeout": 10,
"autoReconnect": true,
"reconnectInterval": 5
}
```
**返回:**
```json
{
"success": true,
"data": {
"connectionStatus": "connected",
"playbackStatus": "stopped",
"currentVideo": null,
"position": 0.0,
"duration": 0.0,
"volume": 50.0,
"isLooping": false,
"isFullscreen": false,
"playlist": [],
"currentPlaylistIndex": null
},
"message": "成功连接到视频播放器"
"success": true,
"data": {
"connectionStatus": "connected",
"playbackStatus": "stopped",
"currentVideo": null,
"position": 0.0,
"duration": 0.0,
"volume": 50.0,
"isLooping": false,
"isFullscreen": false,
"playlist": [],
"currentPlaylistIndex": null
},
"message": "成功连接到视频播放器"
}
```
@@ -88,76 +88,76 @@ send_playback_command(command: PlaybackCommand) -> Result<ApiResponse<()>, Strin
1. **播放**
```json
{
"type": "play"
"type": "play"
}
```
2. **暂停**
```json
{
"type": "pause"
"type": "pause"
}
```
3. **停止**
```json
{
"type": "stop"
"type": "stop"
}
```
4. **跳转**
```json
{
"type": "seek",
"position": 60.5
"type": "seek",
"position": 60.5
}
```
5. **设置音量**
```json
{
"type": "setVolume",
"volume": 75.0
"type": "setVolume",
"volume": 75.0
}
```
6. **设置循环**
```json
{
"type": "setLoop",
"enabled": true
"type": "setLoop",
"enabled": true
}
```
7. **切换全屏**
```json
{
"type": "toggleFullscreen"
"type": "toggleFullscreen"
}
```
8. **加载视频**
```json
{
"type": "loadVideo",
"path": "/path/to/video.mp4"
"type": "loadVideo",
"path": "/path/to/video.mp4"
}
```
9. **设置播放列表**
```json
{
"type": "setPlaylist",
"videos": ["/path/to/video1.mp4", "/path/to/video2.mp4"]
"type": "setPlaylist",
"videos": ["/path/to/video1.mp4", "/path/to/video2.mp4"]
}
```
10. **播放列表中的指定视频**
```json
{
"type": "playFromPlaylist",
"index": 0
"type": "playFromPlaylist",
"index": 0
}
```
@@ -266,30 +266,30 @@ async fn send_status_update(status: PlayerState) -> Result<(), Box<dyn Error>>
```json
{
"type": "status",
"data": {
"playbackStatus": "playing|paused|stopped|loading",
"currentVideo": {
"path": "/path/to/current/video.mp4",
"title": "Video Title",
"duration": 3600.0,
"size": 1024000000,
"format": "mp4"
},
"position": 120.5,
"duration": 3600.0,
"volume": 75.0,
"isLooping": false,
"isFullscreen": true,
"playlist": [
{
"path": "/path/to/video1.mp4",
"title": "Video 1",
"duration": 1800.0
}
],
"currentPlaylistIndex": 0
}
"type": "status",
"data": {
"playbackStatus": "playing|paused|stopped|loading",
"currentVideo": {
"path": "/path/to/current/video.mp4",
"title": "Video Title",
"duration": 3600.0,
"size": 1024000000,
"format": "mp4"
},
"position": 120.5,
"duration": 3600.0,
"volume": 75.0,
"isLooping": false,
"isFullscreen": true,
"playlist": [
{
"path": "/path/to/video1.mp4",
"title": "Video 1",
"duration": 1800.0
}
],
"currentPlaylistIndex": 0
}
}
```
@@ -300,23 +300,23 @@ async fn send_status_update(status: PlayerState) -> Result<(), Box<dyn Error>>
#### 视频播放完成
```json
{
"type": "event",
"event": "playbackFinished",
"data": {
"videoPath": "/path/to/finished/video.mp4"
}
"type": "event",
"event": "playbackFinished",
"data": {
"videoPath": "/path/to/finished/video.mp4"
}
}
```
#### 播放错误
```json
{
"type": "event",
"event": "playbackError",
"data": {
"error": "Failed to load video file",
"videoPath": "/path/to/problematic/video.mp4"
}
"type": "event",
"event": "playbackError",
"data": {
"error": "Failed to load video file",
"videoPath": "/path/to/problematic/video.mp4"
}
}
```
@@ -337,55 +337,55 @@ async fn send_status_update(status: PlayerState) -> Result<(), Box<dyn Error>>
### PlayerState
```typescript
interface PlayerState {
connectionStatus: 'connected' | 'connecting' | 'disconnected' | { error: string }
playbackStatus: 'playing' | 'paused' | 'stopped' | 'loading'
currentVideo?: VideoInfo
position: number
duration: number
volume: number
isLooping: boolean
isFullscreen: boolean
playlist: VideoInfo[]
currentPlaylistIndex?: number
connectionStatus: "connected" | "connecting" | "disconnected" | { error: string }
playbackStatus: "playing" | "paused" | "stopped" | "loading"
currentVideo?: VideoInfo
position: number
duration: number
volume: number
isLooping: boolean
isFullscreen: boolean
playlist: VideoInfo[]
currentPlaylistIndex?: number
}
```
### VideoInfo
```typescript
interface VideoInfo {
path: string
title: string
duration?: number
size?: number
format?: string
path: string
title: string
duration?: number
size?: number
format?: string
}
```
### ConnectionConfig
```typescript
interface ConnectionConfig {
host: string
port: number
timeout: number
autoReconnect: boolean
reconnectInterval: number
host: string
port: number
timeout: number
autoReconnect: boolean
reconnectInterval: number
}
```
### AppSettings
```typescript
interface AppSettings {
connection: ConnectionConfig
defaultVolume: number
defaultLoop: boolean
autoFullscreen: boolean
playbackEndBehavior: 'stop' | 'next' | 'repeat'
theme: string
language: string
showNotifications: boolean
debugMode: boolean
cacheSize: number
proxy?: string
connection: ConnectionConfig
defaultVolume: number
defaultLoop: boolean
autoFullscreen: boolean
playbackEndBehavior: "stop" | "next" | "repeat"
theme: string
language: string
showNotifications: boolean
debugMode: boolean
cacheSize: number
proxy?: string
}
```
@@ -403,9 +403,9 @@ interface AppSettings {
### 错误响应格式
```json
{
"success": false,
"error": "CONNECTION_FAILED",
"message": "无法连接到视频播放器 192.168.1.100:8080"
"success": false,
"error": "CONNECTION_FAILED",
"message": "无法连接到视频播放器 192.168.1.100:8080"
}
```
@@ -443,18 +443,18 @@ use futures_util::{StreamExt, SinkExt};
async fn start_player_server() -> Result<(), Box<dyn Error>> {
let listener = TcpListener::bind("0.0.0.0:8080").await?;
println!("视频播放器服务器启动在 0.0.0.0:8080");
while let Ok((stream, _)) = listener.accept().await {
tokio::spawn(handle_connection(stream));
}
Ok(())
}
async fn handle_connection(stream: TcpStream) -> Result<(), Box<dyn Error>> {
let ws_stream = accept_async(stream).await?;
let (mut ws_sender, mut ws_receiver) = ws_stream.split();
while let Some(msg) = ws_receiver.next().await {
match msg? {
Message::Text(text) => {
@@ -469,7 +469,7 @@ async fn handle_connection(stream: TcpStream) -> Result<(), Box<dyn Error>> {
_ => {}
}
}
Ok(())
}
```

View File

@@ -20,7 +20,7 @@ error[E0599]: no method named `emit` found for struct `tauri::Window` in the cur
// 修复前
use tauri::{State, Window};
// 修复后
// 修复后
use tauri::{Emitter, State, Window};
```
@@ -131,7 +131,7 @@ cargo build # 完整构建
以下功能完全正常工作:
1. **WebSocket 连接管理**
2. **设置持久化存储**
2. **设置持久化存储**
3. **播放命令发送**
4. **状态管理和同步**
5. **视频信息获取**

View File

@@ -22,14 +22,14 @@
- 进度条和音量控制
- 循环播放、全屏等高级功能
- 播放列表管理
-**设置页面 (`/settings`)**:全面的应用配置
- 连接设置IP地址、端口、超时等
- 播放设置(默认音量、循环播放等)
- 界面设置(主题、语言等)
- 高级设置(调试模式、缓存等)
- 设置的导入导出功能
-**关于页面 (`/about`)**:应用信息展示
- 应用基本信息
- 技术栈展示
@@ -218,7 +218,7 @@ cargo build # 构建 Rust 后端
该视频控制器项目成功搭建了一个现代化的桌面应用基础架构,具备:
- 完整的用户界面和用户体验
- 健壮的后端API和服务架构
- 健壮的后端API和服务架构
- 详细的开发文档和规范
- 良好的可扩展性和维护性

View File

@@ -9,6 +9,10 @@ export default defineAppConfig({
colors: {
primary: "blue",
neutral: "zinc"
},
icons: {
light: "ph-sun",
dark: "ph-moon"
}
}
});

View File

@@ -1,5 +1,5 @@
@import "tailwindcss";
@import "@nuxt/ui";
@import "@nuxt/ui-pro";
@theme {
--font-heading: "Montserrat", sans-serif;

View File

@@ -1,11 +1,20 @@
<template>
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 flex">
<!-- 左侧边栏 -->
<aside class="w-40 bg-white dark:bg-gray-800 shadow-sm border-r border-gray-200 dark:border-gray-700 flex flex-col h-screen">
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
<aside
class="w-40 bg-white dark:bg-gray-800 shadow-sm border-r border-gray-200 dark:border-gray-700 flex flex-col h-screen fixed top-0 left-0 z-30"
>
<div class="flex items-center justify-between p-3 border-b border-gray-200 dark:border-gray-700">
<h1 class="text-xl font-semibold text-gray-900 dark:text-white">
视频控制器
</h1>
<div class="mx-auto mt-1">
<UColorModeButton size="xs">
<template #fallback>
<UButton loading variant="ghost" color="neutral" />
</template>
</UColorModeButton>
</div>
</div>
<nav class="flex-1 p-4">
@@ -13,8 +22,7 @@
<li>
<NuxtLink
to="/"
class="flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors"
:class="$route.path === '/'
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'"
>
@@ -25,8 +33,7 @@
<li>
<NuxtLink
to="/settings"
class="flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors"
:class="$route.path === '/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'"
>
@@ -36,13 +43,11 @@
</li>
</ul>
</nav>
<!-- 关于 -->
<div class="p-4 border-t border-gray-200 dark:border-gray-700">
<div class="p-4 ">
<NuxtLink
to="/about"
class="flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors"
:class="$route.path === '/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'"
>
@@ -53,7 +58,7 @@
</aside>
<!-- 主体区域 -->
<main class="flex-1 overflow-auto">
<main class="flex-1 overflow-auto ml-40">
<div class="p-6">
<slot />
</div>

View File

@@ -3,7 +3,9 @@
<!-- 应用信息 -->
<UCard>
<template #header>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">应用信息</h2>
<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">
@@ -11,13 +13,19 @@
<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>
<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-gray-700 dark:text-gray-300">
{{ appInfo.description }}
</p>
<p class="text-sm text-gray-600 dark:text-gray-400">
构建时间: {{ appInfo.buildDate }}
</p>
@@ -31,57 +39,63 @@
<!-- 技术栈 -->
<UCard>
<template #header>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">技术栈</h2>
<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>
<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 class="w-3 h-3 bg-green-500 rounded" />
</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 class="w-3 h-3 bg-blue-500 rounded" />
</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 class="w-3 h-3 bg-cyan-500 rounded" />
</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 class="w-3 h-3 bg-emerald-500 rounded" />
</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>
<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 class="w-3 h-3 bg-orange-500 rounded" />
</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 class="w-3 h-3 bg-red-500 rounded" />
</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 class="w-3 h-3 bg-purple-500 rounded" />
</div>
<span class="text-sm">WebSocket - 实时通信协议</span>
</div>
@@ -93,7 +107,9 @@
<!-- 功能特性 -->
<UCard>
<template #header>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">功能特性</h2>
<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">
@@ -138,11 +154,15 @@
<!-- 系统要求 -->
<UCard>
<template #header>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">系统要求</h2>
<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>
<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" />
@@ -158,9 +178,11 @@
</div>
</div>
</div>
<div>
<h4 class="font-medium text-gray-900 dark:text-white mb-2">网络要求</h4>
<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>
@@ -173,7 +195,9 @@
<!-- 开源许可 -->
<UCard>
<template #header>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">开源许可</h2>
<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">
@@ -195,7 +219,9 @@
<!-- 反馈和支持 -->
<UCard>
<template #header>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">反馈和支持</h2>
<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">
@@ -221,32 +247,32 @@
</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)
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>

View File

@@ -3,20 +3,22 @@
<!-- 连接状态 -->
<UCard>
<template #header>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">连接状态</h2>
<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>
<div :class="connectionStatus === 'connected' ? 'bg-green-500' : connectionStatus === 'connecting' ? 'bg-yellow-500' : 'bg-red-500'" class="w-3 h-3 rounded-full" />
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">
{{ connectionStatusText }}
</span>
</div>
<UButton
@click="toggleConnection"
<UButton
:variant="connectionStatus === 'connected' ? 'soft' : 'solid'"
:color="connectionStatus === 'connected' ? 'red' : 'blue'"
size="sm"
@click="toggleConnection"
>
{{ connectionStatus === 'connected' ? '断开连接' : '连接' }}
</UButton>
@@ -29,16 +31,22 @@
<!-- 视频预览区域 -->
<UCard>
<template #header>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">视频预览</h2>
<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>
<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>
<p class="text-gray-400 text-sm">
暂无视频
</p>
</div>
</div>
</UCard>
@@ -46,20 +54,22 @@
<!-- 播放控制 -->
<UCard>
<template #header>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">播放控制</h2>
<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">
<UButton :disabled="!isConnected" color="green" size="lg" @click="playVideo">
<UIcon name="i-heroicons-play" class="w-5 h-5 mr-2" />
播放
</UButton>
<UButton @click="pauseVideo" :disabled="!isConnected" color="orange" size="lg">
<UButton :disabled="!isConnected" color="orange" size="lg" @click="pauseVideo">
<UIcon name="i-heroicons-pause" class="w-5 h-5 mr-2" />
暂停
</UButton>
<UButton @click="stopVideo" :disabled="!isConnected" color="red" size="lg">
<UButton :disabled="!isConnected" color="red" size="lg" @click="stopVideo">
<UIcon name="i-heroicons-stop" class="w-5 h-5 mr-2" />
停止
</UButton>
@@ -87,15 +97,15 @@
<!-- 额外功能 -->
<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">
<UButton :disabled="!isConnected" :variant="isLooping ? 'solid' : 'outline'" size="sm" @click="toggleLoop">
<UIcon name="i-heroicons-arrow-path" class="w-4 h-4 mr-1" />
循环播放
</UButton>
<UButton @click="toggleFullscreen" :disabled="!isConnected" variant="outline" size="sm">
<UButton :disabled="!isConnected" variant="outline" size="sm" @click="toggleFullscreen">
<UIcon name="i-heroicons-arrows-pointing-out" class="w-4 h-4 mr-1" />
全屏
</UButton>
<UButton @click="openVideoFile" variant="outline" size="sm">
<UButton variant="outline" size="sm" @click="openVideoFile">
<UIcon name="i-heroicons-folder-open" class="w-4 h-4 mr-1" />
打开文件
</UButton>
@@ -107,8 +117,10 @@
<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">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
播放列表
</h2>
<UButton variant="ghost" size="sm" color="red" @click="clearPlaylist">
<UIcon name="i-heroicons-trash" class="w-4 h-4 mr-1" />
清空
</UButton>
@@ -120,8 +132,8 @@
<p>播放列表为空</p>
</div>
<div v-else>
<div
v-for="(item, index) in playlist"
<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"
>
@@ -130,10 +142,10 @@
<span class="text-sm font-medium">{{ item }}</span>
</div>
<div class="flex space-x-1">
<UButton @click="playVideoFromPlaylist(index)" size="xs" variant="ghost">
<UButton size="xs" variant="ghost" @click="playVideoFromPlaylist(index)">
<UIcon name="i-heroicons-play" class="w-4 h-4" />
</UButton>
<UButton @click="removeFromPlaylist(index)" size="xs" variant="ghost" color="red">
<UButton size="xs" variant="ghost" color="red" @click="removeFromPlaylist(index)">
<UIcon name="i-heroicons-x-mark" class="w-4 h-4" />
</UButton>
</div>
@@ -145,108 +157,108 @@
</template>
<script lang="ts" setup>
interface VideoControllerState {
connectionStatus: 'connected' | 'connecting' | 'disconnected'
currentVideo: string | null
progress: number
volume: number
isLooping: boolean
playlist: string[]
playerAddress: string
}
// 响应式状态
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 '未连接'
interface VideoControllerState {
connectionStatus: "connected" | "connecting" | "disconnected"
currentVideo: string | null
progress: number
volume: number
isLooping: boolean
playlist: string[]
playerAddress: string
}
})
// 方法
const toggleConnection = async () => {
if (connectionStatus.value === 'connected') {
// 断开连接
connectionStatus.value = 'disconnected'
} else {
// 尝试连接
connectionStatus.value = 'connecting'
// TODO: 在这里调用 Tauri API 进行实际连接
setTimeout(() => {
connectionStatus.value = 'connected' // 模拟连接成功
}, 1000)
}
}
// 响应式状态
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 playVideo = async () => {
// TODO: 调用 Tauri API
console.log('播放视频')
}
// 计算属性
const isConnected = computed(() => connectionStatus.value === "connected");
const pauseVideo = async () => {
// TODO: 调用 Tauri API
console.log('暂停视频')
}
const connectionStatusText = computed(() => {
switch (connectionStatus.value) {
case "connected": return "已连接";
case "connecting": return "连接中...";
default: return "未连接";
}
});
const stopVideo = async () => {
// TODO: 调用 Tauri API
console.log('停止视频')
}
// 方法
const toggleConnection = async () => {
if (connectionStatus.value === "connected") {
// 断开连接
connectionStatus.value = "disconnected";
} else {
// 尝试连接
connectionStatus.value = "connecting";
// TODO: 在这里调用 Tauri API 进行实际连接
setTimeout(() => {
connectionStatus.value = "connected"; // 模拟连接成功
}, 1000);
}
};
const toggleLoop = async () => {
isLooping.value = !isLooping.value
// TODO: 调用 Tauri API
console.log('循环播放:', isLooping.value)
}
const playVideo = async () => {
// TODO: 调用 Tauri API
console.log("播放视频");
};
const toggleFullscreen = async () => {
// TODO: 调用 Tauri API
console.log('切换全屏')
}
const pauseVideo = async () => {
// TODO: 调用 Tauri API
console.log("暂停视频");
};
const openVideoFile = async () => {
// TODO: 调用 Tauri API 打开文件选择器
console.log('打开文件')
// 模拟添加到播放列表
playlist.value.push(`示例视频${playlist.value.length + 1}.mp4`)
}
const stopVideo = async () => {
// TODO: 调用 Tauri API
console.log("停止视频");
};
const playVideoFromPlaylist = async (index: number) => {
currentVideo.value = playlist.value[index]
// TODO: 调用 Tauri API 播放指定视频
console.log('播放:', currentVideo.value)
}
const toggleLoop = async () => {
isLooping.value = !isLooping.value;
// TODO: 调用 Tauri API
console.log("循环播放:", isLooping.value);
};
const removeFromPlaylist = (index: number) => {
playlist.value.splice(index, 1)
}
const toggleFullscreen = async () => {
// TODO: 调用 Tauri API
console.log("切换全屏");
};
const clearPlaylist = () => {
playlist.value = []
currentVideo.value = null
}
const openVideoFile = async () => {
// TODO: 调用 Tauri API 打开文件选择器
console.log("打开文件");
// 模拟添加到播放列表
playlist.value.push(`示例视频${playlist.value.length + 1}.mp4`);
};
// 监听进度变化
watch(progress, (newProgress) => {
// TODO: 调用 Tauri API 设置播放进度
console.log('设置进度:', newProgress)
})
const playVideoFromPlaylist = async (index: number) => {
currentVideo.value = playlist.value[index];
// TODO: 调用 Tauri API 播放指定视频
console.log("播放:", currentVideo.value);
};
// 监听音量变化
watch(volume, (newVolume) => {
// TODO: 调用 Tauri API 设置音量
console.log('设置音量:', newVolume)
})
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>

View File

@@ -3,7 +3,9 @@
<!-- 连接设置 -->
<UCard>
<template #header>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">连接设置</h2>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
连接设置
</h2>
</template>
<div class="space-y-4">
<UFormGroup label="视频播放器地址" description="设置要连接的视频播放器的IP地址和端口">
@@ -23,12 +25,12 @@
</UFormGroup>
<UFormGroup label="重连间隔 (秒)" description="自动重连的间隔时间">
<UInput
v-model.number="settings.reconnectInterval"
type="number"
min="1"
<UInput
v-model.number="settings.reconnectInterval"
type="number"
min="1"
max="300"
:disabled="!settings.autoReconnect"
:disabled="!settings.autoReconnect"
/>
</UFormGroup>
</div>
@@ -37,7 +39,9 @@
<!-- 播放设置 -->
<UCard>
<template #header>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">播放设置</h2>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
播放设置
</h2>
</template>
<div class="space-y-4">
<UFormGroup label="默认音量" description="新视频播放时的默认音量">
@@ -56,7 +60,7 @@
</UFormGroup>
<UFormGroup label="播放完成后行为">
<USelectMenu
<USelectMenu
v-model="settings.playbackEndBehavior"
:options="playbackEndOptions"
option-attribute="label"
@@ -69,11 +73,13 @@
<!-- 界面设置 -->
<UCard>
<template #header>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">界面设置</h2>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
界面设置
</h2>
</template>
<div class="space-y-4">
<UFormGroup label="主题模式">
<USelectMenu
<USelectMenu
v-model="settings.theme"
:options="themeOptions"
option-attribute="label"
@@ -82,7 +88,7 @@
</UFormGroup>
<UFormGroup label="语言">
<USelectMenu
<USelectMenu
v-model="settings.language"
:options="languageOptions"
option-attribute="label"
@@ -99,7 +105,9 @@
<!-- 高级设置 -->
<UCard>
<template #header>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">高级设置</h2>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
高级设置
</h2>
</template>
<div class="space-y-4">
<UFormGroup label="调试模式" description="启用详细的日志记录">
@@ -118,20 +126,20 @@
<!-- 操作按钮 -->
<div class="flex justify-between">
<UButton @click="resetSettings" variant="outline" color="gray">
<UButton variant="outline" color="gray" @click="resetSettings">
<UIcon name="i-heroicons-arrow-path" class="w-4 h-4 mr-2" />
重置设置
</UButton>
<div class="space-x-2">
<UButton @click="exportSettings" variant="outline">
<UButton variant="outline" @click="exportSettings">
<UIcon name="i-heroicons-arrow-up-tray" class="w-4 h-4 mr-2" />
导出配置
</UButton>
<UButton @click="importSettings" variant="outline">
<UButton variant="outline" @click="importSettings">
<UIcon name="i-heroicons-arrow-down-tray" class="w-4 h-4 mr-2" />
导入配置
</UButton>
<UButton @click="saveSettings" color="blue">
<UButton color="blue" @click="saveSettings">
<UIcon name="i-heroicons-check" class="w-4 h-4 mr-2" />
保存设置
</UButton>
@@ -141,80 +149,27 @@
</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' })
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 resetSettings = async () => {
// 重置为默认设置
settings.value = {
playerHost: '192.168.1.100',
// 响应式设置数据
const settings = ref<AppSettings>({
playerHost: "192.168.1.100",
playerPort: 8080,
connectionTimeout: 10,
autoReconnect: true,
@@ -222,47 +177,100 @@ const resetSettings = async () => {
defaultVolume: 50,
defaultLoop: false,
autoFullscreen: false,
playbackEndBehavior: 'stop',
theme: 'system',
language: 'zh-CN',
playbackEndBehavior: "stop",
theme: "system",
language: "zh-CN",
showNotifications: true,
debugMode: false,
cacheSize: 100,
proxy: ''
}
}
proxy: ""
});
const exportSettings = async () => {
try {
// TODO: 调用 Tauri API 导出设置到文件
console.log('导出设置')
} catch (error) {
console.error('导出设置失败:', error)
}
}
// 选项数据
const playbackEndOptions = [
{ label: "停止播放", value: "stop" },
{ label: "播放下一个", value: "next" },
{ label: "重复播放", value: "repeat" }
];
const importSettings = async () => {
try {
// TODO: 调用 Tauri API 从文件导入设置
console.log('导入设置')
} catch (error) {
console.error('导入设置失败:', error)
}
}
const themeOptions = [
{ label: "跟随系统", value: "system" },
{ label: "浅色模式", value: "light" },
{ label: "深色模式", value: "dark" }
];
// 页面加载时读取设置
onMounted(async () => {
try {
// TODO: 调用 Tauri API 读取保存的设置
console.log('加载设置')
} catch (error) {
console.error('加载设置失败:', error)
}
})
const languageOptions = [
{ label: "简体中文", value: "zh-CN" },
{ label: "English", value: "en" },
{ label: "日本語", value: "ja" }
];
// 监听设置变化并自动保存关键设置
watch(() => [settings.value.playerHost, settings.value.playerPort], async () => {
// 自动保存连接相关设置
await saveSettings()
}, { debounce: 1000 })
// 方法
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>

View File

@@ -1,10 +1,10 @@
export default defineNuxtConfig({
modules: [
"@vueuse/nuxt",
"@nuxt/ui",
"nuxt-svgo",
"reka-ui/nuxt",
"@nuxt/eslint"
"@nuxt/eslint",
"@nuxt/ui-pro"
],
app: {
head: {

View File

@@ -24,7 +24,7 @@
},
"dependencies": {
"@iconify/vue": "^5.0.0",
"@nuxt/ui": "^3.2.0",
"@nuxt/ui-pro": "^3.3.2",
"@tauri-apps/api": "^2.6.0",
"@tauri-apps/plugin-fs": "^2.4.0",
"@tauri-apps/plugin-notification": "^2.3.0",

652
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,7 +26,7 @@ tauri-plugin-os = "2.3.0"
tauri-plugin-fs = "2.4.0"
tauri-plugin-store = "2.3.0"
serde_json = "1"
tokio = { version = "1.0", features = ["full"] }
tokio = { version = "1.0", features = [ "full" ] }
tokio-tungstenite = "0.23"
futures-util = "0.3"
log = "0.4"

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"main":{"identifier":"main","description":"Capabilities for the app window","local":true,"windows":["main","secondary"],"permissions":["core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","shell:allow-open",{"identifier":"shell:allow-execute","allow":[{"args":["-c",{"validator":"\\S+"}],"cmd":"sh","name":"exec-sh","sidecar":false}]},"notification:default","os:allow-platform","os:allow-arch","os:allow-family","os:allow-version","os:allow-locale","fs:allow-document-read","fs:allow-document-write","store:default","core:webview:allow-create-webview","core:webview:allow-create-webview-window"]}}
{ "main": { "identifier": "main", "description": "Capabilities for the app window", "local": true, "windows": ["main", "secondary"], "permissions": ["core:path:default", "core:event:default", "core:window:default", "core:app:default", "core:resources:default", "core:menu:default", "core:tray:default", "shell:allow-open", { "identifier": "shell:allow-execute", "allow": [{ "args": ["-c", { "validator": "\\S+" }], "cmd": "sh", "name": "exec-sh", "sidecar": false }] }, "notification:default", "os:allow-platform", "os:allow-arch", "os:allow-family", "os:allow-version", "os:allow-locale", "fs:allow-document-read", "fs:allow-document-write", "store:default", "core:webview:allow-create-webview", "core:webview:allow-create-webview-window"] } }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff