手把手教你使用Go和HTML开发客户端软件的详细教程

2024-12-27 0 299

科技进步带动了程序员们开发桌面客户端软件的需求。Go语言在这一领域表现突出。今天,我们将探讨如何利用Go和HTML,通过开源项目打造跨平台桌面客户端。这既实用又充满乐趣。

Go语言的优势

Go语言设计简洁,效率高,拥有众多第三方库,使得开发变得更为顺畅。众多程序员因它强大的功能而喜爱。例如,在构建大型网络应用时,Go能高效快速地处理大量网络请求。在不少知名企业的桌面客户端开发中,Go语言的简洁性也帮助团队节省了大量开发时间。它之所以被广泛应用,还因为它能适配多种操作系统。

Go语言在桌面应用开发领域的影响力日益增强。有这样一个例子,某公司先前尝试用其他编程语言打造一款桌面小工具,结果开发过程漫长,问题不断。后来,他们改用Go语言,开发效率大幅提高,产品性能也显著增强。

多平台适用的重要性

go version

现在,用户们操作着各式各样的操作系统,桌面客户端若想受欢迎,必须能在多个平台上运行。一个出色的客户端软件,必须支持Windows、macOS以及Linux等主流操作系统。以某办公软件为例,它起初仅限于Windows系统,这让许多使用Mac和Linux的用户无法使用,限制了其用户基础。后来,该软件转变为跨平台软件,用户数量随之显著增长。

多平台兼容并非仅仅为了扩充用户规模,它还能增强软件的综合竞争力。若软件想在市场上表现出色,忽视任何主流平台都是不可取的。

安装Go的要点

开展开发任务前,需确保Go语言已正确安装。通常情况下,它应当已经安装好。若未安装,请从Go的官方网站下载。安装完成后,可以通过特定命令来检验安装是否成功。特别提醒,由于涉及cgo开发,用户还需额外安装gcc。举例来说,不少初学者可能会在这个步骤出错,忽视gcc的安装,这可能导致后续开发中遇到编译无法通过等问题。

go get github.com/sciter-sdk/go-sciter

安装Go在不同操作系统上都有技巧。比如在Windows上,安装时路径中不能含有中文字符,否则可能会出现未知的错误。至于Linux系统,有时候权限问题也得特别小心处理。

安装SDK的细节

构建项目时,SDK的安装是不可或缺的一步。需要访问官方网站,挑选与操作系统相匹配的SDK版本进行下载。下载完毕后,需要将解压出的动态库文件加入到系统环境变量,或者与执行文件放在一起。新版本的SDK可能并不兼容,建议优先使用4.4.8版。许多开发者往往忽略版本差异,导致使用新SDK时出现不少奇怪的兼容性问题。

各个操作系统在这一阶段需注意的点各不相同。以MacOS为例,其动态库文件的存放位置与Windows系统存在区别。若操作不当,程序运行可能会遭受重大影响。

安装go – 的步骤

接下来,我们需使用Go命令来安装那个名为go的包。这个包在开发过程中扮演着连接Go语言与HTML的关键角色。比如,在数据传输的模块里,go包能够让Go的后端逻辑与基于HTML制作的界面实现无缝对接。若安装不当,这种对接将无法完成,程序的功能也将因此不完整。

package main
import (
	"embed"
	"encoding/json"
	"fmt"
	"github.com/ncruces/zenity"
	"github.com/sciter-sdk/go-sciter"
	"github.com/sciter-sdk/go-sciter/window"
	"github.com/skratchdot/open-golang/open"
	"log"
	"os"
	"strconv"
	"strings"
	"time"
)
// 为了让生成的可执行文件包含了界面文件,直接把views文件夹嵌入到可执行文件中
//go:embed all:views
var views embed.FS
// 定义一个Map类型的数据结构
type Map map[string]interface{}
func main() {
	w, err := window.New(sciter.SW_TITLEBAR|sciter.SW_RESIZEABLE|sciter.SW_CONTROLS|sciter.SW_MAIN|sciter.SW_ENABLE_DEBUG, &sciter.Rect{
		Left:   100,
		Top:    50,
		Right:  1100,
		Bottom: 660,
	})
	if err != nil {
		log.Fatal(err)
	}
  // 定义一个回调函数,用于处理加载资源,home 是自定义的Scheme
	w.SetCallback(&sciter.CallbackHandler{
		OnLoadData: func(params *sciter.ScnLoadData) int {
			if strings.HasPrefix(params.Uri(), "home://") {
				fileData, err := views.ReadFile(params.Uri()[7:])
				if err == nil {
					w.DataReady(params.Uri()[7:], fileData)
				}
			}
			return 0
		},
	})
  // 这里定义一些与前端交互的函数
	w.DefineFunction("openUrl", openUrl)
	w.DefineFunction("getIndexingTasks", getIndexingTasks)
	w.DefineFunction("getIndexingTask", getIndexingTask)
	w.DefineFunction("getIndexingUrls", getIndexingUrls)
	w.DefineFunction("openAccountJson", openAccountJson)
	w.DefineFunction("loadIndexingSitemap", loadIndexingSitemap)
	w.DefineFunction("createGoogleIndexing", createGoogleIndexing)
	w.DefineFunction("startGoogleIndexing", startGoogleIndexing)
	w.DefineFunction("stopGoogleIndexing", stopGoogleIndexing)
	w.DefineFunction("deleteGoogleIndexing", deleteGoogleIndexing)
  // 加载主页面
	mainView, err := views.ReadFile("views/main.html")
	if err != nil {
		fmt.Print("nofile", err)
		os.Exit(0)
	}
	w.LoadHtml(string(mainView), "")
	w.SetTitle("谷歌推送")
	w.Show()
	w.Run()
}
func openUrl(args ...*sciter.Value) *sciter.Value {
	link := args[0].String()
	_ = open.Run(link)
	return nil
}
func getIndexingTasks(args ...*sciter.Value) *sciter.Value {
	//tasks := service.GetIndexingTasks()
  var task = []Map{}
	// 返回Json格式
	return jsonValue(tasks)
}
func getIndexingTask(args ...*sciter.Value) *sciter.Value {
	index, _ := strconv.Atoi(args[0].String())
	//task := service.GetIndexingTask(index)
  task := Map{}
	// 返回Json格式
	return jsonValue(task)
}
func getIndexingUrls(args ...*sciter.Value) *sciter.Value {
	index, _ := strconv.Atoi(args[0].String())
	page, _ := strconv.Atoi(args[1].String())
	if page < 1 {
		page = 1
	}
	//urls, totalPage := service.GetIndexingUrls(index, page)
  urls := []string{}
  totalPage := 0
	// 返回Json格式
	return jsonValue(Map{"urls": urls, "page": page, "totalPage": totalPage})
}
func openAccountJson(args ...*sciter.Value) *sciter.Value {
	accountPath, err := zenity.SelectFile(zenity.Title("选择Account Json文件"), zenity.FileFilter{
		Name:     "Json file",
		Patterns: []string{"*.json"},
		CaseFold: false,
	})
	if err != nil || accountPath == "" {
		fmt.Println(err)
		return nil
	}
	return sciter.NewValue(accountPath)
}
func createGoogleIndexing(args ...*sciter.Value) *sciter.Value {
	accountPath := args[0].String()
	domain := args[1].String()
	tmpNum := args[2].String()
	dailyNum, _ := strconv.Atoi(tmpNum)
	if dailyNum == 0 {
		dailyNum = 200
	}
	if !strings.HasPrefix(domain, "http") {
		return sciter.NewValue("网址填写错误")
	}
	// err := service.CreateIndexing(accountPath, domain, dailyNum)
	// if err != nil {
	// 	return sciter.NewValue(err.Error())
	// }
	return nil
}
func loadIndexingSitemap(args ...*sciter.Value) *sciter.Value {
	index, _ := strconv.Atoi(args[0].String())
	// err := service.LoadIndexingSitemap(index, false)
	// if err != nil {
	// 	return sciter.NewValue(err.Error())
	// }
	return nil
}
func startGoogleIndexing(args ...*sciter.Value) *sciter.Value {
	index, _ := strconv.Atoi(args[0].String())
	// err := service.StartGoogleIndexing(index)
	// if err != nil {
	// 	return sciter.NewValue(err.Error())
	// }
	return nil
}
func stopGoogleIndexing(args ...*sciter.Value) *sciter.Value {
	index, _ := strconv.Atoi(args[0].String())
	//service.StopGoogleIndexing(index)
	return nil
}
func deleteGoogleIndexing(args ...*sciter.Value) *sciter.Value {
	index, _ := strconv.Atoi(args[0].String())
	// 需要先stop
	// service.StopGoogleIndexing(index)
	// // 最后删除
	// service.DeleteIndexingTask(index)
	return nil
}
func jsonValue(val interface{}) *sciter.Value {
	buf, err := json.Marshal(val)
	if err != nil {
		return nil
	}
	return sciter.NewValue(string(buf))
}

在安装过程中,常常会遇到一些普遍的问题,比如因网络问题导致的下载失败。多数情况下,通过重新尝试可以解决问题。但有时,我们必须重新核查网络和开发环境的配置。

编写客户端程序过程

<html resizeable>
<head>
    <style src="home://views/style.css" />
    <meta charSet="utf-8" />
</head>
<body>
<div class="layout">
    <div class="aside">
        <h1 class="soft-title"><a href="home://views/main.html">谷歌<br/>推送助手</a></h1>
        <div class="aside-menus">
            <a href="home://views/task.html" class="menu-item">推送任务</a>
            <a href="home://views/help.html" class="menu-item">使用教程</a>
        </div>
    </div>
    <div class="container">
        <div class="home">
            <div>欢迎使用 谷歌推送助手</div>
            <div class="start-control">
                <a href="home://views/task.html" class="start-btn">开始使用</a>
            </div>
        </div>
    </div>
</div>
</body>
</html>

在编写客户端程序时,我们选取了一个案例,即自动将网站URL提交给推送服务器。完成项目创建后,需要将相应的.dll或.dylib文件存放在项目的主目录中,并依据实际需求复制数据库相关的动态库文件。

编写代码时,main.go文件和各个视图的HTML文件各有其独特之处。main.go承载着程序的核心逻辑,而HTML文件则负责构建用户界面。以views目录下的task.html为例,它需要处理列表展示等任务,这就要求开发者必须根据具体的功能需求来编写代码。

<html resizeable>
<head>
    <style src="home://views/style.css" />
    <meta charSet="utf-8" />
</head>
<body>
<div class="layout">
    <div class="aside">
        <h1 class="soft-title"><a href="home://views/main.html">谷歌<br/>推送助手</a></h1>
        <div class="aside-menus">
            <a href="home://views/task.html" class="menu-item active">推送任务</a>
            <a href="home://views/help.html" class="menu-item">使用教程</a>
        </div>
    </div>
    <div class="container">
        <div class="task-head">
            <button #newTask>新建任务</button>
        </div>
        <table class="task-list" #taskList>
                <colgroup>
                    <col width="30%">
                    <col width="15%">
                    <col width="15%">
                    <col width="15%">
                    <col width="30%">
                </colgroup>
                <thead>
                    <tr>
                        <th>站点域名</th>
                        <th>URL数量</th>
                        <th>已推送/每日推送</th>
                        <th>状态</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                <tr>
                    <td colspan="5">加载中</td>
                </tr>
                </tbody>
            </table>
    </div>
    <form class="control-form" #taslForm>
            <div class="form-header">
                <a class="form-close" #resultClose>关闭</a>
                <h3>创建/编辑任务</h3>
            </div>
        <div class="form-content">
            <div class="form-item">
                <div class="form-label">网址或Sitemap地址:</div>
                <div class="input-block">
                    <input(domain) class="layui-input" type="text" placeholder="http://或https://开头的网站地址或Sitemap地址" />
                    <div class="text-muted">说明:如果填写了Sitemap地址,将自动获取Sitemap中的所有URL推送,<br/>否则将抓取推送网址下的所有链接。</div>
                </div>
            </div>
            <div class="form-item">
                <div class="form-label">选择AccountJson:</div>
                <div class="input-block text-left">
                    <div>
                        <button #selectAccountJson>选择.json文件</button>
                        <span #accountJson></span>
                    </div>
                    <div class="text-muted">说明:需要上传谷歌账号的json文件,用于授权。</div>
                </div>
            </div>
            <div class="form-item">
                <div class="form-label">每天推送数量:</div>
                <div class="input-block">
                    <input(daily_num) class="layui-input" type="text" placeholder="默认200" />
                    <div class="text-muted">说明:请根据你的接口限制,填写每天推送的量。</div>
                </div>
            </div>
            <div>
                <button type="default" #formClose>返回</button>
                <button type="default" #taskSubmit>提交</button>
            </div>
        </div>
    </form>
        <div class="result-list" #resultList>
            <div class="form-header">
                <a class="form-close" #resultClose>关闭</a>
                <h3>查看结果</h3>
            </div>
            <div class="form-content">
                <table>
                    <colgroup>
                        <col width="40%">
                        <col width="60%">
                    </colgroup>
                    <tbody>
                    <tr>
                        <td>网站网站</td>
                        <td #resultDomain></td>
                    </tr>
                    <tr>
                        <td>每日推送数量</td>
                        <td #resultDailyNum>0条</td>
                    </tr>
                    <tr>
                        <td>执行状态</td>
                        <td #resultStatus>waiting</td>
                    </tr>
                    <tr>
                        <td>已发现URL</td>
                        <td #resultUrlCount>0条</td>
                    </tr>
                    <tr>
                        <td>已推送</td>
                        <td #resultDailyFinished>0条</td>
                    </tr>
                    <tr>
                        <td>推送结果</td>
                        <td class="text-left" #resultResult>
                            /* <div><span>https://www.anqicms.com</span><span>失败</span></div> */
                        </td>
                    </tr>
                    <tr>
                        <td></td>
                        <td>
                            <div>
                                <span class="pate-item">页码:<span #resultPage>1</span>/<span #resultTotalPage>1</span></span>
                                <button #resultPrev>上一页</button>
                                <button #resultNext>下一页</button>
                            </div>
                        </td>
                    </tr>
                    </tbody>
                </table>
            </div>
        </div>
</div>
</body>
</html>
<script type="text/tiscript">
    function syncTasks() {
        let res = view.getIndexingTasks()
        let result = JSON.parse(res)
        // 重置 #taskList
        let tb = $(#taskList>tbody)
        tb.html = ""
        if (!result) {
            return;
        }
        for (let i = 0; i < result.length; i++) {
            let task = result[i];
            let tr = new Element(#tr)
            tr.append(new Element(#td, task.domain))
            tr.append(new Element(#td, task.url_count + ""))
            tr.append(new Element(#td, task.daily_finished + "/" + task.daily_num))
            tr.append(new Element(#td, task.status + ""))
            let td = new Element(#td)
            td.@#class = "control-btns"
            td.attributes["id"] = "task-" + task.id
            addControlBtn(td, "结果", "task-result")
            if (task.status == "running") {
                addControlBtn(td, "停止", "task-stop")
            } else {
                addControlBtn(td, "启动", "task-start")
            }
            if (task.status != "running") {
                addControlBtn(td, "编辑", "task-edit")
                addControlBtn(td, "删除", "task-delete")
            }
            tr.append(td)
            tb.append(tr)
        }
    }
    function addControlBtn(el, str, cls) {
        let bt = new Element(#button, str)
        bt.@#class = cls
        el.append(bt)
    }
    self.on("click",".task-start", function() {
        let id = this.$p(td).attributes['id'].replace("task-", "")
        let result = view.startGoogleIndexing(id)
        //view.msgbox(#alert, result || "启动成功");
    });
    self.on("click",".task-stop", function() {
        let id = this.$p(td).attributes['id'].replace("task-", "")
        let result = view.stopGoogleIndexing(id)
        //view.msgbox(#alert, result || "停止成功");
    });
    self.on("click",".task-edit", function() {
        let id = this.$p(td).attributes['id'].replace("task-", "")
        showEditWindow(id)
    });
    self.on("click",".task-result", function() {
        let id = this.$p(td).attributes['id'].replace("task-", "")
        stdout.println(this.$p(td).attributes['id'])
        showResultWindow(id, 1)
    });
    self.on("click",".task-delete", function() {
        let id = this.$p(td).attributes['id'].replace("task-", "")
        let result = view.deleteGoogleIndexing(id)
        //view.msgbox(#alert, result || "删除成功");
    });
    // 新建任务
    event click $(#newTask){
        showEditWindow("-1")
    }
    function showEditWindow(id) {
        let res = view.getIndexingTask(id);
        let result = JSON.parse(res) || {};
        // 回填表单
        $(#taslForm).value=result;
        $(#taslForm).@.addClass("active");
    }
    // 表单
    let accountPath = '';
    event click $(#selectAccountJson){
        let filePath = view.openAccountJson()
        self#accountJson.text = filePath
        accountPath = filePath;
    }
    event click $(#formClose){
        $(#taslForm).@.removeClass("active");
    }
    event click $(#taskSubmit){
        // 第一步,先保存授权信息
        // 第二步,抓取Sitemap
        // 第三步,开始推送
        let result = view.createGoogleIndexing(accountPath, $(#taslForm).value.domain, $(#taslForm).value.daily_num)
        stdout.println(result)
        view.msgbox(#alert, result || "保存成功");
        if (!result) {
            $(#taslForm).@.removeClass("active");
        }
        // 同步结果
        syncTasks();
    }
    let curId = 0;
    let curPage = 1;
    let totalPage = 1;
    function showResultWindow(id, curp) {
        curId = id;
        let res = view.getIndexingTask(curId);
        let result = JSON.parse(res) || {};
        $(#resultList).@.addClass("active");
        $(#resultDomain).text = result.domain;
        $(#resultDailyNum).text = result.daily_num + "条";
        $(#resultStatus).text = result.status;
        $(#resultUrlCount).text = result.url_count + "条";
        $(#resultDailyFinished).text = "累计:" + result.total_finished + "条" + " / 今日:" + result.daily_finished + "条" + (result.daily_finished >= result.daily_num ? ' / 今日已完成' : '');
        
        let res2 = view.getIndexingUrls(curId, curp)
        let result2 = JSON.parse(res2) || {};
        $(#resultPage).text = result2.page + "";
        $(#resultTotalPage).text = result2.totalPage + "";
        curPage = result2.page
        totalPage = result2.totalPage
        $(#resultResult).html = '';
        for (let val in result2.urls) {
            $(#resultResult).append("
" + val.url + "  <span class='item-status' title='"+(val.msg || (val.status == 0 ? '未开始' :''))+"'>" + (val.status == 0 ? '-' : val.status != 200 ? ""+val.status+"" : val.status)+"
"
) } } event click $(#resultPrev) { if(curPage <= 1) { curPage = 1; return; } curPage = curPage - 1; showResultWindow(curId, curPage); } event click $(#resultNext) { if(curPage >= totalPage) { curPage = totalPage; return; } curPage = curPage + 1; showResultWindow(curId, curPage); } event click $(.item-status) { let title = this.attributes['title']; if (title) { view.msgbox(#error, title); } } event click $(#resultClose){ $(#resultList).@.removeClass("active"); $(#taslForm).@.removeClass("active"); } // 进来的时候先执行一遍 syncTasks(); // 加载tasklist,2秒钟刷新一次 self.timer(2000ms, function() { syncTasks(); return true; });
</script>

您是否曾用Go编写过桌面应用程序?欢迎在评论区分享您的使用心得。同时,别忘了为这篇文章点赞并转发。

申明:本文由第三方发布,内容仅代表作者观点,与本网站无关。对本文以及其中全部或者部分内容的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。本网发布或转载文章出于传递更多信息之目的,并不意味着赞同其观点或证实其描述,也不代表本网对其真实性负责。

七爪网 行业资讯 手把手教你使用Go和HTML开发客户端软件的详细教程 https://www.7claw.com/2805433.html

七爪网源码交易平台

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务