用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 的交互。
效果如下
完整的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>