golang跨平台GUI框架fyne介绍与使用详解
Fyne 是一个使用 Go 编写的易于使用的 UI 工具包和应用程序 API。 它旨在构建使用单一代码库在桌面和移动设备上运行的应用程序。
通过批量调用身份证实名和三网手机实名和银行卡核验等接口,完成fyne框架的基本使用介绍
主要包括
- fyne框架的基本应用
- fyne框架的自定义字体使用
- 通过go的并发协程方式实现同时处理多任务
福利彩蛋:没有好玩的 API 接口?上百款免费接口等你来,免费 API,免费 API 大全
介绍
这是一款本地批量核验小工具,测试接口示例来源聚合数据。
计划支持:showapi、阿里云等
该应用集成了身份证实名、三网手机实名和银行卡二、三、四元素校验等多种核验方式;
旨在帮助有需求但无技术的用户快速、准确地完成身份验证,提升用户体验和工作效率;
让实名认证、银行卡核验变得简单、高效!
有技术支持的用户,可以选择接口调用
特点
- 身份证实名认证:通过姓名和身份证号码,快速验证身份证的真实性和一致性,确保您的身份信息安全。
- 三网手机实名认证:核验手机运营商三要素(手机号码、姓名、身份证号)信息是否一致。
- 银行卡二、三、四元素校验:姓名、身份证号码、银行卡号、手机号等相关信息,快速验证银行卡的真实性和一致性,保障您的资金安全。
- 本地批量处理:通过模板上传数据,批量进行核验
- 用户友好界面:简洁直观的操作界面,让您轻松上手,快速完成信息核验。
应用下载:本地批量核验工具
上代码
1.初始化应用
go
package main
import (
"fyne_study/global"
"fyne_study/model"
"fyne_study/mytheme"
"fyne_study/widgets"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
)
func main() {
global.AppRoot = app.NewWithID("cn.juhe.preferences.v1.0")
global.AppRoot.Settings().SetTheme(&mytheme.MyTheme{})
global.W = global.AppRoot.NewWindow("身份证、手机号、银行卡批量核验工具")
global.W.Resize(fyne.NewSize(1200, 820))
if !model.CheckTable() {
model.InitDatabase()
global.InfoDialog("数据库初始化结束")
}
// 设置主菜单:mainmenu是桌面应用上的菜单
// global.W.SetMainMenu(widgets.MenuList())
header := container.NewVBox(widgets.Row1(), widgets.Row2())
// header := container.NewVBox(widget.NewLabel("databalse"))
ly := container.NewBorder(header, nil, nil, nil, widgets.Row3())
global.W.SetContent(ly)
global.W.ShowAndRun()
}
2.menu 属性菜单
go
package widgets
import (
"fyne_study/global"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget"
)
func MenuList() *fyne.MainMenu {
//打开菜单项
openMenuItem := fyne.NewMenuItem("延迟有效期", func() {
sign := widget.NewEntry()
sign.SetPlaceHolder("请输入签名,延迟有效期")
dialog.ShowForm("延迟有效期", "确认", "取消", []*widget.FormItem{
{Text: "", Widget: container.NewGridWrap(fyne.NewSize(300, 35), sign)},
}, func(b bool) {
if b {
if sign.Text != "" {
global.LabelMsgText(sign.Text)
} else {
global.LabelMsgText("签名不正确")
}
}
}, global.W)
})
//保存菜单项
saveMenuItem := fyne.NewMenuItem("联系作者", func() {
dialog.ShowInformation("联系作者","QQ:1776403827 ",global.W)
})
fileMenu := fyne.NewMenu("File", openMenuItem, saveMenuItem)
// 表示文件菜单有三个选项
menu := fyne.NewMainMenu(fileMenu)
return menu
}
3.接口地址和appkey选择输入,保存
示例代码框中的appkey获取
在数据中心 > 我的API获取已申请的接口Appkey
同样在这里可以申请您感兴趣的接口
将申请接的接口的Key黏贴到对应的接口位置
每个接口仅第一次使用时需要配置并保存一次key
选择核验并发次数
选择对应的接口后,下载上传模板,严格按照模板格式上传
上传完数据后,点击开始
完成后先导出数据
再清空数据列表,进行下一批次核验
go
package widgets
import (
"fmt"
"fyne_study/global"
"fyne_study/utils"
"net/url"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
func Row1() *fyne.Container {
// 重新启动是选择上次的结果
api := global.GetCurrentApi()
// 接口选择
global.ApiSelect = widget.NewSelect(global.ApiList, nil)
global.ApiSelect.OnChanged = func(api string) {
_api := global.GetCurrentApi()
if global.Total > 0 && api != _api {
global.ErrorDialog("请求处理完并导出已有数据并清空表单,然后切换接口")
global.ApiSelect.SetSelected(_api)
return
}
// 保存选择的Api接口
global.SetPreferenceVal("selectedApi", "", "", api)
// 设置并发绑定数
nums := global.GetPreferenceVal(api, "concurrency", "string", "5").(string)
if global.PoolSelect != nil {
global.PoolSelect.SetSelected(nums)
}
// appkey处理
keyStr := global.GetPreferenceVal(api, "key", "string", "").(string)
global.BindAppKey.Set(keyStr)
fmt.Println("appkey:", keyStr)
if keyStr == "" {
global.LabelMsgText("当前任务:" + api + ";AppKey:未设置Key")
} else {
global.LabelMsgText("当前任务:" + api + ";AppKey:" + keyStr + ";并发数:" + nums)
}
if global.ListTitle != nil {
global.GetApiInfoSlice(api, "colums")
global.CurCols = global.GetApiInfoSlice(api, "colums")
global.CurRxp = global.GetApiInfoStr(api, "codeExp")
_cols := global.GetApiInfoSlice(_api, "colums")
diff := len(_cols) - len(global.CurCols)
// VBox->GridWarp->Label
grids := global.ListTitle.Objects
for i := 0; i < len(global.CurCols); i++ {
grids[i].(*fyne.Container).Objects[0].(*widget.Label).SetText(global.CurCols[i])
}
if diff > 0 {
// 列数减少
for i := 0; i < len(_cols); i++ {
if i >= len(global.CurCols) {
grids[i].(*fyne.Container).Objects[0].(*widget.Label).SetText("")
}
}
}
}
}
// 从配置加载选中的API
global.ApiSelect.SetSelected(api)
// appkey输入框
global.AppKeyEntry = widget.NewEntryWithData(global.BindAppKey)
global.AppKeyEntry.SetPlaceHolder("配置该接口的appkey")
// 并发数选择框
global.PoolSelect = widget.NewSelect([]string{"1", "5", "10", "15", "20" /*, "25", "30", "35", "40", "50"*/}, nil)
global.PoolSelect.OnChanged = func(val string) {
_nums := global.GetPreferenceVal(api, "concurrency", "string", "5").(string)
// 设置并发数之前,渠道任务未开启或暂停
if global.QueueStatus == "start" && _nums != val {
global.ErrorDialog("请先确任务为开始或已暂停,然后在设置并发数")
global.PoolSelect.SetSelected(_nums)
return
}
global.LabelMsgText(fmt.Sprintf("接口情况:%s,当前并发:%s", global.ApiSelect.Selected, val))
// 当并发数改变时,根据选择的接口进行,并发数设置
global.SetPreferenceVal(global.ApiSelect.Selected, "concurrency", "string", val)
}
// 从配置加载并发数量
nums := global.GetPreferenceVal(api, "concurrency", "string", "5").(string)
global.PoolSelect.SetSelected(nums)
save := widget.NewButtonWithIcon("保存配置", theme.DocumentSaveIcon(), func() {
msg := "当前任务:" + api
keyStr := global.AppKeyEntry.Text
global.SetPreferenceVal(global.ApiSelect.Selected, "key", "string", keyStr)
if keyStr == "" {
msg += ";AppKey:未设置Key"
} else {
msg += ";AppKey:" + keyStr
}
msg += ";并发数:" + nums
global.LabelMsgText(msg)
})
// // 通过NewGridWrap实现组件尺寸自定义
// rowEn := container.NewGridWrap(fyne.NewSize(280, 35), global.ApiSelect)
// rowKy := container.NewGridWrap(fyne.NewSize(280, 35), global.AppKeyEntry)
// rowPoL := container.NewGridWrap(fyne.NewSize(50, 35), widget.NewLabel("并发数:"))
// rowPo := container.NewGridWrap(fyne.NewSize(80, 35), global.PoolSelect)
// rowBt := container.NewGridWrap(fyne.NewSize(100, 35), save)
left := container.NewGridWithColumns(2, global.ApiSelect, global.AppKeyEntry)
right := container.NewGridWithColumns(3, widget.NewLabel("并发数:"), global.PoolSelect, save)
global.LabelMsg = widget.NewLabel("操作提示:")
df := time.Now().AddDate(0, 6, 0).Format(global.DateTime)
expire := global.GetPreferenceVal("app-expire", "", "string", df).(string)
if expire == "" {
expire = df
global.SetPreferenceVal("app-expire", "", "string", expire)
}
global.Expire = binding.BindString(&expire)
global.SignBtn = widget.NewButtonWithIcon("激活", theme.HistoryIcon(), func() {
SignActive(expire)
})
if expire >= time.Now().Format(global.DateTime) {
global.SignBtn.Hide()
} else {
global.SignBtn.Show()
}
_url, _ := url.Parse("https://www.juhe.cn/s/swnkequ0q5dccl=?s=utm_id55")
box := container.NewHBox(widget.NewHyperlink("注册聚合账号", _url), widget.NewLabel("问题反馈:1776403827(QQ)"), global.SignBtn, widget.NewLabel("有效期:"), widget.NewLabelWithData(global.Expire))
row := container.NewBorder(container.NewBorder(nil, nil, nil, box, global.LabelMsg), nil, nil, right, left)
return row
}
// 签名激活
func SignActive(expire string) {
sign := widget.NewMultiLineEntry()
sign.Wrapping = fyne.TextWrapWord
sign.SetPlaceHolder("请输入激活码,激活软件")
dialog.ShowForm("激活", "确认", "取消", []*widget.FormItem{
{Text: "", Widget: container.NewGridWrap(fyne.NewSize(300, 75), sign)},
}, func(b bool) {
if b {
if sign.Text != "" {
tx := global.GetPreferenceVal("app-active-sing", "", "string", "").(string)
if tx == sign.Text {
global.ErrorDialog("激活码已使用")
return
}
dec, err := utils.ECBDecrypt([]byte(sign.Text), global.EcbKey)
if err == nil && string(dec)[:10] == global.SignKey {
if err == nil {
date, err := time.Parse(global.DateTime, expire)
if err == nil {
expire = date.AddDate(1, 0, 0).Format(global.DateTime)
global.Expire.Set(expire)
global.SignBtn.Hide()
global.SetPreferenceVal("app-expire", "", "string", expire)
global.SetPreferenceVal("app-active-sing", "", "string", sign.Text)
}
}
}
if err != nil {
global.ErrorDialog("激活码不正确")
return
} else {
global.InfoDialog("软件激活成功")
return
}
} else {
global.LabelMsgText("签名不正确")
}
}
}, global.W)
}
4.上传需要核验的数据文件
布局一个文件上传以及模板下载区域
go
package widgets
import (
"fmt"
"fyne_study/global"
"fyne_study/model"
"strings"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/storage"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"github.com/tealeg/xlsx"
)
// border : center,right
func Row2() *fyne.Container {
file := widget.NewEntry()
open := widget.NewButtonWithIcon("打开文件", theme.DocumentSaveIcon(), nil)
// 点击打开上传文件
open.OnTapped = func() {
// 再次上传时候判断
if global.Total > 0 {
global.ErrorDialog("请求处理完并导出已有数据并清空表单,然后切换接口")
return
}
fileDialog := dialog.NewFileOpen(func(uc fyne.URIReadCloser, err error) {
if uc != nil {
fmt.Println(uc.URI().Path())
file.SetText(uc.URI().Path())
fmt.Println("uc.URI().Extension()", uc.URI().Extension())
}
fmt.Println("保存配置=", err)
}, global.W)
fileDialog.Resize(fyne.NewSize(1000,600))
fileDialog.SetFilter(storage.NewExtensionFileFilter([]string{".xlsx"}))
fileDialog.Show()
fmt.Println("保存配置")
}
// 处理进度条
upload := widget.NewButtonWithIcon("上传", theme.UploadIcon(), func() {
api := global.GetCurrentApi()
cols := global.GetApiInfoSlice(api, "colums")
dialog.ShowConfirm("提示", "确定上传该文件?"+file.Text, func(b bool) {
if b && file.Text != "" && strings.Contains(file.Text, ".xlsx") {
fmt.Println("正在读取:", file.Text)
excel, err := xlsx.OpenFile(file.Text)
if err == nil {
sheet := excel.Sheets[0]
// 上传数据校验
if len(sheet.Cols) != (len(cols) - 4) {
global.ErrorDialog(fmt.Sprintf("上传数据格式不正确:列数不匹配【%d】,请严格安照模板上传【%d】", len(sheet.Cols), (len(cols) - 4)))
return
}
pBar := widget.NewProgressBar()
pc := container.NewGridWrap(fyne.NewSize(300, 20), pBar)
bar := dialog.NewCustom("提示", "上传数据处理中...", pc, global.W)
bar.Show()
rows, err := model.BatchInsert(sheet, pBar)
if err != nil {
bar.Hide()
global.ErrorDialog("数据上传异常:" + err.Error())
} else {
bar.SetDismissText(fmt.Sprintf("上传结束:处理数据%d", rows))
bar.SetOnClosed(func() {
ListTabelRefresh(1, global.PageSize)
fmt.Println("重置当前页码:", 1)
file.SetText("")
if global.QueueStatus == "end" && global.QueueBtn != nil {
global.QueueBtn.SetIcon(theme.MediaPlayIcon())
global.QueueBtn.SetText("开始")
}
})
}
if rows > 0 {
global.ProcessBar.SetValue(0)
global.ProcessBar.Show()
}
} else {
dialog.ShowError(err, global.W)
}
} else if file.Text == "" || !strings.Contains(file.Text, ".xlsx") {
global.ErrorDialog("请选择正确的上传文件")
} else {
fmt.Println("文件地址", file.Text)
}
}, global.W)
})
// 示例文件下载
// download := widget.NewHyperlink("下载模板", nil)
// download.OnTapped = func() {
// api := global.GetCurrentApi()
// exampleUrl := global.GetApiInfoStr(api, "example")
// _url, _ := url.Parse(exampleUrl)
// fyne.CurrentApp().OpenURL(_url)
// }
download := widget.NewButtonWithIcon("下载模板", theme.DocumentSaveIcon(), func() {
// 获取当前选中的接口
api := global.GetCurrentApi()
// 打开文件保存地址
fileDialog := dialog.NewFileSave(func(uc fyne.URIWriteCloser, err error) {
if uc != nil {
example := global.GetApiInfo2Slice(api, "example")
f := xlsx.NewFile()
sheet, err := f.AddSheet("Sheet1")
if err != nil {
global.ErrorDialog(err.Error())
return
}
for _, item := range example {
row := sheet.AddRow()
for _, col := range item {
cell := row.AddCell()
cell.Value = col
}
}
err = f.Save(uc.URI().Path())
if err != nil {
global.LabelMsg.SetText(err.Error())
return
}
time.Sleep(time.Second * 1)
uc.Close()
global.InfoDialog("模板下载成功")
} else {
if err == nil {
global.LabelMsgText("")
} else {
global.LabelMsgText(err.Error())
}
}
}, global.W)
fileDialog.Resize(fyne.NewSize(1000,600))
fileDialog.SetFileName(api + "-tpl.xlsx")
fileDialog.Show()
})
row := container.NewBorder(nil, nil, nil, container.NewGridWithColumns(3, open, upload, download), file)
return row
}
5.渲染上传进度条,并发处理进度条,以及数据执行按钮等工具栏
渲染上传进度条,并发处理进度条,以及数据执行按钮等工具栏
核验结果的数据导出功能
go
package widgets
import (
"encoding/json"
"fmt"
"fyne_study/global"
"fyne_study/model"
"fyne_study/utils"
"strconv"
"strings"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"github.com/tealeg/xlsx"
)
// border : right
func Row3() *fyne.Container {
label := widget.NewLabel("数据列表:")
// ready,start,pause,end
global.QueueBtn = widget.NewButtonWithIcon("开始", theme.MediaPlayIcon(), nil)
global.QueueBtn.OnTapped = func() {
if global.QueueBtn != nil {
fmt.Println("=====", global.QueueBtn.Text)
if global.QueueBtn.Text == "开始" {
key, _ := global.BindAppKey.Get()
if len(key) != 32 {
global.InfoDialog("请先配置正确的接口请求Key")
return
}
expire, err := global.Expire.Get()
if err != nil {
global.LabelMsgText(err.Error())
return
}
if expire < time.Now().Format("2006-01-02") {
global.InfoDialog("该软件使用已过有效期,请重新激活")
global.SignBtn.Show()
return
} else {
global.SignBtn.Hide()
}
if global.Total > 0 {
if model.CompletedCount() == global.Total {
global.InfoDialog("该任务已完成,请导出并清空数据,进行下一批任务")
return
}
run := dialog.NewConfirm("提示", "确定立即开始任务?", func(e bool) {
if e {
global.QueueStatus = "start"
global.QueueBtn.SetText("暂停")
global.QueueBtn.SetIcon(theme.MediaPauseIcon())
go queueRun(global.QueueBtn)
}
}, global.W)
run.SetConfirmText("确认")
run.SetDismissText("取消")
run.Show()
} else {
global.InfoDialog("当前没有任务要执行")
}
} else if global.QueueBtn.Text == "暂停" {
global.QueueStatus = "pause"
global.QueueBtn.SetText("开始")
global.QueueBtn.SetIcon(theme.MediaPlayIcon())
global.LabelMsgText("当前任务已暂停")
} else {
global.InfoDialog("该任务已完成,请导出并清空数据,进行下一批任务")
return
}
}
}
export := widget.NewButtonWithIcon("导出数据", theme.DownloadIcon(), nil)
export.OnTapped = func() {
// 设置并发数之前,渠道任务未开启或暂停
if global.QueueStatus == "start" {
global.ErrorDialog("请先确任务未开始或已暂停,然后再导出数据")
return
}
count, err := model.UnCompletedCount()
if err != nil {
global.ErrorDialog(err.Error())
return
}
// 存在未处理的数据
if count > 0 {
dialog.ShowConfirm("提示", "数据未处理完,是否确认导出?", func(b bool) {
if b {
ExportData()
}
}, global.W)
} else {
ExportData()
}
}
// 情况数据按钮
clear := widget.NewButtonWithIcon("清空数据", theme.ContentClearIcon(), func() {
dialog.ShowConfirm("提示", "确认要情况当前任务数据?", func(b bool) {
if b {
// 设置并发数之前,渠道任务未开启或暂停
if global.QueueStatus == "start" {
global.ErrorDialog("请先确任务未开始或已暂停且数据已导出,然后再清空数据")
return
}
err := model.ClearDatabase()
if err != nil {
dialog.ShowError(err, global.W)
} else {
global.InfoDialog("数据库清空成功!")
ListTabelRefresh(1, 10)
}
global.ProcessBar.Hide()
}
}, global.W)
})
// 横向布局开始、导出、清空按钮
btns := container.NewHBox(global.QueueBtn, export, clear)
hed := container.NewBorder(nil, nil, label, btns, nil)
// 列表布局
list := Rowlist()
global.ProcessBar = widget.NewProgressBar()
process := model.CompletedCount()
if global.Total > 0 {
global.ProcessBar.SetValue(float64(process) / float64(global.Total))
} else {
global.ProcessBar.Hide()
}
barList := container.NewVBox(hed, global.ProcessBar)
row := container.NewBorder(barList, nil, nil, nil, list)
return row
}
// 数据处理结果导出
func ExportData() {
api := global.GetCurrentApi()
cols := global.GetApiInfoSlice(api, "colums")
cols = append([]string{"Id"}, cols...)
// 打开文件保存地址
fileDialog := dialog.NewFileSave(func(uc fyne.URIWriteCloser, err error) {
if uc != nil {
bar := widget.NewProgressBar()
pc := container.NewGridWrap(fyne.NewSize(300, 20), bar)
bs := dialog.NewCustom("导出进度", "数据写入中...", pc, global.W)
bs.Show()
f := xlsx.NewFile()
sheet, err := f.AddSheet("Sheet3")
if err != nil {
global.LabelMsg.SetText(err.Error())
}
items, err := model.ExportDataList()
if err != nil {
global.LabelMsgText(err.Error())
} else {
for i, item := range items {
bar.SetValue(float64(i+1) / float64(global.Total))
if i == 0 {
title := sheet.AddRow()
for _, col := range cols {
cell := title.AddCell()
cell.Value = col
}
}
row := sheet.AddRow()
for _, col := range cols {
cell := row.AddCell()
var cellData string
switch col {
case "Id":
cellData = fmt.Sprintf("%d", item.Id)
case "姓名":
cellData = item.Realname
case "身份证号":
cellData = item.Idcard
case "手机号":
cellData = item.Mobile
case "银行卡号":
cellData = item.Bankcard
case "结果":
text := item.Result
if text == "1" {
text = "一致"
} else if text == "2" {
text = "不一致"
}
cellData = text
case "描述":
cellData = item.Msg
case "状态":
text := "未处理"
if item.Completed == 1 {
text = "已处理"
}
cellData = text
case "任务Id":
cellData = item.Jobid
default:
cellData = ""
}
cell.Value = cellData
}
}
}
err = f.Save(uc.URI().Path())
if err != nil {
global.LabelMsg.SetText(err.Error())
}
bs.SetDismissText("导出完成")
uc.Close()
time.Sleep(time.Second * 1)
bs.Hide()
}
}, global.W)
fileDialog.Resize(fyne.NewSize(1000, 600))
fileDialog.SetFileName(api + "-export.xlsx")
fileDialog.Show()
}
// 队列任务执行
func queueRun(btn *widget.Button) {
api := global.GetCurrentApi()
limit, _ := strconv.Atoi(global.GetPreferenceVal(api, "concurrency", "string", "1").(string))
// 接口请求参数统一处理
authKey := global.GetPreferenceVal(api, "key", "string", "").(string)
// 接口标识,用于处理和提取接口参数
var apiHeader, apiParams map[string]string
// 接口地址
url := global.GetApiInfoStr(api, "url")
// 请求方式
method := global.GetApiInfoStr(api, "method")
// 请求header
apiHeaderTpl := global.GetApiInfoMap(api, "header")
jmh, _ := json.Marshal(apiHeaderTpl)
json.Unmarshal(jmh, &apiHeader)
// 请求参数
apiParamsTpl := global.GetApiInfoMap(api, "params")
jmp, _ := json.Marshal(apiParamsTpl)
json.Unmarshal(jmp, &apiParams)
// 响应参数模板
responseTpl := global.GetApiInfoMapI(api, "response")
// 响应参数模板
checkCode := global.GetApiInfoStr(api, "checkCode")
fmt.Println("key:", authKey, apiParams)
// 接口appkey参数传递方式
authtype := global.GetApiInfoStr(api, "authtype")
switch authtype {
case "params":
for k, v := range apiParams {
if strings.Contains(v, "APP-KEY") {
apiHeader[k] = strings.Replace(apiHeader[k], "APP-KEY", authKey, -1)
}
}
case "header":
for k, v := range apiHeader {
if strings.Contains(v, "APP-KEY") {
apiHeader[k] = strings.Replace(apiHeader[k], "APP-KEY", authKey, -1)
}
}
}
pcw := utils.NewPool(limit)
process := 0
refresh := true
for global.QueueStatus == "start" {
rows, err := model.QueueList(limit)
if err != nil {
global.QueueStatus = "pause"
global.QueueBtn.SetText("开始")
global.QueueBtn.SetIcon(theme.MediaPlayIcon())
global.LabelMsgText("当前任务已暂停")
global.ErrorDialog("任务查询异常:" + err.Error())
break
}
for _, item := range rows {
pcw.Add(1)
go apiRequest(item, pcw, url, method, checkCode, apiHeader, apiParams, responseTpl)
}
pcw.Wait()
process = model.CompletedCount()
global.ProcessBar.SetValue(float64(process) / float64(global.Total))
fmt.Println("--执行任务:", limit, len(rows), process, global.Total, float64(process)/float64(global.Total), global.PageSize, process, refresh)
if len(rows) < limit {
global.QueueStatus = "end"
}
if global.PageSize < process && refresh {
ListTabelRefresh(1, global.PageSize)
refresh = false
}
}
fmt.Println("任务暂停或结束:", global.QueueStatus)
if global.QueueStatus == "end" {
btn.SetIcon(theme.MediaStopIcon())
btn.SetText("完成")
ListTabelRefresh(1, global.PageSize)
global.InfoDialog("任务执行结束,请及时导出数据")
}
if global.QueueStatus == "pause" {
ListTabelRefresh(1, global.PageSize)
}
}
// 接口请求
func apiRequest(row model.Persons, pcw *utils.PoolChanWt, url, method, checkCode string, apiHeader, apiParams map[string]string, responseTpl map[string]interface{}) {
// 参数处理赋值
for key, val := range apiParams {
switch val {
case "Realname":
apiParams[key] = row.Realname
case "Idcard":
apiParams[key] = row.Idcard
case "Mobile":
apiParams[key] = row.Mobile
case "Bankcard":
apiParams[key] = row.Bankcard
}
}
result := ApiRequstDispatch(url, method, checkCode, apiHeader, apiParams, responseTpl)
model.UpdateResult(row, result)
pcw.Done()
}
6. 核验数据结果列表
导入文件的数据列表
核验结果列
go
package widgets
import (
"fmt"
"fyne_study/global"
"fyne_study/model"
"reflect"
"strconv"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/widget"
"github.com/gogf/gf/v2/container/garray"
)
// border : right
// func rowlist() *fyne.Container {
func Rowlist() *fyne.Container {
// global.ListPerson, total = GetDataList(pageSize, currPage)
fmt.Println("列表查询结果:", len(global.ListPerson), global.Total)
api := global.GetCurrentApi()
global.CurCols = global.GetApiInfoSlice(api, "colums")
global.CurRxp = global.GetApiInfoStr(api, "codeExp")
// 设置表头数据
titles := []fyne.CanvasObject{}
for i, item := range global.DefCols {
lt := ""
if i < len(global.CurCols) {
lt = global.CurCols[i]
}
lbl := widget.NewLabel(lt)
for _, w := range item {
cl := container.NewGridWrap(fyne.NewSize(float32(w), 35), lbl)
titles = append(titles, cl)
}
}
global.ListTitle = container.NewHBox(titles...)
// 列表信息
global.ListTable = widget.NewList(func() int {
return len(global.ListPerson)
}, func() fyne.CanvasObject {
titles := []fyne.CanvasObject{}
for _, item := range global.DefCols {
lbl := widget.NewLabel("")
lbl.Wrapping = fyne.TextWrapWord
for _, w := range item {
cl := container.NewGridWrap(fyne.NewSize(float32(w), 45), lbl)
titles = append(titles, cl)
}
}
vBox := container.NewHBox(titles...)
return vBox
// return widget.NewLabel("")
}, func(index widget.ListItemID, co fyne.CanvasObject) {
ListTabelItem(index, co)
},
)
// 分页组装处理
PaginatorCombing()
return container.NewBorder(global.ListTitle, global.PaginatorBox, nil, nil, global.ListTable)
}
// list刷新
func ListTabelRefresh(currPage, pageSize int) {
var err error
global.ListPerson, global.Total, err = model.GetDataList(pageSize, currPage)
if err != nil {
global.LabelMsgText(err.Error())
global.ErrorDialog(err.Error())
return
}
fmt.Println("ListTabelRefresh:", currPage, pageSize, global.Total, len(global.ListPerson))
global.Paginator = global.PaginatorFunc(currPage, pageSize, int64(global.Total))
if len(global.PagesBox.Objects) > 0 {
for r := global.Paginator.Totalpages; r < len(global.PagesBox.Objects) && r > 0; r++ {
global.PagesBox.Remove(global.PagesBox.Objects[r])
}
}
if global.PaginatorBox != nil {
if global.Paginator.Totalpages == 0 {
global.PaginatorBox.Hide()
} else {
global.PaginatorBox.Show()
}
}
garray.NewIntArrayFrom(global.Paginator.Pages).Iterator(func(k, v int) bool {
// 如果当前分页按钮数量小于5个,且k大于当前数量,则新增一个
if len(global.PagesBox.Objects) < 5 && k+1 > len(global.PagesBox.Objects) {
global.PagesBox.Add(widget.NewButton("1", nil))
}
if k > global.Paginator.Totalpages && global.Paginator.Totalpages < 5 {
for r := global.Paginator.Totalpages; r < 5; r++ {
global.PagesBox.Remove(global.PagesBox.Objects[r])
}
}
btn := global.PagesBox.Objects[k].(*widget.Button)
if v == currPage {
btn.SetText(fmt.Sprintf("*%d", v))
} else {
btn.SetText(fmt.Sprintf("%d", v))
}
btn.OnTapped = func() {
global.CurrPagedb.Set(v)
}
global.ListTable.Refresh()
fmt.Println("--", v)
return true
})
global.PaginatorLabel.SetText(fmt.Sprintf("%d/%d : %d", currPage, global.Paginator.Totalpages, global.Total))
}
// 列表的每项
func ListTabelItem(index widget.ListItemID, co fyne.CanvasObject) {
// VBox->GridWarp->Label
oneRow := co.(*fyne.Container)
for i, col := range global.CurCols {
label := oneRow.Objects[i].(*fyne.Container)
if reflect.TypeOf(label.Objects[0]) == reflect.TypeOf(widget.NewLabel("")) {
lb1 := label.Objects[0].(*widget.Label)
switch col {
case "姓名":
s := fmt.Sprintf("%d = %s", global.ListPerson[index].Id, global.ListPerson[index].Realname)
lb1.SetText(s)
case "身份证号":
lb1.SetText(global.ListPerson[index].Idcard)
case "手机号":
lb1.SetText(global.ListPerson[index].Mobile)
case "银行卡号":
lb1.SetText(global.ListPerson[index].Bankcard)
case "结果":
text := global.ListPerson[index].Result
if text == "1" {
text = "一致"
} else if text == "2" {
text = "不一致"
}
lb1.SetText(text)
case "描述":
lb1.SetText(global.ListPerson[index].Msg)
case "状态":
text := "未处理"
if global.ListPerson[index].Completed == 1 {
text = "已处理"
}
lb1.SetText(text)
case "任务Id":
lb1.SetText(global.ListPerson[index].Jobid)
default:
lb1.SetText("")
}
} else {
fmt.Println("===", reflect.TypeOf(label.Objects[0]))
}
}
}
// 分页数据项组装
func PaginatorCombing() {
currPage := 1
// 添加分页按钮,绑定当前值,并监听页面变更
global.CurrPagedb = binding.BindInt(&currPage)
global.PageSized = binding.BindInt(&global.PageSize)
global.CurrPagedb.AddListener(binding.NewDataListener(func() {
ListTabelRefresh(currPage, global.PageSize)
}))
// 处理分页
global.Paginator = global.PaginatorFunc(currPage, global.PageSize, int64(global.Total))
// 默认显示5页按钮
global.PagesBox = container.NewHBox()
box := 5
if box > global.Paginator.Totalpages {
box = global.Paginator.Totalpages
}
for i := 0; i < box; i++ {
global.PagesBox.Add(widget.NewButton("1", nil))
}
global.PaginatorLabel = widget.NewLabel(fmt.Sprintf("%d/%d : %d", currPage, global.Paginator.Totalpages, global.Total))
first := widget.NewButton("第一页", func() {
global.CurrPagedb.Set(1)
})
pre := widget.NewButton("上一页", func() {
global.CurrPagedb.Set(global.Paginator.Perpage)
})
next := widget.NewButton("下一页", func() {
global.CurrPagedb.Set(global.Paginator.Nextpage)
})
last := widget.NewButton("尾页", nil)
last.OnTapped = func() {
global.CurrPagedb.Set(global.Paginator.Totalpages)
}
// 每页数据选择
pageSizeSel := widget.NewSelect([]string{"10", "15", "20", "50", "100"}, nil)
pageSizeSel.OnChanged = func(pg string) {
// nums = 0
ps, _ := strconv.Atoi(pg)
global.PageSized.Set(ps)
ListTabelRefresh(currPage, ps)
}
pageSizeSel.SetSelected(strconv.Itoa(global.PageSize))
global.PaginatorBox = container.NewHBox(first, pre, global.PagesBox, next, last, global.PaginatorLabel, pageSizeSel)
if global.Paginator.Totalpages > 0 {
global.PaginatorBox.Show()
} else {
global.PaginatorBox.Hide()
}
}
注意:
- 1、应用第一次启动时候会在当前目录下初始化数据库文件
- 2、在切换接口前确保之前的数据已导出
- 3、请严格按照模板格式上传数据
- 4、一定要少量数据先测试、功能无误再大量测试,避免不必要的损失
- 5、该工具仅供交流学习使用,因此产生的任何损失概不负责
应用下载:本地批量核验工具