chendeben 10 месяцев назад
Родитель
Сommit
98cbd6495c

+ 412 - 0
entry/src/main/ets/common/util/PlaylistTable.ets

@@ -0,0 +1,412 @@
+import { relationalStore } from '@kit.ArkData';
+import { Context } from '@kit.AbilityKit';
+import Logger from './Logger';
+import RdbUtils from './RdbUtils';
+import { Playlist, PlaylistSong } from '../../viewmodel/Playlist';
+
+/**
+ * 歌单数据库操作类
+ */
+export default class PlaylistTable {
+  private context: Context;
+  private rdbStore: relationalStore.RdbStore | null = null;
+
+  constructor(context: Context) {
+    this.context = context;
+    this.initRdbStore();
+  }
+
+  /**
+   * 初始化数据库
+   */
+  private async initRdbStore(): Promise<void> {
+    try {
+      const config: relationalStore.StoreConfig = {
+        name: 'PlaylistStore.db',
+        securityLevel: relationalStore.SecurityLevel.S1  // 使用标准安全级别
+      };
+
+      this.rdbStore = await relationalStore.getRdbStore(this.context, config);
+
+      // 创建表
+      await this.createTables();
+
+      Logger.info('PlaylistTable', '数据库初始化成功');
+    } catch (error) {
+      Logger.error('PlaylistTable', `初始化数据库失败: ${error.message}`);
+    }
+  }
+
+  /**
+   * 创建表
+   */
+  private async createTables(): Promise<void> {
+    if (!this.rdbStore) {
+      return;
+    }
+
+    try {
+      // 创建歌单表
+      await this.rdbStore.executeSql(RdbUtils.PLAYLIST_TABLE.sqlCreate);
+      
+      // 创建歌单歌曲关联表
+      await this.rdbStore.executeSql(RdbUtils.PLAYLIST_SONG_TABLE.sqlCreate);
+      
+      Logger.info('PlaylistTable', '数据库表创建成功');
+    } catch (error) {
+      Logger.error('PlaylistTable', `创建表失败: ${error.message}`);
+    }
+  }
+
+  /**
+   * 生成唯一ID
+   */
+  private generateId(): string {
+    return Date.now().toString() + Math.random().toString(36).substr(2, 9);
+  }
+
+  /**
+   * 创建歌单
+   */
+  async createPlaylist(name: string, description?: string, coverPath?: string): Promise<boolean> {
+    if (!this.rdbStore) {
+      return false;
+    }
+
+    try {
+      const id = this.generateId();
+      const now = new Date().toISOString();
+      const sql = 'INSERT INTO playlistTable (id, name, coverPath, description, createTime, updateTime, songCount, sortOrder) VALUES (?, ?, ?, ?, ?, ?, ?, ?)';
+      const params = [id, name, coverPath || null, description || null, now, now, 0, 0];
+      
+      await this.rdbStore.executeSql(sql, params);
+      Logger.info('PlaylistTable', `歌单创建成功: ${name}`);
+      return true;
+    } catch (error) {
+      Logger.error('PlaylistTable', `创建歌单失败: ${error.message}`);
+      return false;
+    }
+  }
+
+  /**
+   * 删除歌单及其所有歌曲
+   */
+  async deletePlaylist(playlistId: string): Promise<boolean> {
+    if (!this.rdbStore) {
+      return false;
+    }
+
+    try {
+      // 先删除歌单歌曲关联
+      const deleteSongsSql = 'DELETE FROM playlistSongTable WHERE playlistId = ?';
+      await this.rdbStore.executeSql(deleteSongsSql, [playlistId]);
+      
+      // 再删除歌单
+      const deletePlaylistSql = 'DELETE FROM playlistTable WHERE id = ?';
+      await this.rdbStore.executeSql(deletePlaylistSql, [playlistId]);
+      
+      Logger.info('PlaylistTable', `歌单删除成功: ${playlistId}`);
+      return true;
+    } catch (error) {
+      Logger.error('PlaylistTable', `删除歌单失败: ${error.message}`);
+      return false;
+    }
+  }
+
+  /**
+   * 更新歌单信息
+   */
+  async updatePlaylist(playlistId: string, name?: string, description?: string, coverPath?: string): Promise<boolean> {
+    if (!this.rdbStore) {
+      return false;
+    }
+
+    try {
+      const updateTime = new Date().toISOString();
+      const updates: string[] = [];
+      const params: (string | number | null)[] = [];
+
+      if (name !== undefined) {
+        updates.push('name = ?');
+        params.push(name);
+      }
+      if (description !== undefined) {
+        updates.push('description = ?');
+        params.push(description);
+      }
+      if (coverPath !== undefined) {
+        updates.push('coverPath = ?');
+        params.push(coverPath);
+      }
+      
+      updates.push('updateTime = ?');
+      params.push(updateTime);
+      params.push(playlistId);
+
+      const sql = `UPDATE playlistTable SET ${updates.join(', ')} WHERE id = ?`;
+      await this.rdbStore.executeSql(sql, params);
+      
+      Logger.info('PlaylistTable', `歌单更新成功: ${playlistId}`);
+      return true;
+    } catch (error) {
+      Logger.error('PlaylistTable', `更新歌单失败: ${error.message}`);
+      return false;
+    }
+  }
+
+  /**
+   * 查询所有歌单
+   */
+  async queryAllPlaylists(): Promise<Playlist[]> {
+    if (!this.rdbStore) {
+      return [];
+    }
+
+    try {
+      const sql = 'SELECT * FROM playlistTable ORDER BY sortOrder ASC, createTime DESC';
+      const resultSet = await this.rdbStore.querySql(sql);
+      
+      const playlists: Playlist[] = [];
+      if (resultSet.goToFirstRow()) {
+        do {
+          const playlist = new Playlist(
+            resultSet.getString(resultSet.getColumnIndex('id')),
+            resultSet.getString(resultSet.getColumnIndex('name')),
+            resultSet.getString(resultSet.getColumnIndex('createTime')),
+            resultSet.getString(resultSet.getColumnIndex('updateTime')),
+            resultSet.getLong(resultSet.getColumnIndex('songCount')),
+            resultSet.getLong(resultSet.getColumnIndex('sortOrder')),
+            resultSet.getString(resultSet.getColumnIndex('coverPath')),
+            resultSet.getString(resultSet.getColumnIndex('description'))
+          );
+          playlists.push(playlist);
+        } while (resultSet.goToNextRow());
+      }
+      
+      resultSet.close();
+      return playlists;
+    } catch (error) {
+      Logger.error('PlaylistTable', `查询歌单失败: ${error.message}`);
+      return [];
+    }
+  }
+
+  /**
+   * 根据ID查询歌单
+   */
+  async queryPlaylistById(playlistId: string): Promise<Playlist | null> {
+    if (!this.rdbStore) {
+      return null;
+    }
+
+    try {
+      const sql = 'SELECT * FROM playlistTable WHERE id = ?';
+      const resultSet = await this.rdbStore.querySql(sql, [playlistId]);
+      
+      if (resultSet.goToFirstRow()) {
+        const playlist = new Playlist(
+          resultSet.getString(resultSet.getColumnIndex('id')),
+          resultSet.getString(resultSet.getColumnIndex('name')),
+          resultSet.getString(resultSet.getColumnIndex('createTime')),
+          resultSet.getString(resultSet.getColumnIndex('updateTime')),
+          resultSet.getLong(resultSet.getColumnIndex('songCount')),
+          resultSet.getLong(resultSet.getColumnIndex('sortOrder')),
+          resultSet.getString(resultSet.getColumnIndex('coverPath')),
+          resultSet.getString(resultSet.getColumnIndex('description'))
+        );
+        resultSet.close();
+        return playlist;
+      }
+      
+      resultSet.close();
+      return null;
+    } catch (error) {
+      Logger.error('PlaylistTable', `查询歌单失败: ${error.message}`);
+      return null;
+    }
+  }
+
+  /**
+   * 添加歌曲到歌单
+   */
+  async addSongToPlaylist(playlistId: string, songFilePath: string): Promise<boolean> {
+    if (!this.rdbStore) {
+      return false;
+    }
+
+    try {
+      // 检查歌曲是否已在歌单中
+      const isInPlaylist = await this.isSongInPlaylist(playlistId, songFilePath);
+      if (isInPlaylist) {
+        Logger.info('PlaylistTable', '歌曲已在歌单中');
+        return false;
+      }
+
+      const id = this.generateId();
+      const addTime = new Date().toISOString();
+      const sql = 'INSERT INTO playlistSongTable (id, playlistId, songFilePath, addTime, sortOrder) VALUES (?, ?, ?, ?, ?)';
+      const params = [id, playlistId, songFilePath, addTime, 0];
+      
+      await this.rdbStore.executeSql(sql, params);
+      
+      // 更新歌单歌曲数量
+      await this.updatePlaylistSongCount(playlistId);
+      
+      Logger.info('PlaylistTable', `歌曲添加到歌单成功: ${songFilePath}`);
+      return true;
+    } catch (error) {
+      Logger.error('PlaylistTable', `添加歌曲到歌单失败: ${error.message}`);
+      return false;
+    }
+  }
+
+  /**
+   * 从歌单中移除歌曲
+   */
+  async removeSongFromPlaylist(playlistId: string, songFilePath: string): Promise<boolean> {
+    if (!this.rdbStore) {
+      return false;
+    }
+
+    try {
+      const sql = 'DELETE FROM playlistSongTable WHERE playlistId = ? AND songFilePath = ?';
+      await this.rdbStore.executeSql(sql, [playlistId, songFilePath]);
+      
+      // 更新歌单歌曲数量
+      await this.updatePlaylistSongCount(playlistId);
+      
+      Logger.info('PlaylistTable', `歌曲从歌单移除成功: ${songFilePath}`);
+      return true;
+    } catch (error) {
+      Logger.error('PlaylistTable', `从歌单移除歌曲失败: ${error.message}`);
+      return false;
+    }
+  }
+
+  /**
+   * 查询歌单中的歌曲
+   */
+  async queryPlaylistSongs(playlistId: string): Promise<PlaylistSong[]> {
+    if (!this.rdbStore) {
+      return [];
+    }
+
+    try {
+      const sql = 'SELECT * FROM playlistSongTable WHERE playlistId = ? ORDER BY sortOrder ASC, addTime ASC';
+      const resultSet = await this.rdbStore.querySql(sql, [playlistId]);
+      
+      const songs: PlaylistSong[] = [];
+      if (resultSet.goToFirstRow()) {
+        do {
+          const song = new PlaylistSong(
+            resultSet.getString(resultSet.getColumnIndex('id')),
+            resultSet.getString(resultSet.getColumnIndex('playlistId')),
+            resultSet.getString(resultSet.getColumnIndex('songFilePath')),
+            resultSet.getString(resultSet.getColumnIndex('addTime')),
+            resultSet.getLong(resultSet.getColumnIndex('sortOrder'))
+          );
+          songs.push(song);
+        } while (resultSet.goToNextRow());
+      }
+      
+      resultSet.close();
+      return songs;
+    } catch (error) {
+      Logger.error('PlaylistTable', `查询歌单歌曲失败: ${error.message}`);
+      return [];
+    }
+  }
+
+  /**
+   * 检查歌曲是否在歌单中
+   */
+  async isSongInPlaylist(playlistId: string, songFilePath: string): Promise<boolean> {
+    if (!this.rdbStore) {
+      return false;
+    }
+
+    try {
+      const sql = 'SELECT COUNT(*) as count FROM playlistSongTable WHERE playlistId = ? AND songFilePath = ?';
+      const resultSet = await this.rdbStore.querySql(sql, [playlistId, songFilePath]);
+      
+      let isInPlaylist = false;
+      if (resultSet.goToFirstRow()) {
+        const count = resultSet.getLong(resultSet.getColumnIndex('count'));
+        isInPlaylist = count > 0;
+      }
+      
+      resultSet.close();
+      return isInPlaylist;
+    } catch (error) {
+      Logger.error('PlaylistTable', `检查歌曲是否在歌单中失败: ${error.message}`);
+      return false;
+    }
+  }
+
+  /**
+   * 更新歌单歌曲数量
+   */
+  private async updatePlaylistSongCount(playlistId: string): Promise<void> {
+    if (!this.rdbStore) {
+      return;
+    }
+
+    try {
+      const countSql = 'SELECT COUNT(*) as count FROM playlistSongTable WHERE playlistId = ?';
+      const resultSet = await this.rdbStore.querySql(countSql, [playlistId]);
+      
+      let count = 0;
+      if (resultSet.goToFirstRow()) {
+        count = resultSet.getLong(resultSet.getColumnIndex('count'));
+      }
+      resultSet.close();
+      
+      const updateSql = 'UPDATE playlistTable SET songCount = ?, updateTime = ? WHERE id = ?';
+      const updateTime = new Date().toISOString();
+      await this.rdbStore.executeSql(updateSql, [count, updateTime, playlistId]);
+    } catch (error) {
+      Logger.error('PlaylistTable', `更新歌单歌曲数量失败: ${error.message}`);
+    }
+  }
+
+  /**
+   * 更新歌单排序
+   */
+  async updatePlaylistSortOrder(playlistId: string, sortOrder: number): Promise<boolean> {
+    if (!this.rdbStore) {
+      return false;
+    }
+
+    try {
+      const updateTime = new Date().toISOString();
+      const sql = 'UPDATE playlistTable SET sortOrder = ?, updateTime = ? WHERE id = ?';
+      await this.rdbStore.executeSql(sql, [sortOrder, updateTime, playlistId]);
+      
+      Logger.info('PlaylistTable', `歌单排序更新成功: ${playlistId}`);
+      return true;
+    } catch (error) {
+      Logger.error('PlaylistTable', `更新歌单排序失败: ${error.message}`);
+      return false;
+    }
+  }
+
+  /**
+   * 更新歌单歌曲排序
+   */
+  async updatePlaylistSongSortOrder(playlistId: string, songFilePath: string, sortOrder: number): Promise<boolean> {
+    if (!this.rdbStore) {
+      return false;
+    }
+
+    try {
+      const sql = 'UPDATE playlistSongTable SET sortOrder = ? WHERE playlistId = ? AND songFilePath = ?';
+      await this.rdbStore.executeSql(sql, [sortOrder, playlistId, songFilePath]);
+      
+      Logger.info('PlaylistTable', `歌单歌曲排序更新成功: ${songFilePath}`);
+      return true;
+    } catch (error) {
+      Logger.error('PlaylistTable', `更新歌单歌曲排序失败: ${error.message}`);
+      return false;
+    }
+  }
+}

+ 34 - 0
entry/src/main/ets/common/util/RdbUtils.ets

@@ -92,6 +92,40 @@ export default class RdbUtils {
       'mimeType']
   };
 
+  /**
+   * 歌单表配置
+   */
+  static readonly PLAYLIST_TABLE: GeneratedObjectLiteralInterface_1 = {
+    tableName: 'playlistTable',
+    sqlCreate: 'CREATE TABLE IF NOT EXISTS playlistTable (\n' +
+      'id TEXT PRIMARY KEY NOT NULL,\n' +
+      'name TEXT NOT NULL,\n' +
+      'coverPath TEXT,\n' +
+      'description TEXT,\n' +
+      'createTime TEXT NOT NULL,\n' +
+      'updateTime TEXT NOT NULL,\n' +
+      'songCount INTEGER DEFAULT 0,\n' +
+      'sortOrder INTEGER DEFAULT 0\n' +
+      ')',
+    columns: ['id', 'name', 'coverPath', 'description', 'createTime', 'updateTime', 'songCount', 'sortOrder']
+  };
+
+  /**
+   * 歌单歌曲关联表配置
+   */
+  static readonly PLAYLIST_SONG_TABLE: GeneratedObjectLiteralInterface_1 = {
+    tableName: 'playlistSongTable',
+    sqlCreate: 'CREATE TABLE IF NOT EXISTS playlistSongTable (\n' +
+      'id TEXT PRIMARY KEY NOT NULL,\n' +
+      'playlistId TEXT NOT NULL,\n' +
+      'songFilePath TEXT NOT NULL,\n' +
+      'addTime TEXT NOT NULL,\n' +
+      'sortOrder INTEGER DEFAULT 0,\n' +
+      'UNIQUE(playlistId, songFilePath)\n' +
+      ')',
+    columns: ['id', 'playlistId', 'songFilePath', 'addTime', 'sortOrder']
+  };
+
   constructor(tableName: string, sqlCreateTable: string, columns: Array<string>) {
     this.tableName = tableName;
     this.sqlCreateTable = sqlCreateTable;

+ 184 - 0
entry/src/main/ets/dialog/PlaylistDialog.ets

@@ -0,0 +1,184 @@
+import { DialogHelper } from '@pura/harmony-dialog';
+import { Playlist } from '../viewmodel/Playlist';
+import { ToastUtil } from '@pura/harmony-utils';
+
+/**
+ * 歌单对话框组件
+ * 用于创建和编辑歌单
+ */
+@Component
+export struct PlaylistDialog {
+  @State playlistName: string = ''
+  @State playlistDescription: string = ''
+  @State isEditMode: boolean = false
+  @State originalPlaylist: Playlist | null = null
+  
+  // 回调函数
+  onConfirm?: (name: string, description: string) => void
+  onCancel?: () => void
+
+  aboutToAppear() {
+    if (this.originalPlaylist) {
+      this.isEditMode = true
+      this.playlistName = this.originalPlaylist.name
+      this.playlistDescription = this.originalPlaylist.description || ''
+    }
+  }
+
+  build() {
+    Column({ space: 20 }) {
+      // 标题
+      Text(this.isEditMode ? '编辑歌单' : '创建歌单')
+        .fontSize(18)
+        .fontWeight(FontWeight.Bold)
+        .fontColor($r('app.color.text_color'))
+        .margin({ top: 20 })
+
+      // 歌单名称输入框
+      Column({ space: 8 }) {
+        Text('歌单名称')
+          .fontSize(14)
+          .fontColor($r('app.color.text_color'))
+          .alignSelf(ItemAlign.Start)
+        
+        TextInput({ placeholder: '请输入歌单名称', text: this.playlistName })
+          .width('100%')
+          .height(40)
+          .backgroundColor($r('app.color.input_background'))
+          .borderRadius(8)
+          .padding({ left: 12, right: 12 })
+          .fontSize(14)
+          .fontColor($r('app.color.text_color'))
+          .onChange((value: string) => {
+            this.playlistName = value
+          })
+      }
+      .width('100%')
+      .alignItems(HorizontalAlign.Start)
+
+      // 歌单描述输入框
+      Column({ space: 8 }) {
+        Text('歌单描述(可选)')
+          .fontSize(14)
+          .fontColor($r('app.color.text_color'))
+          .alignSelf(ItemAlign.Start)
+        
+        TextArea({ placeholder: '请输入歌单描述', text: this.playlistDescription })
+          .width('100%')
+          .height(80)
+          .backgroundColor($r('app.color.input_background'))
+          .borderRadius(8)
+          .padding({ left: 12, right: 12, top: 8, bottom: 8 })
+          .fontSize(14)
+          .fontColor($r('app.color.text_color'))
+          .onChange((value: string) => {
+            this.playlistDescription = value
+          })
+      }
+      .width('100%')
+      .alignItems(HorizontalAlign.Start)
+
+      // 按钮区域
+      Row({ space: 12 }) {
+        Button('取消')
+          .width('45%')
+          .height(40)
+          .backgroundColor($r('app.color.cancel_button_background'))
+          .borderRadius(8)
+          .fontSize(14)
+          .fontColor($r('app.color.cancel_button_text'))
+          .onClick(() => {
+            this.onCancel?.()
+            // 关闭对话框
+            DialogHelper.closeDialog(this.isEditMode ? 'editPlaylistDialog' : 'createPlaylistDialog')
+          })
+
+        Button(this.isEditMode ? '保存' : '创建')
+          .width('45%')
+          .height(40)
+          .backgroundColor($r('app.color.theme_color'))
+          .borderRadius(8)
+          .fontSize(14)
+          .fontColor(Color.White)
+          .onClick(() => {
+            this.handleConfirm()
+          })
+      }
+      .width('100%')
+      .justifyContent(FlexAlign.SpaceBetween)
+      .margin({ top: 20, bottom: 20 })
+    }
+    .width('90%')
+    .backgroundColor($r('app.color.dialog_background'))
+    .borderRadius(12)
+    .padding({ left: 20, right: 20 })
+  }
+
+  /**
+   * 处理确认操作
+   */
+  private handleConfirm() {
+    if (!this.playlistName.trim()) {
+      ToastUtil.showToast('请输入歌单名称')
+      return
+    }
+
+    if (this.playlistName.trim().length > 50) {
+      ToastUtil.showToast('歌单名称不能超过50个字符')
+      return
+    }
+
+    if (this.playlistDescription.trim().length > 200) {
+      ToastUtil.showToast('歌单描述不能超过200个字符')
+      return
+    }
+
+    this.onConfirm?.(this.playlistName.trim(), this.playlistDescription.trim())
+    // 关闭对话框
+    DialogHelper.closeDialog(this.isEditMode ? 'editPlaylistDialog' : 'createPlaylistDialog')
+  }
+}
+
+/**
+ * 显示创建歌单对话框
+ */
+export function showCreatePlaylistDialog(
+  onConfirm: (name: string, description: string) => void,
+  onCancel?: () => void
+) {
+  DialogHelper.showCustomContentDialog({
+    dialogId: 'createPlaylistDialog',
+    title: '创建歌单',
+    autoCancel: true,
+    contentBuilder: () => {
+      PlaylistDialog({
+        onConfirm: onConfirm,
+        onCancel: onCancel
+      })
+    },
+    buttons: []
+  })
+}
+
+/**
+ * 显示编辑歌单对话框
+ */
+export function showEditPlaylistDialog(
+  playlist: Playlist,
+  onConfirm: (name: string, description: string) => void,
+  onCancel?: () => void
+) {
+  DialogHelper.showCustomContentDialog({
+    dialogId: 'editPlaylistDialog',
+    title: '编辑歌单',
+    autoCancel: true,
+    contentBuilder: () => {
+      PlaylistDialog({
+        originalPlaylist: playlist,
+        onConfirm: onConfirm,
+        onCancel: onCancel
+      })
+    },
+    buttons: []
+  })
+}

+ 198 - 5
entry/src/main/ets/pages/NewIndex.ets

@@ -27,7 +27,7 @@ import { resourceManager } from '@kit.LocalizationKit';
 import { systemShare } from '@kit.ShareKit';
 import { uniformTypeDescriptor } from '@kit.ArkData';
 import { hilog } from '@kit.PerformanceAnalysisKit';
-import { DialogHelper } from '@pura/harmony-dialog';
+import { DialogHelper, DialogAction } from '@pura/harmony-dialog';
 import UserUtil from '../common/util/UserUtil';
 import json from '@ohos.util.json';
 import { UserCenter } from './UserCenter';
@@ -40,6 +40,8 @@ import OnlineUpdateLog from '../dialog/OnlineUpdateLog';
 import { LocalMusic } from '../view/LocalMusic';
 import { ChartsCount } from './ChartsCount';
 import { image } from '@kit.ImageKit';
+import { Playlist } from '../viewmodel/Playlist';
+import PlaylistTable from '../common/util/PlaylistTable';
 
 // import { FFmpeg, FFProgressMessageParser } from '@sj/ffmpeg';
 
@@ -152,6 +154,12 @@ struct NewIndex {
     }
   })
   @State tabSelectedIndexes: number[] = [0]
+  
+  // 歌单相关状态变量
+  @State playlistList: Playlist[] = []
+  @State isShowCreatePlaylistDialog: boolean = false
+  @State selectedPlaylist: Playlist | null = null
+  private playlistTable: PlaylistTable | null = null
 
   /**
    * 返回键处理逻辑:
@@ -201,8 +209,8 @@ struct NewIndex {
 
 
   onPageShow() {
-
-
+    // 加载歌单列表
+    this.loadPlaylistList()
   }
   /**
    * 页面显示生命周期钩子
@@ -272,6 +280,14 @@ struct NewIndex {
 
     // 检查并显示更新日志
     this.checkAndShowUpdateLog();
+
+    // 初始化歌单数据库
+    await this.initPlaylistTable();
+
+    // 监听歌单刷新事件
+    emitter.on({ eventId: 2001 }, (eventData: emitter.EventData) => {
+      this.loadPlaylistList()
+    });
   }
 
   /**
@@ -302,6 +318,9 @@ struct NewIndex {
     this.breakpointSystem.unregister();
     emitter.off(888);
     emitter.off(1001);
+    
+    // 监听歌单刷新事件
+    emitter.off( 2001 );
 
   }
 
@@ -633,8 +652,8 @@ struct NewIndex {
       ListItemGroup({ header: this.buildUserInfoCard() }) {
         if(this.tabSelectedIndexes[0]==0){//分类
           this.buildTabCate()
-        }else{//歌单,这里写一个歌单的listItem
-
+        }else{//歌单
+          this.buildPlaylistTab()
         }
 
       }
@@ -992,6 +1011,180 @@ struct NewIndex {
   isLoginChange() {
     this.refreshUserInfoState();
   }
+
+  /**
+   * 构建歌单tab内容
+   */
+  @Builder
+  buildPlaylistTab() {
+    // 创建歌单按钮
+    ListItem() {
+      Button({ type: ButtonType.Capsule, stateEffect: true }) {
+        Row() {
+          SymbolGlyph($r('sys.symbol.plus_circle'))
+            .fontSize(22)
+            .fontColor([this.themeColor])
+            .effectStrategy(SymbolEffectStrategy.HIERARCHICAL)
+            .alignSelf(ItemAlign.Center)
+            .margin({ left: 25 })
+          
+          Text('创建歌单')
+            .margin({ left: 10, right: 20 })
+            .fontSize(15)
+            .fontColor($r('app.color.index_tab_font_color'))
+            .fontWeight(480)
+          
+          Blank()
+          
+          Image($r('app.media.arrow_right'))
+            .width(22)
+            .height(22)
+            .margin({ left: 20, right: 0 })
+            .align(Alignment.Center)
+        }
+        .width('100%')
+        .height(55)
+      }
+      .backgroundColor(Color.Transparent)
+      .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
+      .onClick(() => {
+        this.showCreatePlaylistDialog()
+      })
+    }
+    
+    // 歌单列表
+    ForEach(this.playlistList, (playlist: Playlist) => {
+      ListItem() {
+        Button({ type: ButtonType.Capsule, stateEffect: true }) {
+          Row() {
+            Image(playlist.coverPath || $r('app.media.hm_playlist'))
+              .width(22)
+              .height(22)
+              .margin({ left: 25 })
+              .borderRadius(4)
+              .clip(true)
+            
+            Column() {
+              Text(playlist.name)
+                .margin({ left: 10, right: 20 })
+                .fontSize(15)
+                .fontColor($r('app.color.index_tab_font_color'))
+                .fontWeight(480)
+                .maxLines(1)
+                .textOverflow({ overflow: TextOverflow.Ellipsis })
+              
+              Text(`${playlist.songCount}首`)
+                .margin({ left: 10, right: 20 })
+                .fontSize(12)
+                .fontColor($r('app.color.index_tab_font_color'))
+                .opacity(0.7)
+            }
+            .alignItems(HorizontalAlign.Start)
+            
+            Blank()
+            
+            Image($r('app.media.arrow_right'))
+              .width(22)
+              .height(22)
+              .margin({ left: 20, right: 0 })
+              .align(Alignment.Center)
+          }
+          .width('100%')
+          .height(55)
+        }
+        .backgroundColor(Color.Transparent)
+        .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.6 })
+        .onClick(() => {
+          this.openPlaylist(playlist)
+        })
+        .gesture(LongPressGesture().onAction(() => {
+          this.showPlaylistMenu(playlist)
+        }))
+      }
+    })
+  }
+
+  /**
+   * 显示创建歌单对话框
+   */
+  showCreatePlaylistDialog() {
+    if (!this.playlistTable) {
+      ToastUtil.showToast('歌单功能初始化中,请稍后再试')
+      return
+    }
+
+    DialogHelper.showTextInputDialog({
+      title: '创建歌单',
+      maskColor: Color.Transparent,
+      text: '',
+      placeholder: '请输入歌单名称',
+      onAction: async (action, dialogId, content) => {
+        if (action === DialogAction.TWO && content.trim()) {
+          // 创建歌单
+          const success = await this.playlistTable!.createPlaylist(content.trim())
+          if (success) {
+            ToastUtil.showToast('歌单创建成功')
+            // 刷新歌单列表
+            await this.loadPlaylistList()
+            // 发送歌单刷新事件
+            emitter.emit({ eventId: 2001 }, {})
+          } else {
+            ToastUtil.showToast('歌单创建失败')
+          }
+        }
+      }
+    })
+  }
+
+  /**
+   * 打开歌单详情
+   */
+  openPlaylist(playlist: Playlist) {
+    // TODO: 跳转到歌单详情页面
+    console.info('打开歌单:', playlist.name)
+  }
+
+  /**
+   * 显示歌单菜单
+   */
+  showPlaylistMenu(playlist: Playlist) {
+    // TODO: 显示歌单操作菜单(重命名、删除等)
+    console.info('显示歌单菜单:', playlist.name)
+  }
+
+  /**
+   * 初始化歌单数据库
+   */
+  async initPlaylistTable() {
+    try {
+      this.playlistTable = new PlaylistTable(this.context)
+      console.info('歌单数据库初始化成功')
+
+      // 等待数据库初始化完成后再加载数据
+      setTimeout(async () => {
+        await this.loadPlaylistList()
+      }, 500) // 延迟500ms确保数据库初始化完成
+    } catch (error) {
+      console.error('初始化歌单数据库失败:', error)
+    }
+  }
+
+  /**
+   * 加载歌单列表
+   */
+  async loadPlaylistList() {
+    try {
+      if (this.playlistTable) {
+        const playlists = await this.playlistTable.queryAllPlaylists()
+        this.playlistList = playlists
+        console.info(`成功加载 ${playlists.length} 个歌单`)
+      } else {
+        console.warn('歌单表未初始化')
+      }
+    } catch (error) {
+      console.error('加载歌单列表失败:', error)
+    }
+  }
 }
 
 // 1. 工具函数:将 #RRGGBB 字符串和透明度转为 'rgba(r,g,b,a)' 字符串,深色模式下可返回深色变体

+ 390 - 0
entry/src/main/ets/pages/PlaylistDetailPage.ets

@@ -0,0 +1,390 @@
+import { Playlist } from '../viewmodel/Playlist';
+import { VideoItem } from '../viewmodel/VideoItem';
+import PlaylistTable from '../common/util/PlaylistTable';
+import { MusicItem } from '../view/LocalMusic';
+import { emitter } from '@kit.BasicServicesKit';
+import { ToastUtil } from '@pura/harmony-utils';
+import { showEditPlaylistDialog } from '../dialog/PlaylistDialog';
+
+/**
+ * 歌单详情页面
+ * 展示歌单信息和歌曲列表
+ */
+@Entry
+@Component
+export struct PlaylistDetailPage {
+  @State playlist: Playlist | null = null
+  @State songList: VideoItem[] = []
+  @State isLoading: boolean = true
+  @State isShowEditDialog: boolean = false
+  
+  private playlistTable: PlaylistTable = new PlaylistTable(getContext(this))
+  private playlistId: string = ''
+
+  aboutToAppear() {
+    // 获取传入的歌单ID
+    const params = router.getParams() as Record<string, Object>
+    if (params && params['playlistId']) {
+      this.playlistId = params['playlistId'] as string
+      this.loadPlaylistDetail()
+    }
+  }
+
+  /**
+   * 加载歌单详情
+   */
+  async loadPlaylistDetail() {
+    try {
+      this.isLoading = true
+      
+      // 加载歌单信息
+      const playlist = await this.playlistTable.queryPlaylistById(this.playlistId)
+      if (playlist) {
+        this.playlist = playlist
+        
+        // 加载歌单歌曲
+        const songs = await this.playlistTable.queryPlaylistSongs(this.playlistId)
+        this.songList = songs
+      } else {
+        ToastUtil.showToast('歌单不存在')
+        router.back()
+      }
+    } catch (error) {
+      console.error('加载歌单详情失败:', error)
+      ToastUtil.showToast('加载歌单详情失败')
+    } finally {
+      this.isLoading = false
+    }
+  }
+
+  /**
+   * 播放歌单
+   */
+  playPlaylist() {
+    if (this.songList.length === 0) {
+      ToastUtil.showToast('歌单为空')
+      return
+    }
+
+    // 发送播放歌单事件
+    emitter.emit({ eventId: 2002 }, {
+      playlist: this.playlist,
+      songs: this.songList,
+      startIndex: 0
+    })
+    
+    ToastUtil.showToast('开始播放歌单')
+  }
+
+  /**
+   * 播放指定歌曲
+   */
+  playSong(song: VideoItem, index: number) {
+    // 发送播放歌单事件,指定开始播放的歌曲
+    emitter.emit({ eventId: 2002 }, {
+      playlist: this.playlist,
+      songs: this.songList,
+      startIndex: index
+    })
+  }
+
+  /**
+   * 编辑歌单
+   */
+  editPlaylist() {
+    if (this.playlist) {
+      showEditPlaylistDialog(
+        this.playlist,
+        async (name: string, description: string) => {
+          if (this.playlist) {
+            this.playlist.name = name
+            this.playlist.description = description
+            const success = await this.playlistTable.updatePlaylist(this.playlist)
+            if (success) {
+              ToastUtil.showToast('歌单更新成功')
+              // 发送刷新事件
+              emitter.emit({ eventId: 2001 }, {})
+            } else {
+              ToastUtil.showToast('歌单更新失败')
+            }
+          }
+        }
+      )
+    }
+  }
+
+  /**
+   * 删除歌单
+   */
+  async deletePlaylist() {
+    if (this.playlist) {
+      // 显示确认对话框
+      AlertDialog.show({
+        title: '删除歌单',
+        message: `确定要删除歌单"${this.playlist.name}"吗?此操作不可撤销。`,
+        primaryButton: {
+          value: '取消',
+          action: () => {}
+        },
+        secondaryButton: {
+          value: '删除',
+          fontColor: Color.Red,
+          action: async () => {
+            const success = await this.playlistTable.deletePlaylist(this.playlistId)
+            if (success) {
+              ToastUtil.showToast('歌单删除成功')
+              // 发送刷新事件
+              emitter.emit({ eventId: 2001 }, {})
+              router.back()
+            } else {
+              ToastUtil.showToast('歌单删除失败')
+            }
+          }
+        }
+      })
+    }
+  }
+
+  /**
+   * 从歌单移除歌曲
+   */
+  async removeSongFromPlaylist(song: VideoItem) {
+    if (this.playlist) {
+      const success = await this.playlistTable.removeSongFromPlaylist(this.playlistId, song.filePath)
+      if (success) {
+        // 从本地列表移除
+        const index = this.songList.findIndex(s => s.filePath === song.filePath)
+        if (index !== -1) {
+          this.songList.splice(index, 1)
+          this.songList = [...this.songList] // 触发UI更新
+        }
+        
+        // 更新歌单信息
+        this.playlist.songCount = this.songList.length
+        await this.playlistTable.updatePlaylist(this.playlist)
+        
+        ToastUtil.showToast('已从歌单移除')
+      } else {
+        ToastUtil.showToast('移除失败')
+      }
+    }
+  }
+
+  build() {
+    Column() {
+      // 顶部安全区和标题栏
+      Column() {
+        Blank()
+          .height(px2vp(AppUtil.getStatusBarHeight()))
+          .backgroundColor($r('app.color.title_bar_bg'))
+          .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])
+        
+        // 标题栏
+        Row() {
+          Image($r('app.media.arrow_left'))
+            .width(24)
+            .height(24)
+            .margin({ left: 12, right: 8 })
+            .onClick(() => {
+              router.back()
+            })
+          
+          Text(this.playlist?.name || '歌单详情')
+            .fontSize(18)
+            .fontColor(Color.White)
+            .fontWeight(FontWeight.Medium)
+            .layoutWeight(1)
+            .textAlign(TextAlign.Center)
+          
+          // 更多操作按钮
+          Image($r('app.media.more'))
+            .width(24)
+            .height(24)
+            .margin({ right: 12 })
+            .onClick(() => {
+              this.showMoreMenu()
+            })
+        }
+        .height(48)
+        .width('100%')
+        .alignItems(VerticalAlign.Center)
+        .backgroundColor($r('app.color.title_bar_bg'))
+      }
+
+      if (this.isLoading) {
+        // 加载状态
+        Column() {
+          LoadingProgress()
+            .width(40)
+            .height(40)
+            .color($r('app.color.theme_color'))
+          
+          Text('加载中...')
+            .fontSize(14)
+            .fontColor($r('app.color.text_color'))
+            .margin({ top: 12 })
+        }
+        .layoutWeight(1)
+        .justifyContent(FlexAlign.Center)
+      } else if (this.playlist) {
+        // 歌单信息区域
+        Column() {
+          // 歌单封面和基本信息
+          Row() {
+            Image(this.playlist.coverPath || $r('app.media.hm_playlist'))
+              .width(120)
+              .height(120)
+              .borderRadius(8)
+              .clip(true)
+              .margin({ right: 16 })
+            
+            Column() {
+              Text(this.playlist.name)
+                .fontSize(20)
+                .fontWeight(FontWeight.Bold)
+                .fontColor($r('app.color.text_color'))
+                .maxLines(2)
+                .textOverflow({ overflow: TextOverflow.Ellipsis })
+                .margin({ bottom: 8 })
+              
+              if (this.playlist.description) {
+                Text(this.playlist.description)
+                  .fontSize(14)
+                  .fontColor($r('app.color.text_color'))
+                  .opacity(0.7)
+                  .maxLines(3)
+                  .textOverflow({ overflow: TextOverflow.Ellipsis })
+                  .margin({ bottom: 8 })
+              }
+              
+              Text(`${this.playlist.songCount}首歌曲`)
+                .fontSize(12)
+                .fontColor($r('app.color.text_color'))
+                .opacity(0.6)
+            }
+            .layoutWeight(1)
+            .alignItems(HorizontalAlign.Start)
+          }
+          .width('100%')
+          .padding({ left: 16, right: 16, top: 16, bottom: 16 })
+          
+          // 操作按钮
+          Row({ space: 12 }) {
+            Button('播放全部')
+              .width('45%')
+              .height(40)
+              .backgroundColor($r('app.color.theme_color'))
+              .borderRadius(20)
+              .fontSize(14)
+              .fontColor(Color.White)
+              .onClick(() => {
+                this.playPlaylist()
+              })
+            
+            Button('编辑歌单')
+              .width('45%')
+              .height(40)
+              .backgroundColor($r('app.color.secondary_button_background'))
+              .borderRadius(20)
+              .fontSize(14)
+              .fontColor($r('app.color.text_color'))
+              .onClick(() => {
+                this.editPlaylist()
+              })
+          }
+          .width('100%')
+          .padding({ left: 16, right: 16, bottom: 16 })
+        }
+        .backgroundColor($r('app.color.card_background'))
+        .margin({ left: 16, right: 16, top: 16 })
+        .borderRadius(12)
+
+        // 歌曲列表
+        if (this.songList.length > 0) {
+          List() {
+            ForEach(this.songList, (song: VideoItem, index: number) => {
+              ListItem() {
+                MusicItem(song, index)
+                  .onClick(() => {
+                    this.playSong(song, index)
+                  })
+                  .gesture(LongPressGesture().onAction(() => {
+                    this.showSongMenu(song)
+                  }))
+              }
+            })
+          }
+          .layoutWeight(1)
+          .padding({ left: 16, right: 16 })
+        } else {
+          // 空状态
+          Column() {
+            Image($r('app.media.music_red'))
+              .width(80)
+              .height(80)
+              .opacity(0.3)
+              .margin({ bottom: 16 })
+            
+            Text('歌单为空')
+              .fontSize(16)
+              .fontColor($r('app.color.text_color'))
+              .opacity(0.6)
+              .margin({ bottom: 8 })
+            
+            Text('添加一些歌曲到歌单中')
+              .fontSize(14)
+              .fontColor($r('app.color.text_color'))
+              .opacity(0.4)
+          }
+          .layoutWeight(1)
+          .justifyContent(FlexAlign.Center)
+        }
+      }
+    }
+    .width('100%')
+    .height('100%')
+    .backgroundColor($r('app.color.index_background'))
+  }
+
+  /**
+   * 显示更多菜单
+   */
+  showMoreMenu() {
+    AlertDialog.show({
+      title: '歌单操作',
+      message: '',
+      primaryButton: {
+        value: '取消',
+        action: () => {}
+      },
+      secondaryButton: {
+        value: '删除歌单',
+        fontColor: Color.Red,
+        action: () => {
+          this.deletePlaylist()
+        }
+      }
+    })
+  }
+
+  /**
+   * 显示歌曲菜单
+   */
+  showSongMenu(song: VideoItem) {
+    AlertDialog.show({
+      title: song.name,
+      message: '',
+      primaryButton: {
+        value: '取消',
+        action: () => {}
+      },
+      secondaryButton: {
+        value: '从歌单移除',
+        fontColor: Color.Red,
+        action: () => {
+          this.removeSongFromPlaylist(song)
+        }
+      }
+    })
+  }
+}

+ 51 - 0
entry/src/main/ets/viewmodel/Playlist.ets

@@ -0,0 +1,51 @@
+import { VideoItem } from './VideoItem';
+
+/**
+ * 歌单数据模型
+ */
+@Observed
+export class Playlist {
+  id: string
+  name: string
+  coverPath?: string
+  description?: string
+  createTime: string
+  updateTime: string
+  songCount: number
+  sortOrder: number
+  songs?: VideoItem[] // 用于UI展示的歌曲列表
+
+  constructor(id: string, name: string, createTime: string, updateTime: string, 
+              songCount: number = 0, sortOrder: number = 0, 
+              coverPath?: string, description?: string, songs?: VideoItem[]) {
+    this.id = id
+    this.name = name
+    this.coverPath = coverPath
+    this.description = description
+    this.createTime = createTime
+    this.updateTime = updateTime
+    this.songCount = songCount
+    this.sortOrder = sortOrder
+    this.songs = songs
+  }
+}
+
+/**
+ * 歌单歌曲关联模型
+ */
+export class PlaylistSong {
+  id: string
+  playlistId: string
+  songFilePath: string
+  addTime: string
+  sortOrder: number
+
+  constructor(id: string, playlistId: string, songFilePath: string, 
+              addTime: string, sortOrder: number = 0) {
+    this.id = id
+    this.playlistId = playlistId
+    this.songFilePath = songFilePath
+    this.addTime = addTime
+    this.sortOrder = sortOrder
+  }
+}

+ 28 - 0
entry/src/main/resources/base/element/color.json

@@ -191,6 +191,34 @@
     {
       "name": "main_color",
       "value": "#103fb6"
+    },
+    {
+      "name": "theme_color",
+      "value": "#007DFF"
+    },
+    {
+      "name": "sheet_background",
+      "value": "#FFFFFF"
+    },
+    {
+      "name": "secondary_button_background",
+      "value": "#F5F5F5"
+    },
+    {
+      "name": "dialog_background",
+      "value": "#FFFFFF"
+    },
+    {
+      "name": "input_background",
+      "value": "#F5F5F5"
+    },
+    {
+      "name": "cancel_button_background",
+      "value": "#F5F5F5"
+    },
+    {
+      "name": "cancel_button_text",
+      "value": "#666666"
     }
   ]
 }

+ 144 - 0
entry/src/main/resources/base/element/string.json

@@ -482,6 +482,150 @@
     {
       "name": "back",
       "value": "返回"
+    },
+    {
+      "name": "playlist",
+      "value": "歌单"
+    },
+    {
+      "name": "create_playlist",
+      "value": "创建歌单"
+    },
+    {
+      "name": "edit_playlist",
+      "value": "编辑歌单"
+    },
+    {
+      "name": "delete_playlist",
+      "value": "删除歌单"
+    },
+    {
+      "name": "playlist_name",
+      "value": "歌单名称"
+    },
+    {
+      "name": "playlist_description",
+      "value": "歌单描述"
+    },
+    {
+      "name": "playlist_name_placeholder",
+      "value": "请输入歌单名称"
+    },
+    {
+      "name": "playlist_description_placeholder",
+      "value": "请输入歌单描述"
+    },
+    {
+      "name": "add_to_playlist",
+      "value": "添加到歌单"
+    },
+    {
+      "name": "batch_add_to_playlist",
+      "value": "批量添加到歌单"
+    },
+    {
+      "name": "select_playlist",
+      "value": "选择歌单"
+    },
+    {
+      "name": "play_all",
+      "value": "播放全部"
+    },
+    {
+      "name": "playlist_empty",
+      "value": "歌单为空"
+    },
+    {
+      "name": "playlist_empty_tip",
+      "value": "添加一些歌曲到歌单中"
+    },
+    {
+      "name": "no_playlist",
+      "value": "暂无歌单"
+    },
+    {
+      "name": "no_playlist_tip",
+      "value": "创建歌单来管理你的音乐"
+    },
+    {
+      "name": "new_playlist",
+      "value": "新建歌单"
+    },
+    {
+      "name": "songs_count",
+      "value": "首歌曲"
+    },
+    {
+      "name": "remove_from_playlist",
+      "value": "从歌单移除"
+    },
+    {
+      "name": "playlist_operations",
+      "value": "歌单操作"
+    },
+    {
+      "name": "playlist_created_success",
+      "value": "歌单创建成功"
+    },
+    {
+      "name": "playlist_updated_success",
+      "value": "歌单更新成功"
+    },
+    {
+      "name": "playlist_deleted_success",
+      "value": "歌单删除成功"
+    },
+    {
+      "name": "playlist_not_exist",
+      "value": "歌单不存在"
+    },
+    {
+      "name": "confirm_delete_playlist",
+      "value": "确定要删除歌单"
+    },
+    {
+      "name": "confirm_delete_playlist_tip",
+      "value": "吗?此操作不可撤销。"
+    },
+    {
+      "name": "playlist_name_required",
+      "value": "请输入歌单名称"
+    },
+    {
+      "name": "playlist_name_too_long",
+      "value": "歌单名称不能超过50个字符"
+    },
+    {
+      "name": "playlist_description_too_long",
+      "value": "歌单描述不能超过200个字符"
+    },
+    {
+      "name": "song_added_to_playlist",
+      "value": "已添加到"
+    },
+    {
+      "name": "song_removed_from_playlist",
+      "value": "已从"
+    },
+    {
+      "name": "song_removed_from_playlist_suffix",
+      "value": "移除"
+    },
+    {
+      "name": "batch_add_success",
+      "value": "已添加"
+    },
+    {
+      "name": "batch_add_success_suffix",
+      "value": "首歌曲到"
+    },
+    {
+      "name": "songs_already_in_playlist",
+      "value": "首歌曲已在歌单中"
+    },
+    {
+      "name": "playlist_play_started",
+      "value": "开始播放歌单"
     }
   ]
 }