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

# 应用商店

我对这个文件做了一个小小的重构,首先添加结构。这里我修改了存储结构,添加了一个 showname,这样比显示 filename 更好看一些。

<Back/>
<div class="wrap">
    <h2>插件列表</h2>
    <button class="d-btn" on:click="refresh()">刷新</button>
    <ul ref:list>
        {#each plugins as plugin,index}
        <li in:fade class="plugin">
            <a class="title" target="_blank" href={plugin.repository} title="点我跳转到仓库页">{plugin.showname}</a>

            <p class="info {infoIndex == index? '': 'hidden'}" on:click="showInfo(index)">
                {@html marked(plugin.description || '')}
            </p>
            <p class="control">
                {plugin.author}
                <span class="right">
                {#if plugin.downloaded }
                    <button class="d-btn del" on:click="del(plugin.filename)">
                    {#if inWorking(workingFilename, plugin.filename)}
                        ···
                    {:else}
                        删除
                    {/if}
                    </button>
                {:else}
                    <button class="d-btn green" on:click="download(plugin.filename, plugin.url)">
                    {#if inWorking(workingFilename, plugin.filename)}
                        ···
                    {:else}
                        下载
                    {/if}
                    </button>
                {/if}
            </span>
            </p>
        </li>
        {/each}
    </ul>
</div>

# 导入依赖与样式

svelte 包含一些额外的组件,从里面我们导入一些动画和辅助方法(数组推入,删除、补间动画等等)。

import { ipcRenderer as ipc, shell } from 'electron'
import marked from 'marked'
import { fade } from 'svelte-transitions'
import { push, splice } from 'svelte-extras'

# 添加 helper

function addDownloaed(plugins, local_plugins) {
  // 判断下载过的插件
  return plugins.map(plugin => {
    plugin.downloaded =
      local_plugins.findIndex(
        local_plugin => local_plugin == plugin.filename
      ) >= 0
    return plugin
  })
}

function inWorking(workingFilename, filename) {
  return workingFilename.indexOf(filename) >= 0
}

#  配置项

infoIndex 用来控制详情的展开项,workingFilename 代表着正在进行操作的文件,对于发送过一次,我们必须进行一下锁定,要不然用户点击太多次容易忙不过来。

destroy 的时候一定要移除监听,要不然会造成内存泄露。

export default {
  data() {
    return {
      local_plugins: [], // 本地插件
      all_plugins: [], // 所有插件
      infoIndex: -1, // 展开详情
      workingFilename: [] // 操作中文件
    }
  },
  components: {
    Back: '../components/Back.svelte'
  },
  helpers: {
    marked, // markdown 处理
    inWorking // 是否处理中
  },
  transitions: {
    fade
  },
  computed: {
    plugins: ({ all_plugins, local_plugins }) =>
      addDownloaed(all_plugins, local_plugins)
  },
  methods: {
    push,
    splice,
    showInfo(index) {
      // 显示某一个插件介绍
      const oldIndex = this.get().infoIndex
      if (oldIndex == index) {
        return this.set({
          infoIndex: -1
        })
      }
      this.set({
        infoIndex: index
      })
    },
    refresh() {
      // 刷新
      ipc.send('reload_local_plugins')
      ipc.send('reload_all_plugins')
    },
    download(filename, url) {
      // 下载插件
      if (inWorking(this.get().workingFilename, filename)) {
        return
      }
      this.push('workingFilename', filename)
      this.store.setMsg('info', `下载插件中`)
      ipc.send('download_plugin', {
        filename,
        url
      })
    },
    del(filename) {
      // 删除插件
      if (inWorking(this.get().workingFilename, filename)) {
        return
      }
      this.push('workingFilename', filename)
      this.store.setMsg('info', `删除插件中`)
      ipc.send('delete_plugin', {
        filename
      })
    },
    all_plugins(event, all_plugins) {
      this.set({
        all_plugins
      })
    },
    local_plugins(event, local_plugins) {
      this.set({
        local_plugins
      })
    },
    download_plugin_success(event, filename) {
      // 下载成功
      const filenames = this.get().workingFilename || []
      let index = filenames.indexOf(filename)
      if (index >= 0) {
        this.splice('workingFilename', index, 1)
      }
      if (this.get().workingFilename == 0) this.store.success('下载完成')
    },
    delete_plugin_success(event, filename) {
      // 删除成功
      const filenames = this.get().workingFilename || []
      let index = filenames.indexOf(filename)
      if (index >= 0) {
        this.splice('workingFilename', index, 1)
      }
      if (this.get().workingFilename == 0) this.store.success('删除完成')
    }
  },
  ondestroy() {
    // 清理监听
    ipc.removeListener('all_plugins', this.all_plugins.bind(this))
    ipc.removeListener('local_plugins', this.local_plugins.bind(this))
    ipc.removeListener(
      'download_plugin_success',
      this.download_plugin_success.bind(this)
    )
    ipc.removeListener(
      'delete_plugin_success',
      this.delete_plugin_success.bind(this)
    )
  },
  oncreate() {
    // 添加监听
    ipc.send('get_all_plugins')
    ipc.send('get_local_plugins')

    ipc.on('all_plugins', this.all_plugins.bind(this))
    ipc.on('local_plugins', this.local_plugins.bind(this))
    ipc.on('download_plugin_success', this.download_plugin_success.bind(this))
    ipc.on('delete_plugin_success', this.delete_plugin_success.bind(this))

    this.refs.list.addEventListener(
      // 处理点击事件,外部浏览器打开
      'click',
      e => {
        if (e.target.tagName.toLowerCase() == 'a') {
          e.preventDefault()
          shell.openExternal(e.target.href)
        }
      },
      true
    )
  }
}

# 样式

<style>
    h2 {
        color: #fff;
        font-weight: 600;
        padding: 1rem .5rem;
    }

    .wrap {
        height: 100vh;
        background: #303238;
    }

    h2~button {
        margin-left: .5rem;
        margin-bottom: 1rem;
    }

    ul {
        list-style: none;
        margin: .5rem
    }

    .plugin {
        padding: .5rem;
        background: #383A41;
        border-radius: .2rem;
        margin: .5rem 0;
    }

    .title {
        color: #fff;
        text-decoration: none;
        font-size: 20px;
        outline: none;
        font-weight: 900;
    }

    .info {
        color: #fff;
        font-size: .89rem;
        margin: .3rem 0;
        cursor: default;
        font-weight: 300;
        transition: all .2s;
        max-width: 100%;
    }

    .info :global(a) {
        text-decoration: none;
        color: #fff;
    }

    .info :global(a):hover {
        background: #fff;
        color: black;
    }

    .info.hidden {
        white-space: nowrap;
        text-overflow: ellipsis;
        overflow: hidden;
    }

    .control {
        font-size: .8rem;
        color: #fff;
        display: flex;
        align-items: center;
    }

    .right {
        margin-left: auto;
    }

    .d-btn {
        padding: .3rem 1rem;
        color: #fff;
        background: #54B3FF;
        border: none;
        outline: none;
        cursor: pointer;
        border-radius: 2px;
    }

    .del {
        background: transparent;
    }

    .green {
        background: #4BCB7C;
    }

    .solid {
        border: 1px solid #fff
    }
</style>

# 添加主界面

新建 pages/Main.svelte 页面,别忘记在 store 里面添加路由映射, 并且默认显示该页面。

<div class="wrap">
    <Link className="download-page-btn" to="Download">下载页面</Link>
    <Link className="music-page-btn" to="Music">播放器</Link>
    <Link className="store-page-btn" to="CrawlStore">爬虫商店</Link>
</div>

<script>
    export default {
        components: {
            Link: '../components/Link.svelte'
        },
    };
</script>

<style>
    .wrap {
        display: flex;
        height: 100vh;
        flex-wrap: wrap;
        background: #333;
    }

    .wrap :global(a) {
        display: inline-flex;
        width: 50%;
        text-decoration: none;
        justify-content: center;
        align-items: center;
        color: #fff;
        font-size: 1rem;
        letter-spacing: 5px;
        transition: all .2s;
    }

    .wrap :global(a):hover {
        background: #fb584c
    }
</style>

# 修改窗口创建参数

创建一个无边框的应用。

mainWindow = createMainWindow({
  width: 400,
  height: 560,
  frame: false,
  transparent: true
})