章节内容
历史介绍
开发环境
代码封装
函数式
获取小说命令行版(上)
获取小说命令行版(中)
获取小说命令行版(下)
嵌入到应用中
插件机制
文字转语音
设置与信号中断
插件商店
下载与删除(上)
下载与删除(下)
通知栏程序
播放功能
美化界面
数据可视化(上)
数据可视化(中)
数据可视化(下)
嵌入到应用中
打包应用

首先我们要下载一下图标,可以到 iconfont 下载,然后放在 project_root/static 下面,文档在这里 。

# 全局状态

由于动态组件,退出会销毁,这很明显不是我们想要的,所以只能单独提取出来,而却最好把状态都放到全局来。

首先添加三个状态。

files: [],
index: 0,
music: true

# 修改模板

修改一下 index.ejs , 因为我们等会要做 css 动画,把背景颜色做一下  兼容,并且把高度给撑开。

html {
  background: #383a41;
  min-height: 100vh;
}

# 更改 App 组件

单独提取出来就不会被销毁。Music 要是被销毁了,那就没法播放音频了。

<Tip/>
<svelte:component this="{$currentPage}" name="page" />
<Music/>
<script>
    export default {
        components: {
            Tip: './components/Tip.svelte',
            Music: './pages/Music.svelte'
        },
    };
</script>

# 修改 Main.svelte

通过全局状态来控制菜单的显隐,同样的方式,控制 Music.svelte.

<div class="wrap {$music ? 'hidden': 'show'}">
  <div className="music-page-btn" on:click="$set({music: true})">播放器</div>
</div>
.hidden {
  display: none;
}

.wrap :global(a),
.wrap div {
  display: inline-flex;
  // .....
}

# 回退修复

修改 Link.svelte 使点击 Back.svelte 将 Music.svelte 隐藏,所以我们每次点击都可以将这个转态置为 false,这样就保证了 Music.svelte 的隐藏。

this.refs.link.addEventListener(
  'click',
  e => {
    e.preventDefault()
    this.store.set({
      music: false
    })
    this.store.changePage(this.get().to)
  },
  false
)

# 修改播放组件

首先我们需要把状态的更改都是用 this.store.set 和 $set 进行修改,然后还需要将文件名处理成正确的显示格式。

::-webkit-scrollbar-thumb 是简单的修改了一下滚动条的颜色。

<div class="music-page { $music ?'show':'hidden' } ">
    <Back/>
    <div>
        <ul class="main">
            {#each files as file, index}
            <li on:click="$set({index})">{handleName(file)}</li>
            {/each}
        </ul>
        <div class="control">
            <div class="title">{title}</div>
            <div class="flex">
                <button on:click="file()"><img src="plus-circle.png" width="25" height="25" alt="add"></button>
                <button on:click="play()"><img src="play-circle.png" width="25" height="25" alt="play"></button>
                <button on:click="pause()"><img src="time-out.png" width="25" height="25" alt="stop"></button>
                <button on:click="prev()"><img src="left-circle.png" width="25" height="25" alt="prev"></button>
                <button on:click="next()"><img src="right-circle.png" width="25" height="25" alt="next"></button>
            </div>
        </div>
    </div>
</div>

<script>
    import {
        ipcRenderer,
        remote
    } from "electron";
    const {
        dialog,
        app
    } = remote
    const {
        readdir
    } = remote.require('fs-extra')

    import {
        resolve
    } from 'path'

    function handleName(name) {
        return name.replace('.mp3', '').split('-')[1]
    }

    export default {
        data() {
            return {
                files: [],
                title: ''
            };
        },
        helpers: {
            handleName
        },
        components: {
            Back: '../components/Back.svelte'
        },
        oncreate() {
            this.player = new Audio()
            document.querySelector('#app').appendChild(this.player)

            const {
                files
            } = this.store.get()

            this.set({
                files
            })

            this.player.onended = this.next.bind(this) // 结束进入下一首
            let listener = this.store.on('state', ({
                changed
            }) => {
                if (changed.index || changed.files) { // 当重新选了文件,播放第一首
                    const {
                        files,
                        index
                    } = this.store.get()
                    this.player.src = "file://" + files[index]
                    this.set({
                        title: handleName(this.get().files[index])
                    })
                    this.play()
                }
            })
            this.on('destroy', listener.cancel)
        },
        ondestroy() {
            document.querySelectorAll("audio").forEach(a => a.remove())
        },
        methods: {
            next() {
                const {
                    index,
                    files
                } = this.store.get()
                let i = index + 1
                if (i > files.length - 1) {
                    i = files.length - 1
                }
                this.store.set({
                    index: i
                })
            },
            prev() {
                const {
                    index,
                    files
                } = this.store.get()
                let i = index - 1
                if (i < 0) {
                    i = 0
                }
                this.store.set({
                    index: i
                })
            },
            play() {
                if (!this.player.currentSrc) {
                    const {
                        files,
                        index
                    } = this.store.get()
                    this.set({
                        title: handleName(files[index])
                    })
                    this.player.src = "file://" + files[index]
                }
                this.player.play()
            },
            pause() {
                this.player.pause()
            },
            file() {
                dialog.showOpenDialog({
                    title: '选择音乐目录',
                    defaultPath: app.getPath('home'),
                    properties: ['openDirectory']
                }, async(folders) => {
                    if (folders.length > 0) {
                        const folder = folders[0]
                        let files = await readdir(folder)
                        this.set({
                            files
                        })
                        const compare = (a, b) => {
                            let t1 = a.split('-')[0];
                            let t2 = b.split('-');
                            return parseInt(t1) - parseInt(t2)
                        }
                        files = files.sort(compare).map(file => resolve(folder, file))
                        this.store.set({
                            files,
                            index: 0
                        })
                    }
                })
            }
        }
    };
</script>


<style>
    .music-page {
        transition: all ease-in .1s;
        height: 100vh;
    }

    .hidden {
        transform: translate(100%, 0)
    }

    .show {
        transform: translate(0, 0)
    }

    .control {
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
        height: 100px;
        background: #383A41;
        display: flex;
        flex-direction: column;
    }

    .title {
        color: #f8f8f8;
        padding: 1rem 0;
        text-align: center;
        font-size: .8rem;
        min-height: 54px;
        user-select: none;
    }

    .flex {
        display: flex;
        justify-content: space-around;
        align-items: center;
        flex-basis: 100%;
    }

    .control button {
        background: transparent;
        border: none;
        outline: none;
        cursor: pointer;
    }

    .control button:hover {
        background: #2f2f2f;
    }

    .main {
        height: calc(100vh - 160px);
        margin-bottom: 60px;
        overflow: scroll;
        background: #303238;
    }

    li {
        color: #f8f8f8;
        padding: 1.1rem 1rem;
        font-size: .9rem;
        cursor: pointer;
    }

    li:hover {
        background: #2f2f2f;
    }

     ::-webkit-scrollbar-thumb {
        background: #383A41;
    }

    .control button {
        line-height: 0;
        padding: .5rem 1rem;
    }
</style>