效果展示

编写一个热点查看程序,包含百度热搜、微博热搜、头条、知乎等,废话不说上效果图:

  • 效果图1:

  • 效果图2

  • 打包大小
涉及技术点

Golang

使用golang 1.9 编写代码

Wails + vue3

使用Wails技术实现GUI渲染,页面组件使用ant-design-vue,vite进行前端资源打包。
Wails技术
https://wails.io/zh-Hans/docs/introduction

Wails 是一个可让您使用 Go 和 Web 技术编写桌面应用的项目。

将它看作为 Go 的快并且轻量的 Electron 替代品。 您可以使用 Go 的灵活性和强大功能,结合丰富的现代前端,轻松的构建应用程序。

  • 原生菜单、对话框、主题和半透明
  • Windows、macOS 和 linux 支持
  • 内置 Svelte、React 、Preact 、Vue、Lit 和 Vanilla JS 的模板
  • 从 JavaScript 轻松调用 Go 方法
  • 自动将 Go 结构体转换为 TypeScript 模块
  • Windows 上不需要 CGO 或外部 DLL
  • 使用 Vite 的实时开发模式
  • 可以轻松创建、构建和打包应用的强大命令行工具
  • 使用 Wails 构建的应用程序兼容 Apple & Microsoft 商店

colly v2

应用程序打包

wails build -clean
环境准备

go环境

PATH~/go/bin

node环境

npm --version 检查环境

WebView2

在window环境下运行,需要保证WebView2,现在window10/11默认已经安装好了,微软强制内置的环境,可以忽略,如果后续环境检测不通过可以再额外进行安装。

Wails 环境

go install github.com/wailsapp/wails/v2/cmd/wails@latest

环境检测

wails doctor

如果提示 wails 找不到命令,检查 …go/bin 是否配置path环境

PS C:\Users\14639> wails doctor
DEB | Using go webview2loader
Wails CLI v2.5.1

 SUCCESS  Done.

# System

OS           | Windows 10 Home China
Version      | 2009 (Build: 22000)
ID           | 21H2
Go Version   | go1.19.9
Platform     | windows
Architecture | amd64

# Wails

Version | v2.5.1

# Dependencies

Dependency | Package Name | Status    | Version
WebView2   | N/A          | Installed | 113.0.1774.57
Nodejs     | N/A          | Installed | 16.14.2
npm        | N/A          | Installed | 8.5.0
*upx       | N/A          | Available |
*nsis      | N/A          | Available |
* - Optional Dependency

# Diagnosis

Your system is ready for Wails development!
Optional package(s) installation details:
  - upx : Available at https://upx.github.io/
  - nsis : More info at https://wails.io/docs/guides/windows-installer/

 ♥   If Wails is useful to you or your company, please consider sponsoring the project:
https://github.com/sponsors/leaanthony

具体环境配置细节可以参考wails官网:安装 | Wails

项目开发

项目创建

wails init -n wails-demo -t vue

项目结构

frontend根目录

项目命令

wails devwails build -cleannpm install xxx

代码介绍

wails dev
  • 核心代码介绍

启动类

main.go
package main  
  
import (  
"embed"  
"github.com/wailsapp/wails/v2"  
"github.com/wailsapp/wails/v2/pkg/options"  
"github.com/wailsapp/wails/v2/pkg/options/assetserver"  
)  
// 下面代码不能删除,是为了go打包资源文件
//go:embed all:frontend/dist  
var assets embed.FS  
  
func main() {  
// Create an instance of the app structure  
app := NewApp()  
  
// NewMenu  窗口操作菜单
//newMenu := menu.NewMenu()  
//FileMenu := newMenu.AddSubmenu("菜单")  
//FileMenu.AddText("设置", keys.CmdOrCtrl("t"), func(data *menu.CallbackData) {  
// runtime.EventsEmit(app.ctx, "open-file", time.Now().Format("2006-01-02 15:04:05"))  
//})  
//FileMenu.AddSeparator()  
//FileMenu.AddText("退出", keys.CmdOrCtrl("q"), func(_ *menu.CallbackData) {  
// runtime.Quit(app.ctx)  
//})   
	// Create application with options
	err := wails.Run(&options.App{
		Title:         "实时热点",
		Width:         1024,
		Height:        768,
		DisableResize: true,
		//Menu:   newMenu,
		AssetServer: &assetserver.Options{
			Assets: assets,
		},
		BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
		OnStartup:        app.startup,
		Bind: []interface{}{
			app,
		},
	})

	if err != nil {
		println("Error:", err.Error())
	}
}
App.go
package main

import (
	"context"
)

// App struct
type App struct {
	ctx context.Context
	hsr *HotSearchRouter
}

// NewApp creates a new App application struct
func NewApp() *App {
	return &App{}
}

// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
	a.ctx = ctx
	hsr := &HotSearchRouter{}
	hsr.Init()
	a.hsr = hsr
}

// Greet returns a greeting for the given name
func (a *App) Greet(index int) []HotSearchDto {
	if index == Last {
		return []HotSearchDto{}
	}
	return a.hsr.Route(index).Visit()
}

hot_search.go
前端核心代码 App.vue
<script setup>
import {reactive} from 'vue'
// import HelloWorld from './components/HelloWorld.vue'
import txImg from './assets/images/tx.gif'
import {Greet} from '../wailsjs/go/main/App'
import { onMounted } from 'vue'
import {
    PieChartOutlined,
    BarChartOutlined,
    DotChartOutlined,
    LineChartOutlined} from '@ant-design/icons-vue';

onMounted(() => {
    tabClick(0)
})
const data = reactive({
    activeKey: 0,
    image: [
        "https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png",
        txImg,
        "https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png",
        txImg,
        "https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png",
    ],
    hotData: {
        0: [],
        1: [],
        2: [],
        3: []
    },
    loading: false
})

function handleMouse(e) {
    // e.preventDefault();
}

function tabClick(index) {
    data.loading = true
    Greet(index).then(result => {
        console.log(result)
        data.loading = false
        data.hotData[index] = result
    })
}

function urlClick(url) {
    window.runtime.BrowserOpenURL(url)
    return false
}

</script>

<template>
  <div style="width: 100%; height: 100%;overflow: hidden;padding-bottom: 200px" @contextmenu="handleMouse">
      <div style="text-align: center">
          <a-image :width="200"
                   :src="data.image[data.activeKey]"/>
      </div>

      <div style="padding: 10px;overflow: auto;height: 100%;">
          <a-tabs v-model:activeKey="data.activeKey" type="card" @tabClick="tabClick">
              <a-tab-pane :key="0">
                  <template  #tab>
                <span>
                  <pie-chart-outlined />
                  百度
                </span>
                  </template>
                  <a-list item-layout="horizontal" :data-source="data.hotData[0]" rowKey="sort" :loading="data.loading">
                      <template #renderItem="{ item }">
                          <a-list-item>
                              <a-list-item-meta :description="item.desc">
                                  <template #title>
                                      <a href="javascript:" @click="urlClick(item.url)">{{ item.title }}</a>
                                  </template>
                                  <template #avatar>
                                      <a-avatar v-if="item.sort < 3" style="background-color: red">{{ item.sort + 1 }}</a-avatar>
                                      <a-avatar v-else-if="item.sort >=3 && item.sort < 6" style="background-color: #f56a00">{{ item.sort + 1 }}</a-avatar>
                                      <a-avatar v-else-if="item.sort >=6 && item.sort < 9" style="background-color: #c8b50eff">{{ item.sort + 1 }}</a-avatar>
                                      <a-avatar v-else>{{ item.sort + 1 }}</a-avatar>
                                  </template>
                              </a-list-item-meta>
                              <div>热度:
                                  <span v-if="item.sort < 3" style="color: red">{{ item.hot }}</span>
                                  <span v-else-if="item.sort >=3 && item.sort < 6" style="color: #f56a00">{{ item.hot }}</span>
                                  <span v-else-if="item.sort >=6 && item.sort < 9" style="color: #c8b50eff">{{ item.hot }}</span>
                                  <span v-else>{{ item.hot }}</span>
                              </div>
                          </a-list-item>
                      </template>
                  </a-list>
              </a-tab-pane>
              <a-tab-pane :key="1">
                  <template  #tab>
                <span>
                  <bar-chart-outlined />
                  微博
                </span>
                  </template>
                  <a-list item-layout="horizontal" :data-source="data.hotData[1]" rowKey="sort" :loading="data.loading">
                      <template #renderItem="{ item }">
                          <a-list-item>
                              <a-list-item-meta :description="item.desc">
                                  <template #title>
                                      <a href="javascript:" @click="urlClick(item.url)">{{ item.title }}</a>
                                  </template>
                                  <template #avatar>
                                      <a-avatar v-if="item.sort < 3" style="background-color: red">{{ item.sort + 1 }}</a-avatar>
                                      <a-avatar v-else-if="item.sort >=3 && item.sort < 6" style="background-color: #f56a00">{{ item.sort + 1 }}</a-avatar>
                                      <a-avatar v-else-if="item.sort >=6 && item.sort < 9" style="background-color: #c8b50eff">{{ item.sort + 1 }}</a-avatar>
                                      <a-avatar v-else>{{ item.sort + 1 }}</a-avatar>
                                  </template>
                              </a-list-item-meta>
                              <div>热度:
                                  <span v-if="item.sort < 3" style="color: red">{{ item.hot }}</span>
                                  <span v-else-if="item.sort >=3 && item.sort < 6" style="color: #f56a00">{{ item.hot }}</span>
                                  <span v-else-if="item.sort >=6 && item.sort < 9" style="color: #c8b50eff">{{ item.hot }}</span>
                                  <span v-else>{{ item.hot }}</span>
                              </div>
                          </a-list-item>
                      </template>
                  </a-list>
              </a-tab-pane>
              <a-tab-pane :key="2">
                  <template  #tab>
                <span>
                  <dot-chart-outlined />
                  头条
                </span>
                  </template>
                  <a-list item-layout="horizontal" :data-source="data.hotData[2]" rowKey="sort" :loading="data.loading">
                      <template #renderItem="{ item }">
                          <a-list-item>
                              <a-list-item-meta :description="item.desc">
                                  <template #title>
                                      <a href="javascript:" @click="urlClick(item.url)">{{ item.title }}</a>
                                  </template>
                                  <template #avatar>
                                      <a-avatar v-if="item.sort < 3" style="background-color: red">{{ item.sort + 1 }}</a-avatar>
                                      <a-avatar v-else-if="item.sort >=3 && item.sort < 6" style="background-color: #f56a00">{{ item.sort + 1 }}</a-avatar>
                                      <a-avatar v-else-if="item.sort >=6 && item.sort < 9" style="background-color: #c8b50eff">{{ item.sort + 1 }}</a-avatar>
                                      <a-avatar v-else>{{ item.sort + 1 }}</a-avatar>
                                  </template>
                              </a-list-item-meta>
                              <div>热度:
                                  <span v-if="item.sort < 3" style="color: red">{{ item.hot }}</span>
                                  <span v-else-if="item.sort >=3 && item.sort < 6" style="color: #f56a00">{{ item.hot }}</span>
                                  <span v-else-if="item.sort >=6 && item.sort < 9" style="color: #c8b50eff">{{ item.hot }}</span>
                                  <span v-else>{{ item.hot }}</span>
                              </div>
                          </a-list-item>
                      </template>
                  </a-list>
              </a-tab-pane>
              <a-tab-pane :key="3">
                  <template  #tab>
                <span>
                  <line-chart-outlined />
                  知乎
                </span>
                  </template>
                  <a-list item-layout="horizontal" :data-source="data.hotData[3]" rowKey="sort" :loading="data.loading">
                      <template #renderItem="{ item }">
                          <a-list-item>
                              <a-list-item-meta :description="item.desc">
                                  <template #title>
                                      <a href="javascript:" @click="urlClick(item.url)">{{ item.title }}</a>
                                  </template>
                                  <template #avatar>
                                      <a-avatar v-if="item.sort < 3" style="background-color: red">{{ item.sort + 1 }}</a-avatar>
                                      <a-avatar v-else-if="item.sort >=3 && item.sort < 6" style="background-color: #f56a00">{{ item.sort + 1 }}</a-avatar>
                                      <a-avatar v-else-if="item.sort >=6 && item.sort < 9" style="background-color: #c8b50eff">{{ item.sort + 1 }}</a-avatar>
                                      <a-avatar v-else>{{ item.sort + 1 }}</a-avatar>
                                  </template>
                              </a-list-item-meta>
                              <div>热度:
                                  <span v-if="item.sort < 3" style="color: red">{{ item.hot }}</span>
                                  <span v-else-if="item.sort >=3 && item.sort < 6" style="color: #f56a00">{{ item.hot }}</span>
                                  <span v-else-if="item.sort >=6 && item.sort < 9" style="color: #c8b50eff">{{ item.hot }}</span>
                                  <span v-else>{{ item.hot }}</span>
                              </div>
                          </a-list-item>
                      </template>
                  </a-list>
              </a-tab-pane>
          </a-tabs>
      </div>

  </div>

</template>

<style>
.ant-layout-header {
    background-color: #7cb305;
}
</style>