wails 的实例 1 剪切板记录工具

1,186 阅读2分钟

logo-universal.png

用wails 做了一个剪切板记录的小工具,主要是实践下wails框架中go程序和前端程序的数据交互。用到了wails中的事件,功能比较简单,只为练习用。

Events 事件

Wails 运行时提供了一个统一的事件系统,可以实现go和js 程序之间的事件传递,消息传递。

基本逻辑

这个小工具通过wails 提供的API,用go定时获取剪切板数据,然后通过事件机制实时的传给JS,然后在界面展示。

第一步

根据wails的文档 wails.io/zh-Hans/doc… 创建项目,我这里创建了一个vue-ts的(wails 安装过程省略)

wails init -n myproject -t vue-ts -ide vscode  // -ide vscode 参数会生成一个vscode的配置文件

然后vs code 打开项目,目录结构如下

wailsTest
├─.vscode
├─build
├─frontend
│   ├─dist
│   │  └─assets
│   ├─node_modules
│   ├─src
│   │  ├─assets
│   │  │  ├─fonts
│   │  │  └─images
│   │  ├─components
│   │  └─event
│   └─wailsjs
├─app.go
├─go.sum
├─main.go
└─wails.json

第二步

在app.go中写定时获取剪切板数据的方法,并在startup方法中调用,获取剪切板以后,触发自定义的事件copyHandler,事件的注册稍后会在js中注册。

// 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
	a.LogCopyInfo()
}

// 循环获取剪切板数据
func (a *App) LogCopyInfo() {

	go func(ctx context.Context) {
		for {
			time.Sleep(time.Millisecond * 100)
			str, err := runtime.ClipboardGetText(a.ctx)
			if err != nil {
				log.Println("app.go:43", err.Error())
			} else {
				// 事件触发
				runtime.EventsEmit(ctx, "copyHandler", str) 
			}
		}
	}(a.ctx)
}

第三步

修改前端文件,需要把自带的例子修改下,最终如下

App.vue

<script setup>
import HelloWorld from './components/HelloWorld.vue'</script>

<template>
  <HelloWorld/>
</template>

<style>
 .body{
  height: 100%;
 }
</style>

修改HelloWord.vue,在这里还尝试了下用js调用wails的运行时库,调用的时候需要引入

import {WindowSetSize,EventsOn,ClipboardSetText} from '../../wailsjs/runtime/runtime'

在js中注册事件 ,用runtime 中的 EventsOn,注册一个名为copyHandler的事件

// 注册事件
function AddEvent(){
  EventsOn("copyHandler",(msg)=>{
    let copyInfo={
      body:msg,
      time:new Date().toLocaleTimeString()
    }
    if (msg === data.lastCopyMsg){
      return
    }
    if (data.copyList.length>=data.maxLen){
      data.copyList.pop()
    }
    data.copyList.unshift(copyInfo)
    data.lastCopyMsg = msg
  })
}

最后还需要在程序退出的时候解绑事件,至此在进行编译调试打包,就可以用了

func (a *App) CloseApp() {
	// 解绑所有事件
	runtime.EventsOffAll(a.ctx)
	// 退出系统
	os.Exit(0)
}

总结

例子中还用到了动态设置窗口大小,做了无边框,自己写了 窗口的title,和关闭。例子主要练习wails 的go和js 的交互。

效果如下

image.png

完整的helloWord.vue
<template>
  <main style="height: 100%;width: 100%;overflow: hidden;">
    <div class="tool-bar" style="--wails-draggable:drag"  >
        <div class="title">
          <img :src="iconUrl" style="width: 16px;height: 16px;" />
          {{ data.name }} </div>
        <div style="cursor: pointer;" @click="closeApp">退出</div>
    </div>
    <div id="result" class="result">
      <div class="msg-item" v-for="(item,index) in data.copyList" :key="index" @click="showDetailHandler(item.body)" >
        <p class="msg-body">{{ item.body }}</p>
        <p class="msg-time">
            <span>{{ item.time }} </span>
            <img :src="iconCopy" style="width: 16px;height: 16px;" @click="setCopy(item)" />
        </p>
      </div>
    </div>
    <div class="setting">
      <span class="label">缓存条数</span>
      <input type="text" v-model="data.setLen" style="width:80px;" @blur="setMaxLen">
    </div>
  </main>
</template>

<script setup>
import {onMounted,reactive} from 'vue'
import {Greet,LogCopyInfo,CloseApp} from '../../wailsjs/go/main/App'
import {WindowSetSize,EventsOn,ClipboardSetText} from '../../wailsjs/runtime/runtime'
import iconUrl from "../assets/images/logo-universal.png"
import iconCopy from "../assets/images/copy.png"

onMounted(()=>{
  WindowSetSize(data.winsSize.w,data.winsSize.h)
  AddEvent()
})

const data = reactive({
  name: "剪切板",
  resultText: "Please enter your name below 👇",
  winsSize:{
    w:300,
    h:600,
  },
  copyList:[],
  lastCopyMsg:"",
  maxLen:10,
  setLen:10,
  detailMsg:"",
  showDetail:false
})

function startLoop() {
  LogCopyInfo()
}

function closeApp(){
  CloseApp()
}

// 注册事件
function AddEvent(){
  EventsOn("copyHandler",(msg)=>{
    let copyInfo={
      body:msg,
      time:new Date().toLocaleTimeString()
    }
    if (msg === data.lastCopyMsg){
      return
    }
    if (data.copyList.length>=data.maxLen){
      data.copyList.pop()
    }
    data.copyList.unshift(copyInfo)
    data.lastCopyMsg = msg
  })
}

// 复制到剪切板
function setCopy(item){
  ClipboardSetText(item.body)
}

// 改变最大行数
function setMaxLen(e){
  data.maxLen = Number(data.setLen)
  if (data.copyList.length > data.maxLen){
    let overLen=data.copyList.length - data.maxLen
    console.log("overLen" , overLen)
    for (let index = 0; index < overLen; index++) {
      data.copyList.pop()
    }
  }
}

</script>

<style scoped>
.result {
  width: 100%;
  height: calc( 100% - 65px);
  overflow: auto;
  text-align: left;
}
.msg-item{
    cursor: pointer;
    padding: 2px 5px;
    display: flex;
    justify-content: space-between;
    justify-items: center;
  }
  .msg-body ,.msg-time{
    margin: 0;
    padding: 0;
  }
  .msg-body{
    flex: 1;
    white-space:nowrap;
    text-overflow:ellipsis;
    overflow:hidden;
  }
  .msg-time{
    padding-left: 20px;
  }
  .msg-time img{
    margin-bottom: -2px;
    margin-left:2px;
  }
  .tool-bar{
    font-size: 12px;
    color: #000;
    font-weight: 500;
    padding: 8px 5px;
    display: flex;
    background: #fff;
    justify-content: space-between;
    justify-items: center;
  }
  .title{
    display: flex;
    justify-content: space-between;
    justify-items: center;
  }

  .setting .label{
    padding-right: 10px;
  }
  .detail{
    position: absolute;
    top: 0;
    left:  0 ;
    background-color: #0000008f;
  }
</style>