其实通知栏的程序就是去掉窗口的边框,然后定位到通知栏小图标的下面,通知栏是可以获得它的位置坐标的,我们可以基于这个坐标进行计算来获得。这个我们可以使用 来帮助我们进行计算,大家也可以参考 menubar通知栏程序 - 图1 项目进行改造。

别忘记自己添加一下定义文件,

  1. declare module 'electron-positioner'

修改 tray.ts

由于我们需要控制顺序,ready 里面的顺序分开来看不是特别明显,所以我们提取到 index.ts 中,我们需要自定义 Tray 小图标的一些单击,双击,右键的事件,当计算的距离是 tray 开头的时候,需要传入 tray.getBounds() 获取的小图标坐标点。

  1. import Positioner from 'electron-positioner'
  2. import { opensetting } from './tray'
  3. let tray: Tray
  4. let positioner: any
  5. function setPostion(win: BrowserWindow) {
  6. // 得到位置
  7. positioner = new Positioner(win)
  8. positioner.move('trayCenter', tray.getBounds())
  9. win.show()
  10. }
  11. function createTray() {
  12. // 创建通知栏图标
  13. tray = new Tray(resolve(__dirname, 'tray_w24h24.png'))
  14. const contextMenu = Menu.buildFromTemplate([
  15. { label: '设置', click: opensetting },
  16. {
  17. label: '退出',
  18. role: 'quit'
  19. }
  20. ])
  21. const toggle = () => {
  22. // 显示隐藏
  23. if (!mainWindow) {
  24. return
  25. }
  26. if (mainWindow.isVisible()) {
  27. return mainWindow.hide()
  28. }
  29. positioner.move('trayCenter', tray.getBounds())
  30. mainWindow.show()
  31. }
  32. tray.on('double-click', toggle) // 双击
  33. tray.on('right-click', () => {
  34. // 右键菜单
  35. tray.popUpContextMenu(contextMenu)
  36. }
  37. async function ready() {
  38. mainWindow = createMainWindow({
  39. width: 400,
  40. height: 560,
  41. frame: false,
  42. transparent: true,
  43. show: false
  44. })
  45. createTray()
  46. setPostion(mainWindow)
  47. pluginSetUp()
  48. crawlSetUp()
  49. }

这个时候有一个小 bug , 所有的跳转按钮是可拖动的,我们需要阻止一下默认事件。

添加状态

我们需要一个状态来标记是否已经开始了队列。以及添加一个警告的方法。

  1. const store = new Store({
  2. currentPage: Main,
  3. msg: {
  4. type: 'success',
  5. content: ''
  6. },
  7. start: false // 开始队列否
  8. })
  9. function warring(content, timer = 1000) {
  10. store.set({
  11. msg: {
  12. type: 'warring',
  13. content
  14. }
  15. })
  16. setTimeout(resetMsg, timer) // 自动关闭消息
  17. }
  18. store.warring = warring.bind(store)

添加模板逻辑,访问全局状态,以 $ 开头。需要一个 canvas 来画进度框。包裹一层是为了居中,一定要在属性上面给确定的大小,要不然会画出来的东西就看不到了。对于 canvas 我也有录制过视屏,,不过不是免费的内容。

canvas 一定要通过 css 控制显隐,要不然会很难操作,动态挂载生命周期极其难控制。

  1. <Back/>
  2. {#if !$start}
  3. <label>文件名</label>
  4. <input bind:value=folderName />
  5. <label>爬取网址</label>
  6. <input bind:value=url />
  7. {/if}
  8. {#if !$start}
  9. <button on:click="download()">下载</button>
  10. {:else}
  11. <button on:click="stop()">中断</button>
  12. {/if}
  13. </div>
  14. {#if status.type }
  15. <div class="type">
  16. {status.type == 'crawl' && status.step == 'chapter' ? '爬取章节': ''}
  17. {status.type == 'crawl' && status.step == 'text'? '爬取内容': ''}
  18. {status.type == 'audio'? '转换音频': ''}
  19. </div>
  20. {/if}
  21. {#if status.title}
  22. <div class="title">
  23. {status.title}
  24. </div>
  25. {/if}
  26. <div class="box {$start?'show':''}">
  27. <canvas ref:canvas id="canvas" width="250" height="250"></canvas>
  28. </div>
  29. </div>

定义了颜色 color 它代表每一阶段进度条的颜色,然后在 oncreate 的时候,绘制 canvas。  这里我对所有的错误相关的进行了重构,错误消息都是 type: 'error' 的结构。当中止队列的时候不要忘记清空状态。once 是用来控制消息提示只调用一次,调用多次会出现 Bug,暂时无法找到何处出了问题,猜测是内置动画的原因。

对于百分比其实很简单,2 * PI / 100 就是百分之一 ,乘以百分比即可。为了保证画布的干净每次都需要 clearRect

添加重试与等待机制

有的时候会报 getaddrinfo ENOTFOUND 错误,我们添加一个重试的机制。

  1. const getMp3Data: any = async (text: string, opts: any = {}) => {
  2. const textArr = splitText(text)
  3. return Promise.all(
  4. textArr.map(async chunk => {
  5. const { data } = await client.text2audio(chunk, opts)
  6. return data
  7. })
  8. ).catch(() => getMp3Data(text, opts))
  9. }

再添加一个等待机制,自行到 tray.ts 里面添加配置哦,这样可以减小网络出错的概率。

    在测试的时候,发现  总是有的时候会遇见 有错误,会弹出选项框,这就很不友好,但是实际内容是没有丢失的,我们可以直接忽略掉它,修改 index.ts