This commit is contained in:
2025-09-24 16:43:10 +08:00
parent 12f46e6f8e
commit 9dbc054483
130 changed files with 16474 additions and 4660 deletions

49
public/apps/README.md Normal file
View File

@@ -0,0 +1,49 @@
# 外部应用目录
此目录用于存放外部应用非内置Vue应用
## 目录结构
外部应用应该按以下结构组织:
```
public/apps/
├── app-name/
│ ├── index.html # 应用主页面
│ ├── manifest.json # 应用清单文件
│ └── ... # 其他应用文件
└── another-app/
├── index.html
├── manifest.json
└── ...
```
## 应用清单格式
每个外部应用都应该包含一个 `manifest.json` 文件:
```json
{
"id": "app-id",
"name": "应用名称",
"version": "1.0.0",
"description": "应用描述",
"author": "作者",
"icon": "图标URL或表情符号",
"permissions": ["storage", "notification"],
"window": {
"width": 800,
"height": 600,
"resizable": true
},
"category": "应用分类",
"keywords": ["关键词1", "关键词2"]
}
```
## 安全说明
外部应用将在iframe沙箱环境中运行具有以下限制
- 无法直接访问父页面
- 通过postMessage与系统通信
- 受到严格的权限控制

View File

@@ -0,0 +1,170 @@
# 音乐播放器 - 外置应用案例
这是一个完整的外置应用案例展示了如何在Vue桌面系统中开发外置应用。
## 功能特性
### 🎵 音乐播放功能
- **音频格式支持**: 支持所有浏览器兼容的音频格式MP3、WAV、OGG等
- **播放控制**: 播放、暂停、上一曲、下一曲
- **进度控制**: 可拖拽的进度条,支持跳转到任意位置
- **音量控制**: 音量滑块调节,实时显示音量百分比
### 🎲 播放模式
- **随机播放**: 支持随机播放模式切换
- **重复播放**: 三种重复模式(关闭、单曲循环、列表循环)
- **播放列表**: 完整的播放列表管理
### 🎨 用户界面
- **现代设计**: 采用渐变色彩和毛玻璃效果
- **响应式布局**: 支持窗口大小调整
- **直观操作**: 清晰的视觉反馈和状态提示
- **窗口控制**: 最小化、最大化、关闭按钮
### ⌨️ 交互体验
- **键盘快捷键**:
- `Space`: 播放/暂停
- `←/→`: 上一曲/下一曲
- `↑/↓`: 音量增减
- **拖拽支持**: 支持文件拖拽添加(计划中)
- **状态持久化**: 播放列表自动保存
### 🔧 系统集成
- **系统SDK集成**: 与主系统的无缝集成
- **窗口控制**: 通过系统API控制窗口状态
- **系统通知**: 状态变化的系统通知
- **数据存储**: 使用系统存储API保存用户数据
## 文件结构
```
music-player/
├── manifest.json # 应用清单文件
├── index.html # 主HTML页面
├── style.css # 样式文件
├── app.js # 主要逻辑文件
└── README.md # 说明文档
```
## 技术实现
### 应用清单 (manifest.json)
定义了应用的基本信息、权限要求、窗口配置等:
- 应用ID、名称、版本信息
- 窗口大小和行为配置
- 所需权限(存储、文件读取、通知)
- 分类和关键词
### 用户界面 (index.html + style.css)
- **布局结构**: 采用Flexbox布局分为头部、主要区域、播放列表和状态栏
- **样式设计**: 使用CSS渐变、阴影、过渡动画等现代效果
- **响应式**: 针对不同屏幕尺寸优化显示
### 应用逻辑 (app.js)
核心类 `MusicPlayer` 实现了所有功能:
#### 初始化流程
1. DOM就绪检测
2. 系统SDK连接
3. 事件监听器设置
4. 界面状态初始化
#### 音频处理
- 使用HTML5 Audio API
- 事件驱动的状态管理
- 错误处理和恢复机制
#### 播放列表管理
- 文件选择和验证
- 内存中的播放队列
- 持久化存储支持
## 系统SDK集成
该应用展示了如何正确使用系统SDK
### 窗口控制
```javascript
// 窗口操作
this.systemSDK.window.minimize();
this.systemSDK.window.toggleMaximize();
this.systemSDK.window.close();
// 关闭事件监听
this.systemSDK.window.onBeforeClose(() => {
this.cleanup();
return true;
});
```
### 系统通知
```javascript
this.systemSDK.notification.show({
title: '音乐播放器',
message: '应用已启动,准备播放音乐!',
type: 'info'
});
```
### 数据存储
```javascript
// 保存数据
this.systemSDK.storage.setItem('music-player-playlist', data);
// 读取数据
const data = this.systemSDK.storage.getItem('music-player-playlist');
```
## 安全考虑
- **文件访问**: 通过文件选择器安全地访问用户文件
- **内存管理**: 及时释放对象URL避免内存泄漏
- **错误处理**: 完整的错误捕获和用户友好的错误提示
- **权限控制**: 仅请求必要的系统权限
## 性能优化
- **懒加载**: 音频文件仅在需要时加载
- **事件防抖**: 避免频繁的状态更新
- **内存回收**: 应用关闭时清理所有资源
- **DOM优化**: 高效的DOM操作和事件委托
## 扩展可能
这个案例可以进一步扩展:
1. **音频可视化**: 添加频谱显示
2. **歌词显示**: 支持LRC歌词文件
3. **均衡器**: 音频效果控制
4. **在线音乐**: 集成在线音乐服务
5. **播放历史**: 记录播放统计
6. **主题切换**: 多种UI主题
## 开发指南
### 本地测试
1. 将整个文件夹放置到 `public/apps/` 目录下
2. 启动Vue桌面系统
3. 通过应用管理器安装或直接打开应用
### 调试技巧
- 使用浏览器开发者工具调试
- 检查控制台日志了解应用状态
- 利用系统SDK的调试功能
### 部署注意事项
- 确保所有文件路径正确
- 验证清单文件格式
- 测试在不同窗口大小下的表现
## 总结
这个音乐播放器应用是一个完整的外置应用开发示例,展示了:
- 如何构建功能完整的外置应用
- 系统SDK的正确使用方法
- 现代Web技术的应用
- 良好的用户体验设计
- 安全和性能的最佳实践
通过学习这个案例,开发者可以了解外置应用的完整开发流程,并以此为基础开发自己的应用。

View File

@@ -0,0 +1,751 @@
/**
* 音乐播放器 - 外置应用案例
* 展示了如何创建一个功能完整的外置应用
*/
class MusicPlayer {
constructor() {
// 应用状态
this.isPlaying = false;
this.currentTrackIndex = 0;
this.playlist = [];
this.isShuffleMode = false;
this.repeatMode = 'none'; // none, one, all
this.volume = 0.7;
// DOM 元素
this.audioPlayer = null;
this.playPauseBtn = null;
this.progressBar = null;
this.volumeBar = null;
this.playlist_element = null;
// 系统SDK
this.systemSDK = null;
this.init();
}
/**
* 初始化应用
*/
async init() {
console.log('[音乐播放器] 初始化开始');
// 等待DOM加载完成
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => this.setupApp());
} else {
this.setupApp();
}
// 初始化系统SDK
await this.initSystemSDK();
}
/**
* 初始化系统SDK
*/
async initSystemSDK() {
try {
console.log('[音乐播放器] 开始初始化系统SDK');
// 等待系统SDK可用
let attempts = 0;
const maxAttempts = 100; // 增加尝试次数
while (!window.SystemSDK && attempts < maxAttempts) {
console.log(`[音乐播放器] 等待SystemSDK可用... (尝试 ${attempts + 1}/${maxAttempts})`);
await new Promise(resolve => setTimeout(resolve, 100));
attempts++;
}
if (window.SystemSDK) {
console.log('[音乐播放器] SystemSDK对象已找到开始初始化');
// 初始化SDK
const initResult = await window.SystemSDK.init({
appId: 'music-player',
appName: '音乐播放器',
version: '1.0.0',
permissions: ['storage.read', 'storage.write']
});
if (initResult.success) {
this.systemSDK = window.SystemSDK;
console.log('[音乐播放器] 系统SDK初始化成功');
// 显示系统通知
if (this.systemSDK.ui) {
try {
await this.systemSDK.ui.showNotification({
title: '音乐播放器',
message: '应用已启动,准备播放音乐!',
type: 'info'
});
} catch (error) {
console.warn('[音乐播放器] 显示通知失败:', error);
}
}
// 从本地存储恢复播放列表
await this.loadPlaylistFromStorage();
} else {
console.error('[音乐播放器] 系统SDK初始化失败:', initResult.error);
}
} else {
console.error('[音乐播放器] 系统SDK不可用已达到最大尝试次数');
}
} catch (error) {
console.error('[音乐播放器] 系统SDK初始化失败:', error);
}
}
/**
* 设置应用界面和事件
*/
setupApp() {
console.log('[音乐播放器] 设置界面');
// 获取DOM元素
this.audioPlayer = document.getElementById('audioPlayer');
this.playPauseBtn = document.getElementById('playPauseBtn');
this.progressBar = document.getElementById('progressBar');
this.volumeBar = document.getElementById('volumeBar');
this.playlist_element = document.getElementById('playlist');
// 设置音频事件
this.setupAudioEvents();
// 设置控制按钮事件
this.setupControlEvents();
// 设置窗口控制事件
this.setupWindowControls();
// 设置键盘快捷键
this.setupKeyboardShortcuts();
// 初始化音量
this.setVolume(this.volume * 100);
console.log('[音乐播放器] 应用设置完成');
}
/**
* 设置音频播放器事件
*/
setupAudioEvents() {
if (!this.audioPlayer) return;
// 播放开始
this.audioPlayer.addEventListener('play', () => {
this.isPlaying = true;
this.updatePlayButton();
this.updateStatus('正在播放');
});
// 播放暂停
this.audioPlayer.addEventListener('pause', () => {
this.isPlaying = false;
this.updatePlayButton();
this.updateStatus('已暂停');
});
// 播放结束
this.audioPlayer.addEventListener('ended', () => {
this.handleTrackEnded();
});
// 时间更新
this.audioPlayer.addEventListener('timeupdate', () => {
this.updateProgress();
});
// 加载完成
this.audioPlayer.addEventListener('loadedmetadata', () => {
this.updateTotalTime();
});
// 加载错误
this.audioPlayer.addEventListener('error', (e) => {
console.error('[音乐播放器] 播放错误:', e);
this.updateStatus('播放出错');
this.nextTrack();
});
}
/**
* 设置控制按钮事件
*/
setupControlEvents() {
// 播放/暂停
document.getElementById('playPauseBtn')?.addEventListener('click', () => {
this.togglePlayPause();
});
// 上一曲
document.getElementById('prevBtn')?.addEventListener('click', () => {
this.prevTrack();
});
// 下一曲
document.getElementById('nextBtn')?.addEventListener('click', () => {
this.nextTrack();
});
// 随机播放
document.getElementById('shuffleBtn')?.addEventListener('click', () => {
this.toggleShuffle();
});
// 重复播放
document.getElementById('repeatBtn')?.addEventListener('click', () => {
this.toggleRepeat();
});
// 进度条
this.progressBar?.addEventListener('input', () => {
this.seekTo(this.progressBar.value);
});
// 音量控制
this.volumeBar?.addEventListener('input', () => {
this.setVolume(this.volumeBar.value);
});
// 文件选择
document.getElementById('addFilesBtn')?.addEventListener('click', () => {
document.getElementById('fileInput').click();
});
document.getElementById('fileInput')?.addEventListener('change', (e) => {
this.handleFileSelection(e.target.files);
});
// 清空播放列表
document.getElementById('clearPlaylistBtn')?.addEventListener('click', () => {
this.clearPlaylist();
});
}
/**
* 设置窗口控制事件
*/
setupWindowControls() {
// 最小化
document.getElementById('minimizeBtn')?.addEventListener('click', () => {
if (this.systemSDK) {
this.systemSDK.window.minimize();
}
});
// 最大化/还原
document.getElementById('maximizeBtn')?.addEventListener('click', () => {
if (this.systemSDK) {
this.systemSDK.window.toggleMaximize();
}
});
// 关闭
document.getElementById('closeBtn')?.addEventListener('click', () => {
if (this.systemSDK) {
this.systemSDK.window.close();
} else {
window.close();
}
});
}
/**
* 设置键盘快捷键
*/
setupKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT') return;
switch (e.code) {
case 'Space':
e.preventDefault();
this.togglePlayPause();
break;
case 'ArrowLeft':
e.preventDefault();
this.prevTrack();
break;
case 'ArrowRight':
e.preventDefault();
this.nextTrack();
break;
case 'ArrowUp':
e.preventDefault();
this.setVolume(Math.min(100, this.volume * 100 + 5));
break;
case 'ArrowDown':
e.preventDefault();
this.setVolume(Math.max(0, this.volume * 100 - 5));
break;
}
});
}
/**
* 处理文件选择
*/
handleFileSelection(files) {
const audioFiles = Array.from(files).filter(file =>
file.type.startsWith('audio/')
);
if (audioFiles.length === 0) {
this.updateStatus('未选择音频文件');
return;
}
audioFiles.forEach(file => {
const track = {
id: Date.now() + Math.random(),
name: file.name.replace(/\.[^/.]+$/, ""),
file: file,
url: URL.createObjectURL(file),
duration: 0
};
this.playlist.push(track);
});
this.updatePlaylist();
this.savePlaylistToStorage();
this.updateStatus(`添加了 ${audioFiles.length} 首歌曲`);
// 如果是第一次添加歌曲,自动播放
if (this.playlist.length === audioFiles.length) {
this.loadTrack(0);
}
}
/**
* 播放/暂停切换
*/
togglePlayPause() {
if (!this.audioPlayer || this.playlist.length === 0) {
this.updateStatus('播放列表为空');
return;
}
if (this.isPlaying) {
this.audioPlayer.pause();
} else {
this.audioPlayer.play().catch(error => {
console.error('[音乐播放器] 播放失败:', error);
this.updateStatus('播放失败');
});
}
}
/**
* 上一曲
*/
prevTrack() {
if (this.playlist.length === 0) return;
let newIndex;
if (this.isShuffleMode) {
newIndex = Math.floor(Math.random() * this.playlist.length);
} else {
newIndex = this.currentTrackIndex - 1;
if (newIndex < 0) {
newIndex = this.playlist.length - 1;
}
}
this.loadTrack(newIndex);
}
/**
* 下一曲
*/
nextTrack() {
if (this.playlist.length === 0) return;
let newIndex;
if (this.isShuffleMode) {
newIndex = Math.floor(Math.random() * this.playlist.length);
} else {
newIndex = this.currentTrackIndex + 1;
if (newIndex >= this.playlist.length) {
newIndex = 0;
}
}
this.loadTrack(newIndex);
}
/**
* 加载指定曲目
*/
loadTrack(index) {
if (index < 0 || index >= this.playlist.length) return;
this.currentTrackIndex = index;
const track = this.playlist[index];
this.audioPlayer.src = track.url;
this.updateCurrentTrackInfo(track);
this.updatePlaylistHighlight();
if (this.isPlaying) {
this.audioPlayer.play().catch(error => {
console.error('[音乐播放器] 播放失败:', error);
});
}
}
/**
* 处理曲目播放结束
*/
handleTrackEnded() {
switch (this.repeatMode) {
case 'one':
this.audioPlayer.currentTime = 0;
this.audioPlayer.play();
break;
case 'all':
this.nextTrack();
break;
default:
if (this.currentTrackIndex < this.playlist.length - 1) {
this.nextTrack();
} else {
this.isPlaying = false;
this.updatePlayButton();
this.updateStatus('播放完成');
}
break;
}
}
/**
* 切换随机播放
*/
toggleShuffle() {
this.isShuffleMode = !this.isShuffleMode;
const btn = document.getElementById('shuffleBtn');
if (btn) {
btn.classList.toggle('active', this.isShuffleMode);
}
this.updateStatus(this.isShuffleMode ? '随机播放已开启' : '随机播放已关闭');
}
/**
* 切换重复播放模式
*/
toggleRepeat() {
const modes = ['none', 'one', 'all'];
const currentIndex = modes.indexOf(this.repeatMode);
this.repeatMode = modes[(currentIndex + 1) % modes.length];
const btn = document.getElementById('repeatBtn');
if (btn) {
btn.classList.toggle('active', this.repeatMode !== 'none');
switch (this.repeatMode) {
case 'one':
btn.textContent = '🔂';
break;
case 'all':
btn.textContent = '🔁';
break;
default:
btn.textContent = '🔁';
break;
}
}
const modeNames = { none: '关闭', one: '单曲循环', all: '列表循环' };
this.updateStatus(`重复播放: ${modeNames[this.repeatMode]}`);
}
/**
* 设置音量
*/
setVolume(value) {
this.volume = value / 100;
if (this.audioPlayer) {
this.audioPlayer.volume = this.volume;
}
const volumeValue = document.getElementById('volumeValue');
if (volumeValue) {
volumeValue.textContent = `${Math.round(value)}%`;
}
if (this.volumeBar) {
this.volumeBar.value = value;
}
}
/**
* 跳转到指定时间
*/
seekTo(percentage) {
if (this.audioPlayer && this.audioPlayer.duration) {
const time = (percentage / 100) * this.audioPlayer.duration;
this.audioPlayer.currentTime = time;
}
}
/**
* 更新播放按钮状态
*/
updatePlayButton() {
if (this.playPauseBtn) {
this.playPauseBtn.textContent = this.isPlaying ? '⏸️' : '▶️';
this.playPauseBtn.classList.toggle('playing', this.isPlaying);
}
}
/**
* 更新进度条
*/
updateProgress() {
if (this.audioPlayer && this.progressBar && this.audioPlayer.duration) {
const progress = (this.audioPlayer.currentTime / this.audioPlayer.duration) * 100;
this.progressBar.value = progress;
const currentTime = document.getElementById('currentTime');
if (currentTime) {
currentTime.textContent = this.formatTime(this.audioPlayer.currentTime);
}
}
}
/**
* 更新总时长显示
*/
updateTotalTime() {
if (this.audioPlayer) {
const totalTime = document.getElementById('totalTime');
if (totalTime) {
totalTime.textContent = this.formatTime(this.audioPlayer.duration || 0);
}
}
}
/**
* 更新当前曲目信息
*/
updateCurrentTrackInfo(track) {
const titleElement = document.getElementById('trackTitle');
const artistElement = document.getElementById('trackArtist');
const albumElement = document.getElementById('trackAlbum');
if (titleElement) titleElement.textContent = track.name;
if (artistElement) artistElement.textContent = '未知艺术家';
if (albumElement) albumElement.textContent = '未知专辑';
}
/**
* 更新播放列表显示
*/
updatePlaylist() {
if (!this.playlist_element) return;
if (this.playlist.length === 0) {
this.playlist_element.innerHTML = '<li class="playlist-empty">暂无音乐文件</li>';
this.updateTrackCount();
return;
}
this.playlist_element.innerHTML = this.playlist.map((track, index) => `
<li class="playlist-item ${index === this.currentTrackIndex ? 'playing' : ''}"
data-index="${index}">
<span class="track-number">${index + 1}</span>
<div class="track-details">
<div class="track-name">${track.name}</div>
<div class="track-duration">${this.formatTime(track.duration)}</div>
</div>
</li>
`).join('');
// 添加点击事件
this.playlist_element.querySelectorAll('.playlist-item').forEach(item => {
item.addEventListener('click', () => {
const index = parseInt(item.dataset.index);
this.loadTrack(index);
if (!this.isPlaying) {
this.togglePlayPause();
}
});
});
this.updateTrackCount();
}
/**
* 更新播放列表高亮
*/
updatePlaylistHighlight() {
if (!this.playlist_element) return;
this.playlist_element.querySelectorAll('.playlist-item').forEach((item, index) => {
item.classList.toggle('playing', index === this.currentTrackIndex);
});
}
/**
* 清空播放列表
*/
clearPlaylist() {
this.playlist.forEach(track => {
if (track.url) {
URL.revokeObjectURL(track.url);
}
});
this.playlist = [];
this.currentTrackIndex = 0;
this.isPlaying = false;
if (this.audioPlayer) {
this.audioPlayer.pause();
this.audioPlayer.src = '';
}
this.updatePlaylist();
this.updatePlayButton();
this.updateCurrentTrackInfo({ name: '选择音乐文件开始播放' });
this.updateStatus('播放列表已清空');
this.savePlaylistToStorage();
}
/**
* 更新状态栏
*/
updateStatus(message) {
const statusText = document.getElementById('statusText');
if (statusText) {
statusText.textContent = message;
}
}
/**
* 更新歌曲数量
*/
updateTrackCount() {
const trackCount = document.getElementById('trackCount');
if (trackCount) {
trackCount.textContent = `${this.playlist.length} 首歌曲`;
}
}
/**
* 格式化时间显示
*/
formatTime(seconds) {
if (!seconds || isNaN(seconds)) return '00:00';
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
/**
* 保存播放列表到本地存储
*/
savePlaylistToStorage() {
try {
const playlistData = this.playlist.map(track => ({
id: track.id,
name: track.name,
duration: track.duration
}));
// 使用系统SDK进行存储操作
if (this.systemSDK && this.systemSDK.storage) {
console.log('[音乐播放器] 保存播放列表到系统存储');
this.systemSDK.storage.set('music-player-playlist', JSON.stringify(playlistData))
.then(result => {
if (result.success) {
console.log('[音乐播放器] 播放列表保存成功');
} else {
console.warn('[音乐播放器] 保存播放列表到系统存储失败:', result.error);
}
})
.catch(error => {
console.error('[音乐播放器] 保存播放列表失败:', error);
});
} else {
console.warn('[音乐播放器] 系统SDK未初始化无法保存播放列表');
}
} catch (error) {
console.error('[音乐播放器] 保存播放列表失败:', error);
}
}
/**
* 从本地存储加载播放列表
*/
async loadPlaylistFromStorage() {
try {
// 使用系统SDK进行存储操作
if (this.systemSDK && this.systemSDK.storage) {
console.log('[音乐播放器] 从系统存储加载播放列表');
const result = await this.systemSDK.storage.get('music-player-playlist');
if (result.success && result.data) {
try {
const playlistData = JSON.parse(result.data);
console.log(`[音乐播放器] 从系统存储加载了 ${playlistData.length} 首歌曲`);
// 这里可以恢复播放列表
} catch (parseError) {
console.warn('[音乐播放器] 解析播放列表数据失败:', parseError);
}
} else if (!result.success) {
console.warn('[音乐播放器] 从系统存储加载播放列表失败:', result.error);
}
} else {
console.warn('[音乐播放器] 系统SDK未初始化无法加载播放列表');
}
} catch (error) {
console.error('[音乐播放器] 加载播放列表失败:', error);
}
}
/**
* 清理资源
*/
cleanup() {
console.log('[音乐播放器] 清理资源');
// 暂停播放
if (this.audioPlayer) {
this.audioPlayer.pause();
}
// 释放对象URL
this.playlist.forEach(track => {
if (track.url) {
URL.revokeObjectURL(track.url);
}
});
// 保存状态
this.savePlaylistToStorage();
}
}
// 应用启动
let musicPlayerApp;
// 确保在DOM加载完成后启动应用
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
musicPlayerApp = new MusicPlayer();
});
} else {
musicPlayerApp = new MusicPlayer();
}
// 导出供外部使用
window.MusicPlayerApp = musicPlayerApp;

View File

@@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>音乐播放器</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="music-player">
<!-- 头部标题栏 -->
<header class="player-header">
<h1>🎵 音乐播放器</h1>
<div class="header-controls">
<button id="minimizeBtn" class="control-btn"></button>
<button id="maximizeBtn" class="control-btn">🔲</button>
<button id="closeBtn" class="control-btn"></button>
</div>
</header>
<!-- 主要播放区域 -->
<main class="player-main">
<!-- 当前播放信息 -->
<section class="current-track">
<div class="track-art">
<img id="trackImage" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIiBmaWxsPSIjRkY2NzMxIi8+CjxwYXRoIGQ9Ik0zNSA3NVYyNUw2NSA1MEwzNSA3NVoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPg==" alt="音乐封面">
</div>
<div class="track-info">
<h2 id="trackTitle">选择音乐文件开始播放</h2>
<p id="trackArtist">未知艺术家</p>
<p id="trackAlbum">未知专辑</p>
</div>
</section>
<!-- 进度控制 -->
<section class="progress-section">
<div class="time-display">
<span id="currentTime">00:00</span>
<input type="range" id="progressBar" min="0" max="100" value="0" class="progress-bar">
<span id="totalTime">00:00</span>
</div>
</section>
<!-- 播放控制 -->
<section class="controls">
<button id="shuffleBtn" class="control-btn secondary">🔀</button>
<button id="prevBtn" class="control-btn">⏮️</button>
<button id="playPauseBtn" class="control-btn primary">▶️</button>
<button id="nextBtn" class="control-btn">⏭️</button>
<button id="repeatBtn" class="control-btn secondary">🔁</button>
</section>
<!-- 音量控制 -->
<section class="volume-section">
<span class="volume-icon">🔊</span>
<input type="range" id="volumeBar" min="0" max="100" value="70" class="volume-bar">
<span id="volumeValue">70%</span>
</section>
</main>
<!-- 播放列表 -->
<aside class="playlist-section">
<div class="playlist-header">
<h3>播放列表</h3>
<div class="playlist-controls">
<input type="file" id="fileInput" accept="audio/*" multiple style="display: none;">
<button id="addFilesBtn" class="btn-secondary">添加文件</button>
<button id="clearPlaylistBtn" class="btn-secondary">清空列表</button>
</div>
</div>
<ul id="playlist" class="playlist">
<li class="playlist-empty">暂无音乐文件</li>
</ul>
</aside>
<!-- 状态栏 -->
<footer class="status-bar">
<span id="statusText">就绪</span>
<span id="trackCount">0 首歌曲</span>
</footer>
</div>
<!-- 隐藏的音频元素 -->
<audio id="audioPlayer" preload="none"></audio>
<script src="app.js"></script>
</body>
</html>

View File

@@ -0,0 +1,27 @@
{
"id": "music-player",
"name": "音乐播放器",
"version": "1.0.0",
"description": "一个功能丰富的音乐播放器应用,支持播放本地音乐文件",
"author": "外置应用开发者",
"entryPoint": "index.html",
"icon": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIHZpZXdCb3g9IjAgMCA0OCA0OCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iMjQiIGN5PSIyNCIgcj0iMjIiIGZpbGw9IiNGRjY3MzEiLz4KPHBhdGggZD0iTTE5IDMyVjE2TDMxIDI0TDE5IDMyWiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+",
"permissions": [
"storage.read",
"storage.write",
"file.read",
"system.notification"
],
"window": {
"width": 600,
"height": 400,
"minWidth": 400,
"minHeight": 300,
"resizable": true,
"minimizable": true,
"maximizable": true,
"closable": true
},
"category": "多媒体",
"keywords": ["音乐", "播放器", "媒体", "音频"]
}

View File

@@ -0,0 +1,430 @@
/* 音乐播放器样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #333;
overflow: hidden;
height: 100vh;
}
.music-player {
display: flex;
flex-direction: column;
height: 100vh;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
}
/* 头部标题栏 */
.player-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
background: linear-gradient(90deg, #FF6B35, #F7931E);
color: white;
user-select: none;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
.player-header h1 {
font-size: 18px;
font-weight: 600;
}
.header-controls {
display: flex;
gap: 8px;
}
.header-controls .control-btn {
width: 28px;
height: 28px;
border: none;
border-radius: 6px;
background: rgba(255, 255, 255, 0.2);
cursor: pointer;
font-size: 12px;
transition: all 0.2s ease;
}
.header-controls .control-btn:hover {
background: rgba(255, 255, 255, 0.3);
transform: scale(1.05);
}
/* 主播放区域 */
.player-main {
flex: 1;
padding: 20px;
display: flex;
flex-direction: column;
gap: 20px;
overflow-y: auto;
}
/* 当前播放信息 */
.current-track {
display: flex;
align-items: center;
gap: 20px;
padding: 20px;
background: rgba(255, 255, 255, 0.7);
border-radius: 12px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.track-art {
flex-shrink: 0;
}
.track-art img {
width: 80px;
height: 80px;
border-radius: 8px;
object-fit: cover;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.track-info h2 {
font-size: 20px;
font-weight: 600;
margin-bottom: 5px;
color: #2c3e50;
}
.track-info p {
font-size: 14px;
color: #7f8c8d;
margin-bottom: 3px;
}
/* 进度控制 */
.progress-section {
background: rgba(255, 255, 255, 0.7);
padding: 15px 20px;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.time-display {
display: flex;
align-items: center;
gap: 15px;
}
.time-display span {
font-size: 12px;
color: #666;
font-weight: 500;
min-width: 40px;
}
.progress-bar {
flex: 1;
height: 6px;
border-radius: 3px;
background: #e1e1e1;
outline: none;
cursor: pointer;
appearance: none;
}
.progress-bar::-webkit-slider-thumb {
appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: #FF6B35;
cursor: pointer;
box-shadow: 0 2px 6px rgba(255, 107, 53, 0.3);
transition: all 0.2s ease;
}
.progress-bar::-webkit-slider-thumb:hover {
transform: scale(1.2);
box-shadow: 0 3px 8px rgba(255, 107, 53, 0.4);
}
.progress-bar::-moz-range-thumb {
width: 16px;
height: 16px;
border-radius: 50%;
background: #FF6B35;
cursor: pointer;
border: none;
box-shadow: 0 2px 6px rgba(255, 107, 53, 0.3);
}
/* 播放控制 */
.controls {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
padding: 20px;
background: rgba(255, 255, 255, 0.7);
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.control-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
padding: 12px;
border-radius: 50%;
transition: all 0.2s ease;
user-select: none;
}
.control-btn:hover {
background: rgba(255, 107, 53, 0.1);
transform: scale(1.1);
}
.control-btn.primary {
background: linear-gradient(135deg, #FF6B35, #F7931E);
color: white;
font-size: 32px;
box-shadow: 0 4px 15px rgba(255, 107, 53, 0.3);
}
.control-btn.primary:hover {
transform: scale(1.05);
box-shadow: 0 6px 20px rgba(255, 107, 53, 0.4);
}
.control-btn.secondary {
font-size: 18px;
color: #666;
}
.control-btn.active {
color: #FF6B35;
background: rgba(255, 107, 53, 0.1);
}
/* 音量控制 */
.volume-section {
display: flex;
align-items: center;
gap: 10px;
padding: 15px 20px;
background: rgba(255, 255, 255, 0.7);
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.volume-icon {
font-size: 18px;
color: #666;
}
.volume-bar {
flex: 1;
height: 4px;
border-radius: 2px;
background: #e1e1e1;
outline: none;
cursor: pointer;
appearance: none;
}
.volume-bar::-webkit-slider-thumb {
appearance: none;
width: 14px;
height: 14px;
border-radius: 50%;
background: #FF6B35;
cursor: pointer;
box-shadow: 0 2px 4px rgba(255, 107, 53, 0.3);
}
.volume-bar::-moz-range-thumb {
width: 14px;
height: 14px;
border-radius: 50%;
background: #FF6B35;
cursor: pointer;
border: none;
box-shadow: 0 2px 4px rgba(255, 107, 53, 0.3);
}
#volumeValue {
font-size: 12px;
color: #666;
min-width: 30px;
}
/* 播放列表 */
.playlist-section {
background: rgba(255, 255, 255, 0.9);
border-top: 1px solid rgba(0, 0, 0, 0.05);
max-height: 200px;
display: flex;
flex-direction: column;
}
.playlist-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
.playlist-header h3 {
font-size: 16px;
font-weight: 600;
color: #2c3e50;
}
.playlist-controls {
display: flex;
gap: 10px;
}
.btn-secondary {
padding: 6px 12px;
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 6px;
cursor: pointer;
font-size: 12px;
color: #666;
transition: all 0.2s ease;
}
.btn-secondary:hover {
background: #e9ecef;
border-color: #adb5bd;
}
.playlist {
flex: 1;
overflow-y: auto;
list-style: none;
}
.playlist-item {
display: flex;
align-items: center;
padding: 12px 20px;
cursor: pointer;
transition: background-color 0.2s ease;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
.playlist-item:hover {
background: rgba(255, 107, 53, 0.1);
}
.playlist-item.playing {
background: rgba(255, 107, 53, 0.15);
color: #FF6B35;
font-weight: 500;
}
.playlist-item .track-number {
font-size: 12px;
color: #999;
min-width: 20px;
margin-right: 15px;
}
.playlist-item .track-details {
flex: 1;
}
.playlist-item .track-name {
font-size: 14px;
margin-bottom: 2px;
}
.playlist-item .track-duration {
font-size: 11px;
color: #999;
}
.playlist-empty {
padding: 20px;
text-align: center;
color: #999;
font-style: italic;
}
/* 状态栏 */
.status-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 20px;
background: #f8f9fa;
border-top: 1px solid rgba(0, 0, 0, 0.05);
font-size: 12px;
color: #666;
}
/* 响应式设计 */
@media (max-width: 500px) {
.current-track {
flex-direction: column;
text-align: center;
}
.track-art img {
width: 60px;
height: 60px;
}
.controls {
gap: 15px;
}
.control-btn {
font-size: 20px;
padding: 10px;
}
.control-btn.primary {
font-size: 28px;
}
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: #999;
}
/* 动画效果 */
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.control-btn.primary.playing {
animation: pulse 2s infinite;
}