Wails2项目创建 Go 后端 app.go与Vue3 前端交互示例、保存文件、导出数据、选择并读取文件、选择文件夹、消息对话框
我能为你提供什么服务?
网站建设 | 小程序开发 | 软件定制
我是鹏魔王,一个做网站、小程序的程序员,记录生活日常、及技术分享。
本欲起身离红尘,奈何影子落人间,欢迎关注
-
保存文本文件 -
导出csv数据 -
选择并读取文件 -
选择文件夹 -
消息对话框


donwload_demo/├── app.go # Go 后端 - 5 个演示方法├── main.go # Wails 应用入口├── wails.json # Wails 配置├── go.mod / go.sum # Go 依赖└── frontend/├── src/│ ├── App.vue # 前端界面 - 5 个卡片按钮 + 结果面板│ ├── main.js│ └── style.css├── wailsjs/ # Wails JS 绑定├── package.json└── vite.config.js
package mainimport ("context""fmt""os""path/filepath"wruntime "github.com/wailsapp/wails/v2/pkg/runtime")type App struct {ctx context.Context}funcNewApp() *App {return &App{}}func(a *App) startup(ctx context.Context) {a.ctx = ctx}// SaveTextFile 演示:使用原生保存对话框保存文本文件func(a *App) SaveTextFile() (string, error) {if a.ctx == nil {return "", fmt.Errorf("context not initialized")}options := wruntime.SaveDialogOptions{Title: "保存文本文件",DefaultFilename: "demo.txt",Filters: []wruntime.FileFilter{{DisplayName: "文本文件 (*.txt)", Pattern: "*.txt"},{DisplayName: "所有文件 (*.*)", Pattern: "*.*"},},}filePath, err := wruntime.SaveFileDialog(a.ctx, options)if err != nil {return "", fmt.Errorf("打开保存对话框失败: %v", err)}if filePath == "" {return "", fmt.Errorf("用户取消了保存")}content := "Hello, World!\n这是通过 Wails SaveFileDialog 保存的文件。\n"content += "保存时间: 2026-05-06\n"content += "这是一个演示示例。\n"if err := os.WriteFile(filePath, []byte(content), 0644); err != nil {return "", fmt.Errorf("保存文件失败: %v", err)}return filePath, nil}// SaveExcelDemo 演示:保存一个简单的 CSV 文件(模拟 Excel 导出)func(a *App) SaveExcelDemo() (string, error) {if a.ctx == nil {return "", fmt.Errorf("context not initialized")}options := wruntime.SaveDialogOptions{Title: "导出数据",DefaultFilename: "导出数据.csv",Filters: []wruntime.FileFilter{{DisplayName: "CSV 文件 (*.csv)", Pattern: "*.csv"},},}filePath, err := wruntime.SaveFileDialog(a.ctx, options)if err != nil {return "", fmt.Errorf("打开保存对话框失败: %v", err)}if filePath == "" {return "", fmt.Errorf("用户取消了保存")}csvContent := "ID,名称,价格,状态\n"csvContent += "1,商品A,99.00,上架\n"csvContent += "2,商品B,199.00,上架\n"csvContent += "3,商品C,299.00,下架\n"csvContent += "4,商品D,499.00,上架\n"csvContent += "5,商品E,999.00,上架\n"if err := os.WriteFile(filePath, []byte(csvContent), 0644); err != nil {return "", fmt.Errorf("保存文件失败: %v", err)}return filePath, nil}// SelectAndReadFile 演示:使用原生打开对话框选择并读取文件func(a *App) SelectAndReadFile() (string, error) {if a.ctx == nil {return "", fmt.Errorf("context not initialized")}options := wruntime.OpenDialogOptions{Title: "选择文件",Filters: []wruntime.FileFilter{{DisplayName: "文本文件 (*.txt)", Pattern: "*.txt"},{DisplayName: "所有文件 (*.*)", Pattern: "*.*"},},}filePath, err := wruntime.OpenFileDialog(a.ctx, options)if err != nil {return "", fmt.Errorf("打开文件对话框失败: %v", err)}if filePath == "" {return "", fmt.Errorf("用户取消了选择")}data, err := os.ReadFile(filePath)if err != nil {return "", fmt.Errorf("读取文件失败: %v", err)}return fmt.Sprintf("文件: %s\n大小: %d 字节\n\n内容:\n%s", filepath.Base(filePath), len(data), string(data)), nil}// SelectDirectory 演示:选择文件夹func(a *App) SelectDirectory() (string, error) {if a.ctx == nil {return "", fmt.Errorf("context not initialized")}options := wruntime.OpenDialogOptions{Title: "选择文件夹",}dirPath, err := wruntime.OpenDirectoryDialog(a.ctx, options)if err != nil {return "", fmt.Errorf("选择文件夹失败: %v", err)}if dirPath == "" {return "", fmt.Errorf("用户取消了选择")}entries, err := os.ReadDir(dirPath)if err != nil {return "", fmt.Errorf("读取文件夹失败: %v", err)}result := fmt.Sprintf("文件夹: %s\n文件数: %d\n\n内容:\n", dirPath, len(entries))for _, e := range entries {t := "文件"if e.IsDir() {t = "目录"}result += fmt.Sprintf(" [%s] %s\n", t, e.Name())}return result, nil}// MessageDialog 演示:消息对话框func(a *App) MessageDialog() (string, error) {if a.ctx == nil {return "", fmt.Errorf("context not initialized")}result, err := wruntime.MessageDialog(a.ctx, wruntime.MessageDialogOptions{Title: "提示",Message: "这是一个消息对话框示例。\n对话框是 Wails Runtime 原生组件。",Type: wruntime.QuestionDialog,Buttons: []string{"确定", "取消", "帮助"},})if err != nil {return "", err}return fmt.Sprintf("你点击了: %q", result), nil}
App.vue文件
<template><divclass="container"><header><h1>Wails 文件操作示例</h1><p>展示 Wails Runtime 原生对话框的使用方法</p></header><divclass="card-grid"><divclass="card" @click="saveTextFile"><divclass="icon">📝</div><h3>保存文本文件</h3><p>弹出系统保存对话框,将文本保存到指定位置</p><spanclass="tag">SaveFileDialog</span></div><divclass="card" @click="saveExcelDemo"><divclass="icon">📊</div><h3>导出 CSV 数据</h3><p>模拟导出数据,弹出系统保存对话框保存 CSV</p><spanclass="tag">SaveFileDialog</span></div><divclass="card" @click="selectAndReadFile"><divclass="icon">📂</div><h3>选择并读取文件</h3><p>弹出打开对话框,选择文件后读取并显示内容</p><spanclass="tag">OpenFileDialog</span></div><divclass="card" @click="selectDirectory"><divclass="icon">📁</div><h3>选择文件夹</h3><p>弹出文件夹选择对话框,列出文件夹内容</p><spanclass="tag">OpenDirectoryDialog</span></div><divclass="card" @click="messageDialog"><divclass="icon">💬</div><h3>消息对话框</h3><p>显示原生消息对话框,演示用户交互</p><spanclass="tag">MessageDialog</span></div></div><divv-if="resultText"class="result-panel"><divclass="result-header"><span>执行结果</span><buttonclass="clear-btn" @click="resultText = ''">清除</button></div><preclass="result-content">{{ resultText }}</pre></div></div></template><scriptsetup>import { ref } from 'vue'const resultText = ref('')const saveTextFile = async () => {try {const filePath = await window.go.main.App.SaveTextFile()resultText.value = `✓ 文件保存成功!\n路径: ${filePath}`} catch (err) {resultText.value = `✗ ${err.message || err}`}}const saveExcelDemo = async () => {try {const filePath = await window.go.main.App.SaveExcelDemo()resultText.value = `✓ CSV 导出成功!\n路径: ${filePath}`} catch (err) {resultText.value = `✗ ${err.message || err}`}}const selectAndReadFile = async () => {try {const content = await window.go.main.App.SelectAndReadFile()resultText.value = content} catch (err) {resultText.value = `✗ ${err.message || err}`}}const selectDirectory = async () => {try {const content = await window.go.main.App.SelectDirectory()resultText.value = content} catch (err) {resultText.value = `✗ ${err.message || err}`}}const messageDialog = async () => {try {const result = await window.go.main.App.MessageDialog()resultText.value = `✓ 对话框结果:\n${result}`} catch (err) {resultText.value = `✗ ${err.message || err}`}}</script><stylescoped>.container {max-width: 800px;margin: 0 auto;padding: 40px 20px;}header {text-align: center;margin-bottom: 40px;}header h1 {font-size: 28px;font-weight: 700;margin-bottom: 8px;background: linear-gradient(135deg, #667eea, #764ba2);-webkit-background-clip: text;-webkit-text-fill-color: transparent;}header p {color: #6b7280;font-size: 14px;}.card-grid {display: grid;grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));gap: 16px;margin-bottom: 24px;}.card {background: white;border-radius: 12px;padding: 24px;cursor: pointer;transition: all 0.2s;border: 1px solid #e5e7eb;}.card:hover {transform: translateY(-2px);box-shadow: 0 8px 24px rgba(0,0,0,0.1);border-color: #667eea;}.icon {font-size: 36px;margin-bottom: 12px;}.card h3 {font-size: 16px;font-weight: 600;margin-bottom: 8px;color: #1f2937;}.card p {font-size: 13px;color: #6b7280;line-height: 1.5;margin-bottom: 12px;}.tag {display: inline-block;font-size: 11px;padding: 2px 8px;border-radius: 4px;background: #ede9fe;color: #6d28d9;font-weight: 500;}.result-panel {background: white;border-radius: 12px;border: 1px solid #e5e7eb;overflow: hidden;}.result-header {display: flex;justify-content: space-between;align-items: center;padding: 12px 16px;background: #f9fafb;border-bottom: 1px solid #e5e7eb;font-size: 14px;font-weight: 500;}.clear-btn {font-size: 12px;padding: 4px 12px;background: transparent;border: 1px solid #d1d5db;border-radius: 4px;cursor: pointer;color: #6b7280;}.clear-btn:hover {background: #f3f4f6;}.result-content {padding: 16px;font-family: monospace;font-size: 13px;line-height: 1.6;color: #374151;white-space: pre-wrap;word-break: break-all;max-height: 300px;overflow-y: auto;}</style>
夜雨聆风