李锋镝的博客

  • 首页
  • 时间轴
  • 评论区显眼包🔥
  • 左邻右舍
  • 博友圈
  • 关于我
    • 关于我
    • 另一个网站
    • 我的导航站
    • 网站地图
    • 赞助
  • 留言
  • 🚇开往
Destiny
自是人生长恨水长东
  1. 首页
  2. 后端
  3. 正文

Wails 深度实战指南:用 Go + 前端技术打造轻量桌面应用(从入门到部署)

2025年11月10日 428点热度 0人点赞 0条评论

提起桌面应用开发,Electron 无疑是最流行的选择,但它“内存占用高、打包体积大”的痛点让很多开发者望而却步。有没有更轻量、更高效的替代方案?答案是 Wails——一个让你用 Go 写后端逻辑、用 HTML/CSS/JS 写前端界面的跨平台桌面应用框架。

Wails 完美结合了 Go 的高性能与前端的灵活交互,打包体积仅为 Electron 的 1/10,启动速度提升 3-5 倍,特别适合开发工具类、数据可视化、本地服务集成等场景。本文在原文基础上,补充详细的环境配置、多前端框架适配、进阶功能实现、性能优化和实战案例,帮你从“会用”到“精通”Wails 开发。

一、Wails 核心优势与适用场景

在开始实战前,先明确 Wails 的核心价值,判断是否匹配你的开发需求:

1. 核心优势(对比 Electron)

特性 Wails Electron
打包体积 轻量(简单应用 20-40MB) 庞大(基础应用 ≥ 150MB)
内存占用 低(启动后 ≈ 50MB) 高(启动后 ≈ 200MB+)
启动速度 快(1-3 秒) 慢(3-8 秒)
后端语言 Go(原生性能、丰富库生态) Node.js(异步IO强,CPU密集型弱)
跨平台支持 Windows 10+/macOS 10.15+/Linux(主流发行版) 全平台支持(兼容性更强)
原生能力集成 直接调用 Go 系统库,无需额外插件 需通过 Node.js 插件调用原生能力
开发成本 低(复用 Go + 前端现有技能) 低(纯前端技能栈)

2. 适用场景

  • 本地工具类应用(如文件转换、数据爬虫、日志分析);
  • 轻量级数据可视化工具(如报表展示、实时监控);
  • 本地服务集成(如与数据库、硬件设备、第三方 API 交互);
  • 对性能和体积敏感的桌面应用(如嵌入式设备应用)。

3. 不适用场景

  • 复杂多媒体应用(如视频编辑、游戏开发);
  • 对跨平台兼容性要求极高(如支持 Windows 7 及以下);
  • 重度依赖 Node.js 生态的应用(如大量使用 npm 原生模块)。

二、环境搭建:从依赖安装到项目初始化

1. 前置依赖安装

Wails 依赖 Go 后端和前端构建工具,需提前安装以下环境:

  • Go:1.20 及以上版本(推荐 1.22+);
  • 前端工具:Node.js(16+)+ npm(8+);
  • 版本控制:Git;
  • 系统依赖:
    • Windows:安装 GCC(通过 MinGW-w64 或 Chocolatey 安装);
    • macOS:安装 Xcode Command Line Tools(xcode-select --install);
    • Linux:安装依赖库(Ubuntu/Debian:sudo apt install libgtk-3-dev libwebkit2gtk-4.0-dev)。

验证依赖

打开终端执行以下命令,确保所有依赖安装成功:

# 验证 Go 版本
go version
# 验证 Node.js 和 npm 版本
node -v && npm -v
# 验证 Git
git --version

2. 安装 Wails CLI

通过 Go 命令安装 Wails 命令行工具:

go install github.com/wailsapp/wails/v2/cmd/wails@latest

配置环境变量(关键步骤)

确保 $GOPATH/bin 已加入系统环境变量:

  • Windows:set PATH=%PATH%;%GOPATH%\bin(或在系统属性中配置);
  • macOS/Linux:echo 'export PATH=$PATH:$GOPATH/bin' >> ~/.zshrc(生效:source ~/.zshrc)。

验证 Wails 安装

执行 wails doctor 检查系统是否满足开发要求,输出 SUCCESS Your system is ready for Wails development! 即成功。

3. 初始化第一个 Wails 项目

Wails 提供多种前端模板(Vue、React、Svelte、Lit 等),本文以最常用的 Vue 3 + TypeScript 为例:

步骤 1:创建项目

# 初始化 Vue + TS 项目(-n 项目名 -t 模板)
wails init -n wails-demo -t vue-ts

步骤 2:项目结构详解

进入项目目录 cd wails-demo,核心结构如下:

wails-demo/
├── app.go              # Go 后端主逻辑(核心文件)
├── main.go             # 程序入口(初始化应用、配置窗口)
├── go.mod/go.sum        # Go 依赖管理
├── wails.json           # Wails 全局配置(窗口、打包、前端构建)
├── frontend/            # 前端项目目录(Vue 3 + TS)
│   ├── src/             # 前端源码(组件、逻辑)
│   ├── public/          # 静态资源
│   ├── package.json     # 前端依赖
│   └── dist/            # 前端构建产物(Wails 自动生成)
└── build/               # 打包输出目录(最终可执行文件)

步骤 3:启动开发模式

wails dev
  • 首次启动会自动构建前端和后端,启动后弹出桌面窗口;
  • 支持热重载:修改前端代码实时生效,修改 Go 代码需重启服务。

三、核心实战:前后端联动与数据交互

Wails 的核心优势是“前端调用 Go 方法像调用本地函数一样简单”,本节详细讲解数据交互、类型转换、错误处理等核心功能。

1. 基础交互:前端调用 Go 方法

实现“输入姓名 → 调用 Go 方法 → 前端显示结果”的完整流程:

步骤 1:编写 Go 后端方法(app.go)

package main

import "context"

// App 结构体:Wails 会自动注册为后端服务
type App struct{}

// Hello:供前端调用的方法(ctx 为上下文,name 为前端传入参数)
// 注意:Go 方法返回值最多 2 个:第一个为正常结果,第二个为 error
func (a *App) Hello(ctx context.Context, name string) (string, error) {
    if name == "" {
        return "", fmt.Errorf("姓名不能为空")
    }
    return fmt.Sprintf("你好,%s!欢迎使用 Wails 开发桌面应用~", name), nil
}

步骤 2:前端调用 Go 方法(Vue 组件)

打开 frontend/src/views/Home.vue,修改代码:

<script setup lang="ts">
import { ref } from 'vue';
// 导入 Wails 自动生成的 Go 方法类型定义
import { Hello } from '../../wailsjs/go/main/App';

const name = ref('');
const result = ref('');
const error = ref('');

// 调用 Go 方法
const greet = async () => {
  error.value = '';
  try {
    // 像调用本地函数一样调用 Go 方法(返回 Promise)
    result.value = await Hello(name.value);
  } catch (err) {
    error.value = (err as Error).message;
  }
};
</script>

<template>
  <div class="greet-container">
    <input
      v-model="name"
      placeholder="请输入你的姓名"
      class="name-input"
    />
    <button @click="greet" class="greet-btn">打招呼</button>
    <p v-if="result" class="result">{{ result }}</p>
    <p v-if="error" class="error">{{ error }}</p>
  </div>
</template>

<style scoped>
/* 简单样式 */
.greet-container {
  padding: 20px;
}
.name-input {
  padding: 8px;
  width: 300px;
  margin-right: 10px;
}
.greet-btn {
  padding: 8px 16px;
  background: #42b983;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
.error {
  color: #ff4d4f;
  margin-top: 10px;
}
.result {
  margin-top: 10px;
  font-size: 16px;
}
</style>

步骤 3:更新方法绑定(关键)

新增或修改 Go 方法后,需重新生成前端类型定义文件:

# 生成/更新 wailsjs 绑定文件
wails generate module

或直接重启 wails dev,会自动触发绑定更新。

2. 复杂数据交互:结构体与类型转换

Wails 支持 Go 结构体与前端 JSON 数据的自动转换,需注意结构体字段必须添加 json 标签:

步骤 1:定义 Go 结构体与方法

package main

import (
  "context"
  "fmt"
)

// User:前端传入的结构体(必须有 json 标签)
type User struct {
  Name string `json:"name"`   // 姓名
  Age  uint8  `json:"age"`    // 年龄
  City string `json:"city"`   // 城市
}

// Profile:Go 返回给前端的结构体
type Profile struct {
  UserInfo User   `json:"userInfo"`
  Greeting string `json:"greeting"`
  Tips     string `json:"tips"`
}

// GetUserProfile:接收结构体参数,返回复杂结构体
func (a *App) GetUserProfile(ctx context.Context, user User) (Profile, error) {
  if user.Name == "" {
    return Profile{}, fmt.Errorf("姓名不能为空")
  }
  greeting := fmt.Sprintf("你好,%s!来自%s,今年%d岁~", user.Name, user.City, user.Age)
  tips := "Wails 支持结构体与 JSON 自动转换"
  return Profile{
    UserInfo: user,
    Greeting: greeting,
    Tips:     tips,
  }, nil
}

步骤 2:前端调用(传递 JSON 数据)

<script setup lang="ts">
import { ref } from 'vue';
import { GetUserProfile } from '../../wailsjs/go/main/App';

// 绑定表单数据(与 Go 结构体字段对应)
const userForm = ref({
  name: '',
  age: 18,
  city: ''
});
const profile = ref<{
  userInfo: { name: string; age: number; city: string };
  greeting: string;
  tips: string;
} | null>(null);
const error = ref('');

const getUserProfile = async () => {
  error.value = '';
  try {
    // 前端 JSON 自动转换为 Go 结构体
    profile.value = await GetUserProfile(userForm.value);
  } catch (err) {
    error.value = (err as Error).message;
  }
};
</script>

<template>
  <div class="profile-container">
    <div class="form-item">
      <label>姓名:</label>
      <input v-model="userForm.name" placeholder="请输入姓名" />
    </div>
    <div class="form-item">
      <label>年龄:</label>
      <input v-model.number="userForm.age" type="number" min="1" />
    </div>
    <div class="form-item">
      <label>城市:</label>
      <input v-model="userForm.city" placeholder="请输入城市" />
    </div>
    <button @click="getUserProfile" class="submit-btn">获取个人信息</button>
    <div v-if="profile" class="profile-result">
      <p>{{ profile.greeting }}</p>
      <p>提示:{{ profile.tips }}</p>
    </div>
    <p v-if="error" class="error">{{ error }}</p>
  </div>
</template>

3. 错误处理最佳实践

Wails 规定:Go 方法的第二个返回值必须是 error 类型,前端通过 catch 捕获。推荐错误处理方式:

  • 业务错误:返回具体错误信息(如“姓名不能为空”);
  • 系统错误:返回友好提示,详细日志在 Go 端打印;
  • 前端统一捕获错误,避免程序崩溃。

四、进阶功能:解锁 Wails 核心能力

1. 窗口配置与原生能力

通过 wails.json 配置窗口大小、标题、图标、无边框等属性:

{
  "name": "wails-demo",
  "outputfilename": "wails-demo",
  "frontend": {
    "path": "frontend/dist",
    "install": "npm install",
    "build": "npm run build",
    "dev": "npm run dev"
  },
  "window": {
    "title": "Wails 演示应用",
    "width": 800,
    "height": 600,
    "minWidth": 400,
    "minHeight": 300,
    "resizable": true,
    "fullscreen": false,
    "frameless": false, // 是否无边框
    "icon": "frontend/public/favicon.ico" // 窗口图标
  }
}

2. 文件操作(Go 后端实现)

Wails 可直接调用 Go 的 os 库操作本地文件,示例:读取文件内容并返回给前端:

package main

import (
  "context"
  "fmt"
  "os"
)

// ReadFile:读取本地文件内容
func (a *App) ReadFile(ctx context.Context, filePath string) (string, error) {
  // 检查文件是否存在
  if _, err := os.Stat(filePath); os.IsNotExist(err) {
    return "", fmt.Errorf("文件不存在:%s", filePath)
  }
  // 读取文件内容
  content, err := os.ReadFile(filePath)
  if err != nil {
    return "", fmt.Errorf("读取文件失败:%v", err)
  }
  return string(content), nil
}

前端调用:

<script setup lang="ts">
import { ref } from 'vue';
import { ReadFile } from '../../wailsjs/go/main/App';

const filePath = ref("C:/test.txt"); // 本地文件路径
const fileContent = ref("");
const error = ref("");

const readFile = async () => {
  try {
    fileContent.value = await ReadFile(filePath.value);
  } catch (err) {
    error.value = (err as Error).message;
  }
};
</script>

3. 系统通知与对话框

Wails 集成了系统原生对话框和通知功能,无需额外依赖:

package main

import (
  "context"
  "github.com/wailsapp/wails/v2/pkg/runtime"
)

// ShowNotification:显示系统通知
func (a *App) ShowNotification(ctx context.Context, title, content string) error {
  runtime.Notify(ctx, title, content)
  return nil
}

// ShowOpenFileDialog:打开文件选择对话框
func (a *App) ShowOpenFileDialog(ctx context.Context) (string, error) {
  // 配置文件选择对话框
  dialogOptions := runtime.OpenDialogOptions{
    Title: "选择文件",
    Filters: []runtime.FileFilter{
      {Name: "文本文件", Patterns: []string{"*.txt"}},
      {Name: "所有文件", Patterns: []string{"*"}},
    },
    SingleFile: true, // 单选文件
  }
  // 打开对话框并返回选中路径
  filePath, err := runtime.OpenFileDialog(ctx, dialogOptions)
  if err != nil {
    return "", err
  }
  return filePath, nil
}

4. 后台任务与进度反馈

长时间任务(如下载、数据处理)需在后台执行,并向前端反馈进度:

package main

import (
  "context"
  "fmt"
  "time"
)

// LongTask:后台长时间任务(模拟进度更新)
func (a *App) LongTask(ctx context.Context, total int) (string, error) {
  for i := 1; i <= total; i++ {
    // 向前端发送进度更新(通过 ctx 传递)
    runtime.EventsEmit(ctx, "task-progress", fmt.Sprintf("进度:%d/%d", i, total))
    time.Sleep(500 * time.Millisecond) // 模拟任务耗时
  }
  return "任务完成!", nil
}

前端监听进度事件:

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { LongTask } from '../../wailsjs/go/main/App';
import { on } from '../../wailsjs/runtime';

const total = ref(10);
const progress = ref("");
const result = ref("");

// 监听 Go 端发送的进度事件
onMounted(() => {
  on("task-progress", (data: string) => {
    progress.value = data;
  });
});

const startTask = async () => {
  progress.value = "";
  result.value = "";
  result.value = await LongTask(total.value);
};
</script>

五、多前端框架适配:Vue/React/Svelte

Wails 支持主流前端框架,以下是不同框架的初始化命令和注意事项:

1. Vue 3(推荐)

# Vue + JavaScript
wails init -n wails-vue -t vue
# Vue + TypeScript(推荐)
wails init -n wails-vue-ts -t vue-ts
  • 优势:生态丰富、文档完善、适合快速开发;
  • 注意:前端路由需使用 hash 模式(避免与 Wails 路由冲突)。

2. React

# React + JavaScript
wails init -n wails-react -t react
# React + TypeScript
wails init -n wails-react-ts -t react-ts
  • 优势:组件化强、适合复杂交互;
  • 注意:需手动配置 publicPath 为 ./(避免打包后资源路径错误)。

3. Svelte

wails init -n wails-svelte -t svelte
  • 优势:打包体积小、运行效率高;
  • 注意:适合简单应用,复杂场景生态不如 Vue/React。

4. 原生 HTML/CSS/JS

wails init -n wails-native -t vanilla
  • 优势:零依赖、启动最快;
  • 适用场景:极简工具类应用(如小工具、配置器)。

六、打包部署:跨平台分发

1. 基础打包命令

# 打包当前系统版本(默认生成到 build/bin 目录)
wails build
# 打包 Windows 版本(macOS/Linux 跨平台打包)
wails build -platform windows/amd64
# 打包 macOS 版本(Windows/Linux 跨平台打包)
wails build -platform darwin/amd64
# 打包 Linux 版本(Windows/macOS 跨平台打包)
wails build -platform linux/amd64

2. 打包优化

  • 压缩体积:使用 UPX 压缩可执行文件(需先安装 UPX):

    wails build -upx # 自动调用 UPX 压缩
  • 自定义图标:替换 frontend/public/favicon.ico,支持 ICO(Windows)、ICNS(macOS)格式;
  • 清理依赖:前端项目执行 npm prune --production 移除开发依赖,减少打包体积。

3. 分发格式

  • Windows:生成 .exe 文件,可通过 Inno Setup 打包为安装包;
  • macOS:生成 .app 应用,可打包为 .dmg 镜像;
  • Linux:生成可执行文件,可打包为 .deb(Debian/Ubuntu)或 .rpm(CentOS/Fedora)。

七、实战案例:本地文件转换工具

基于 Wails 开发一个“Markdown 转 HTML”工具,整合文件操作、格式转换、预览功能:

1. 功能需求

  • 选择本地 Markdown 文件;
  • 转换为 HTML 格式;
  • 实时预览转换结果;
  • 保存 HTML 文件到本地。

2. Go 后端核心代码(app.go)

package main

import (
  "context"
  "fmt"
  "os"
  "github.com/gomarkdown/markdown"
  "github.com/wailsapp/wails/v2/pkg/runtime"
)

// MarkdownToHTML:Markdown 转 HTML
func (a *App) MarkdownToHTML(ctx context.Context, mdContent string) (string, error) {
  if mdContent == "" {
    return "", fmt.Errorf("Markdown 内容不能为空")
  }
  // 使用 gomarkdown 库转换
  html := markdown.ToHTML([]byte(mdContent), nil, nil)
  return string(html), nil
}

// SelectMarkdownFile:选择 Markdown 文件
func (a *App) SelectMarkdownFile(ctx context.Context) (string, error) {
  dialogOpts := runtime.OpenDialogOptions{
    Title: "选择 Markdown 文件",
    Filters: []runtime.FileFilter{
      {Name: "Markdown 文件", Patterns: []string{"*.md"}},
    },
    SingleFile: true,
  }
  filePath, err := runtime.OpenFileDialog(ctx, dialogOpts)
  if err != nil {
    return "", err
  }
  // 读取文件内容
  content, err := os.ReadFile(filePath)
  if err != nil {
    return "", fmt.Errorf("读取文件失败:%v", err)
  }
  return string(content), nil
}

// SaveHTMLFile:保存 HTML 文件
func (a *App) SaveHTMLFile(ctx context.Context, htmlContent string) error {
  dialogOpts := runtime.SaveDialogOptions{
    Title: "保存 HTML 文件",
    Filters: []runtime.FileFilter{
      {Name: "HTML 文件", Patterns: []string{"*.html"}},
    },
  }
  filePath, err := runtime.SaveFileDialog(ctx, dialogOpts)
  if err != nil {
    return err
  }
  // 写入文件
  return os.WriteFile(filePath, []byte(htmlContent), 0644)
}

3. Vue 前端核心代码

<script setup lang="ts">
import { ref } from 'vue';
import { MarkdownToHTML, SelectMarkdownFile, SaveHTMLFile } from '../../wailsjs/go/main/App';

const mdContent = ref("");
const htmlContent = ref("");
const error = ref("");

// 选择 Markdown 文件
const selectFile = async () => {
  try {
    mdContent.value = await SelectMarkdownFile();
    // 自动转换
    htmlContent.value = await MarkdownToHTML(mdContent.value);
  } catch (err) {
    error.value = (err as Error).message;
  }
};

// 手动转换
const convert = async () => {
  try {
    htmlContent.value = await MarkdownToHTML(mdContent.value);
  } catch (err) {
    error.value = (err as Error).message;
  }
};

// 保存 HTML 文件
const saveFile = async () => {
  try {
    await SaveHTMLFile(htmlContent.value);
    alert("保存成功!");
  } catch (err) {
    error.value = (err as Error).message;
  }
};
</script>

<template>
  <div class="app-container">
    <div class="btn-group">
      <button @click="selectFile" class="btn">选择 Markdown 文件</button>
      <button @click="convert" class="btn">转换为 HTML</button>
      <button @click="saveFile" class="btn">保存 HTML 文件</button>
    </div>
    <div class="content-container">
      <div class="md-editor">
        <textarea v-model="mdContent" placeholder="请输入 Markdown 内容..."></textarea>
      </div>
      <div class="html-preview">
        <h3>预览结果:</h3>
        <div v-html="htmlContent" class="preview-content"></div>
      </div>
    </div>
    <p v-if="error" class="error">{{ error }}</p>
  </div>
</template>

4. 运行与打包

# 开发模式启动
wails dev
# 打包为可执行文件
wails build -upx

八、常见问题与避坑指南

1. 前端无法调用 Go 方法

  • 检查 Go 方法是否为公共方法(首字母大写);
  • 执行 wails generate module 更新绑定文件;
  • 确保 Go 方法参数和返回值类型支持转换(如不支持接口、匿名结构体)。

2. 打包后资源路径错误

  • 前端 publicPath 配置为 ./(Vue 在 vue.config.js,React 在 package.json);
  • 静态资源(图标、图片)放在 frontend/public 目录。

3. 跨平台打包失败

  • Windows 打包需安装 MinGW-w64;
  • macOS 打包需安装 Xcode Command Line Tools;
  • Linux 打包需安装 libgtk-3-dev 等依赖库。

4. 性能优化建议

  • 避免频繁前后端通信(批量处理数据);
  • 前端减少 DOM 操作(使用虚拟列表处理大量数据);
  • Go 端使用协程处理并发任务(避免阻塞主线程)。

九、总结:Wails 开发的核心价值

Wails 打破了“前端开发桌面应用必须用 Electron”的固有认知,让 Go 开发者也能快速打造轻量、高性能的桌面应用。其核心价值在于“复用现有技能栈、平衡性能与开发效率”——用 Go 处理后端逻辑(文件、网络、系统交互),用前端技术打造友好界面,无需学习新语言即可上手。

如果你需要开发工具类、数据可视化、本地服务集成等场景的应用,Wails 绝对是比 Electron 更优的选择。随着 Wails 生态的不断完善,它在桌面应用开发领域的竞争力会持续提升。

除非注明,否则均为李锋镝的博客原创文章,转载必须以链接形式标明本文链接

本文链接:https://www.lifengdi.com/hou-duan/4566

相关文章

  • 配置Jackson使用字段而不是getter/setter来序列化和反序列化
  • org.apache.ibatis.plugin.Interceptor类详细介绍及使用
  • JDK25模块级导入深度解析:Java导入机制的革命性进化
  • 深度解析 Disruptor:无锁队列的高性能实现与实践
  • 精通Linux根目录:核心文件夹深度解析与实战指南
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
标签: Electron Golang Wails
最后更新:2025年11月10日

李锋镝

既然选择了远方,便只顾风雨兼程。

打赏 点赞
< 上一篇
下一篇 >

文章评论

1 2 3 4 5 6 7 8 9 11 12 13 14 15 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 46 47 48 49 50 51 52 53 54 55 57 58 60 61 62 63 64 65 66 67 69 72 74 76 77 78 79 80 81 82 85 86 87 90 92 93 94 95 96 97 98 99
取消回复

我是人间惆怅客,知君何事泪纵横,断肠声里忆平生。

那年今日(04月14日)

  • 2010年:中国青海玉树大地震
  • 1894年:托马斯·爱迪生展示了其新发明活动电影放映机
  • 1629年:荷兰物理学家克里斯蒂安·惠更斯出生
  • 1578年:西班牙国王腓力三世出生
  • 605年:隋炀帝下令开凿大运河
  • 更多历史事件
最新 热点 随机
最新 热点 随机
Everything Claude Code 详细使用文档 配置Jackson使用字段而不是getter/setter来序列化和反序列化 这个域名注册整整十年了,十年时间,真快啊 Claude Code全维度实战指南:从入门到精通,解锁AI编程新范式 Apollo配置中心中的protalDB的作用是什么 org.apache.ibatis.plugin.Interceptor类详细介绍及使用
AI时代,个人技术博客的出路在哪里?使用WireGuard在Ubuntu 24.04系统搭建VPN这个域名注册整整十年了,十年时间,真快啊WordPress实现用户评论等级排行榜插件WordPress网站换了个字体,差点儿把样式换崩了做了一个WordPress文章热力图插件
开发者必懂的 AI 向量入门:从数学基础到实战应用 分代ZGC这么牛?底层原理是什么? 图解 | 原来这就是网络 使用springboot结合AI生成视频 Java枚举梳理总结一 Excel2016右键新建工作表,打开时提示“因为文件格式或文件扩展名无效。请确定文件未损坏,并且文件扩展名与文件的格式匹配。”的解决办法
标签聚合
设计模式 ElasticSearch docker 多线程 SpringBoot JAVA AI 分布式 MySQL JVM Spring SQL 架构 K8s IDEA WordPress 数据库 AI编程 Redis 日常
友情链接
  • Blogs·CN
  • Honesty
  • Mr.Sun的博客
  • 临窗旋墨
  • 哥斯拉
  • 彬红茶日记
  • 志文工作室
  • 懋和道人
  • 拾趣博客导航
  • 搬砖日记
  • 旧时繁华
  • 林羽凡
  • 瓦匠个人小站
  • 皮皮社
  • 知向前端
  • 蜗牛工作室
  • 韩小韩博客
  • 风渡言

COPYRIGHT © 2026 lifengdi.com. ALL RIGHTS RESERVED.

域名年龄

Theme Kratos Made By Dylan

津ICP备2024022503号-3

京公网安备11011502039375号