新增应用打包模块
This commit is contained in:
244
PACK_GUIDE.md
Normal file
244
PACK_GUIDE.md
Normal file
@@ -0,0 +1,244 @@
|
||||
# 网站打包成应用指南
|
||||
|
||||
## 功能说明
|
||||
|
||||
本工具可以将静态网站打包成独立的桌面应用程序,无需用户安装浏览器或其他运行环境。
|
||||
|
||||
## 打包原理
|
||||
|
||||
1. **创建 Wails 项目**:自动生成一个新的 Wails3 项目结构
|
||||
2. **嵌入网站文件**:将静态网站文件(HTML、CSS、JS等)嵌入到应用中
|
||||
3. **编译应用**:使用 Wails3 编译生成桌面应用
|
||||
4. **独立运行**:打包后的应用内置 WebView,无需浏览器
|
||||
|
||||
## 环境要求
|
||||
|
||||
### 必需环境
|
||||
|
||||
- **Go 1.21+**:Go 编程语言环境
|
||||
- **Wails3**:桌面应用框架
|
||||
|
||||
### 安装方式
|
||||
|
||||
#### 方式一:使用本工具安装(推荐)
|
||||
|
||||
1. 进入"应用打包"页面
|
||||
2. 查看环境检查卡片
|
||||
3. 点击"安装 Go"和"安装 Wails3"按钮
|
||||
4. 等待安装完成
|
||||
|
||||
#### 方式二:手动安装
|
||||
|
||||
**安装 Go:**
|
||||
```powershell
|
||||
# 下载 Go 安装包
|
||||
# https://go.dev/dl/
|
||||
|
||||
# 或使用工具提供的脚本
|
||||
.\install\go.bat
|
||||
```
|
||||
|
||||
**安装 Wails3:**
|
||||
```powershell
|
||||
# 确保已安装 Go
|
||||
go install github.com/wailsapp/wails/v3/cmd/wails3@latest
|
||||
|
||||
# 或使用工具提供的脚本
|
||||
.\install\wails.bat
|
||||
```
|
||||
|
||||
## 使用步骤
|
||||
|
||||
### 1. 准备网站文件
|
||||
|
||||
确保您的网站文件夹包含:
|
||||
- `index.html`(必需,作为应用入口)
|
||||
- CSS 文件
|
||||
- JavaScript 文件
|
||||
- 图片、字体等资源文件
|
||||
|
||||
示例结构:
|
||||
```
|
||||
my-website/
|
||||
├── index.html
|
||||
├── css/
|
||||
│ └── style.css
|
||||
├── js/
|
||||
│ └── app.js
|
||||
└── images/
|
||||
└── logo.png
|
||||
```
|
||||
|
||||
### 2. 选择网站
|
||||
|
||||
- 点击"选择文件夹"按钮
|
||||
- 选择包含网站文件的文件夹
|
||||
- 或从已下载的网站列表中选择
|
||||
|
||||
### 3. 配置应用信息
|
||||
|
||||
填写应用基本信息:
|
||||
- **应用名称**(必填):应用的显示名称
|
||||
- **版本号**:如 1.0.0
|
||||
- **作者**:您的名字或团队名称
|
||||
- **应用描述**:简短描述应用功能
|
||||
|
||||
### 4. 打包设置
|
||||
|
||||
配置打包参数:
|
||||
- **目标平台**:选择 Windows、macOS 或 Linux
|
||||
- **窗口宽度**:默认 1280px
|
||||
- **窗口高度**:默认 800px
|
||||
- **输出目录**:可自定义,默认为 `site-dist`
|
||||
|
||||
### 5. 开始打包
|
||||
|
||||
- 确认所有配置正确
|
||||
- 点击"开始打包"按钮
|
||||
- 等待编译完成(首次可能需要5-10分钟)
|
||||
|
||||
## 打包输出
|
||||
|
||||
### 文件位置
|
||||
|
||||
默认输出到项目的 `site-dist` 目录,包含:
|
||||
- Windows:`your-app.exe`
|
||||
- macOS:`your-app.app`
|
||||
- Linux:`your-app`(可执行文件)
|
||||
|
||||
### 文件大小
|
||||
|
||||
打包后的应用大小取决于:
|
||||
- 网站文件大小
|
||||
- 平台差异
|
||||
- 通常在 20-50MB 之间
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 首次打包
|
||||
|
||||
- 需要下载 Go 依赖包
|
||||
- 可能耗时 5-10 分钟
|
||||
- 需要网络连接
|
||||
- 建议有至少 500MB 可用磁盘空间
|
||||
|
||||
### 网站要求
|
||||
|
||||
- **纯静态网站**:支持 HTML、CSS、JavaScript
|
||||
- **不支持服务端代码**:如 PHP、Python 等
|
||||
- **相对路径**:建议使用相对路径引用资源
|
||||
- **入口文件**:必须有 index.html
|
||||
|
||||
### 资源引用
|
||||
|
||||
在 HTML 中使用相对路径:
|
||||
```html
|
||||
<!-- 推荐 -->
|
||||
<link rel="stylesheet" href="./css/style.css">
|
||||
<script src="./js/app.js"></script>
|
||||
<img src="./images/logo.png">
|
||||
|
||||
<!-- 不推荐 -->
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<script src="http://example.com/js/app.js"></script>
|
||||
```
|
||||
|
||||
### 跨域问题
|
||||
|
||||
打包后的应用使用 `file://` 协议,可能遇到跨域限制:
|
||||
- 避免使用外部 API(CORS 限制)
|
||||
- 避免使用绝对路径
|
||||
- 将所有资源放在网站文件夹内
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 打包失败怎么办?
|
||||
|
||||
A: 检查以下几点:
|
||||
1. 确保 Go 和 Wails3 已正确安装
|
||||
2. 检查网络连接(首次打包需要下载依赖)
|
||||
3. 确保磁盘空间充足
|
||||
4. 查看错误信息,根据提示解决
|
||||
|
||||
### Q: 打包的应用很大?
|
||||
|
||||
A: 这是正常的,因为:
|
||||
1. 包含了 WebView 运行时
|
||||
2. 包含了 Go 运行时
|
||||
3. 包含了所有网站文件
|
||||
4. 可以通过压缩网站资源减小体积
|
||||
|
||||
### Q: 应用可以在其他电脑运行吗?
|
||||
|
||||
A: 可以!打包后的应用是独立的:
|
||||
- Windows 应用可在任何 Windows 7+ 系统运行
|
||||
- macOS 应用可在任何 macOS 10.13+ 系统运行
|
||||
- Linux 应用可在大多数 Linux 发行版运行
|
||||
- 无需安装 Go、Wails 或浏览器
|
||||
|
||||
### Q: 如何更新应用?
|
||||
|
||||
A: 重新打包即可:
|
||||
1. 修改网站文件
|
||||
2. 更改版本号
|
||||
3. 重新执行打包流程
|
||||
4. 分发新的可执行文件
|
||||
|
||||
### Q: 可以添加自定义图标吗?
|
||||
|
||||
A: 当前版本暂不支持,后续版本会添加此功能。
|
||||
|
||||
## 高级用法
|
||||
|
||||
### 自定义配置
|
||||
|
||||
编辑生成的项目:
|
||||
1. 打包时会在临时目录创建项目
|
||||
2. 可以在 `main.go` 中自定义窗口行为
|
||||
3. 可以添加 Go 后端代码
|
||||
|
||||
### 批量打包
|
||||
|
||||
使用命令行方式:
|
||||
```powershell
|
||||
# 切换到临时项目目录
|
||||
cd C:\Users\{user}\AppData\Local\Temp\wails-pack-{app-name}
|
||||
|
||||
# 执行构建
|
||||
wails3 build
|
||||
```
|
||||
|
||||
## 技术架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ 桌面应用 (.exe) │
|
||||
├─────────────────────────────────────┤
|
||||
│ Wails3 框架 │
|
||||
│ ├─ WebView (渲染引擎) │
|
||||
│ ├─ Go 运行时 │
|
||||
│ └─ 嵌入的网站文件 │
|
||||
│ ├─ HTML │
|
||||
│ ├─ CSS │
|
||||
│ ├─ JavaScript │
|
||||
│ └─ 其他资源 │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 支持的平台
|
||||
|
||||
| 平台 | 架构 | 输出格式 | 备注 |
|
||||
|------|------|---------|------|
|
||||
| Windows | x64 | .exe | Windows 7+ |
|
||||
| macOS | x64/arm64 | .app | macOS 10.13+ |
|
||||
| Linux | x64 | 可执行文件 | 主流发行版 |
|
||||
|
||||
## 更多信息
|
||||
|
||||
- Wails 官方文档:https://wails.io
|
||||
- Go 官方网站:https://go.dev
|
||||
- 项目 GitHub:待添加
|
||||
|
||||
## 许可证
|
||||
|
||||
本工具遵循 MIT 许可证。
|
||||
120
app.go
120
app.go
@@ -556,16 +556,35 @@ func (a *App) PackApp(packConfig map[string]interface{}) error {
|
||||
// 获取配置参数
|
||||
sitePath, _ := packConfig["sitePath"].(string)
|
||||
appName, _ := packConfig["appName"].(string)
|
||||
version, _ := packConfig["version"].(string)
|
||||
author, _ := packConfig["author"].(string)
|
||||
description, _ := packConfig["description"].(string)
|
||||
outputDir, _ := packConfig["outputDir"].(string)
|
||||
width, _ := packConfig["width"].(float64)
|
||||
height, _ := packConfig["height"].(float64)
|
||||
|
||||
// 平台参数处理
|
||||
platformsInterface, _ := packConfig["platforms"].([]interface{})
|
||||
platforms := make([]string, 0)
|
||||
for _, p := range platformsInterface {
|
||||
if platform, ok := p.(string); ok {
|
||||
platforms = append(platforms, platform)
|
||||
}
|
||||
}
|
||||
|
||||
if sitePath == "" || appName == "" {
|
||||
return fmt.Errorf("缺少必要参数")
|
||||
return fmt.Errorf("缺少必要参数: 网站路径和应用名称不能为空")
|
||||
}
|
||||
|
||||
// 检查网站路径是否存在
|
||||
if _, err := os.Stat(sitePath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("网站路径不存在: %s", sitePath)
|
||||
}
|
||||
|
||||
// 发送打包进度
|
||||
a.app.Event.Emit("pack:progress", map[string]interface{}{
|
||||
"step": "preparing",
|
||||
"percent": 10,
|
||||
"percent": 5,
|
||||
"message": "正在准备打包环境...",
|
||||
})
|
||||
|
||||
@@ -576,15 +595,25 @@ func (a *App) PackApp(packConfig map[string]interface{}) error {
|
||||
}
|
||||
|
||||
if !envStatus.HasGo || !envStatus.HasWails {
|
||||
return fmt.Errorf("缺少必要的环境: Go=%v, Wails=%v", envStatus.HasGo, envStatus.HasWails)
|
||||
return fmt.Errorf("缺少必要的环境: Go=%v, Wails=%v。请先安装开发环境", envStatus.HasGo, envStatus.HasWails)
|
||||
}
|
||||
|
||||
// 创建临时项目目录
|
||||
a.app.Event.Emit("pack:progress", map[string]interface{}{
|
||||
"step": "creating",
|
||||
"percent": 30,
|
||||
"message": "正在创建项目结构...",
|
||||
})
|
||||
// 设置默认值
|
||||
if version == "" {
|
||||
version = "1.0.0"
|
||||
}
|
||||
if description == "" {
|
||||
description = appName
|
||||
}
|
||||
if len(platforms) == 0 {
|
||||
platforms = []string{"windows"}
|
||||
}
|
||||
if width == 0 {
|
||||
width = 1280
|
||||
}
|
||||
if height == 0 {
|
||||
height = 800
|
||||
}
|
||||
|
||||
// 设置输出目录
|
||||
if outputDir == "" {
|
||||
@@ -595,8 +624,12 @@ func (a *App) PackApp(packConfig map[string]interface{}) error {
|
||||
}
|
||||
}
|
||||
|
||||
// 确保输出目录存在
|
||||
os.MkdirAll(outputDir, 0755)
|
||||
// 创建临时项目目录
|
||||
a.app.Event.Emit("pack:progress", map[string]interface{}{
|
||||
"step": "creating",
|
||||
"percent": 15,
|
||||
"message": "正在创建Wails项目结构...",
|
||||
})
|
||||
|
||||
tempDir := filepath.Join(os.TempDir(), "wails-pack-"+appName)
|
||||
os.RemoveAll(tempDir) // 清理旧的
|
||||
@@ -605,48 +638,63 @@ func (a *App) PackApp(packConfig map[string]interface{}) error {
|
||||
return fmt.Errorf("创建临时目录失败: %v", err)
|
||||
}
|
||||
|
||||
// TODO: 创建Wails项目结构,复制网站文件等
|
||||
// 这里是完整打包逻辑的占位符
|
||||
// 实际需要:
|
||||
// 1. 创建wails项目结构 (go.mod, main.go等)
|
||||
// 2. 复制网站文件到项目的assets目录
|
||||
// 3. 配置wails项目参数
|
||||
// 使用打包服务创建项目
|
||||
packService := &services.PackService{}
|
||||
config := services.PackConfig{
|
||||
SitePath: sitePath,
|
||||
AppName: appName,
|
||||
Version: version,
|
||||
Author: author,
|
||||
Description: description,
|
||||
Platforms: platforms,
|
||||
Width: int(width),
|
||||
Height: int(height),
|
||||
OutputDir: outputDir,
|
||||
}
|
||||
|
||||
log.Printf("准备打包网站: %s 到应用: %s", sitePath, appName)
|
||||
log.Printf("开始打包网站: %s", sitePath)
|
||||
log.Printf("应用名称: %s", appName)
|
||||
log.Printf("临时目录: %s", tempDir)
|
||||
log.Printf("输出目录: %s", outputDir)
|
||||
|
||||
a.app.Event.Emit("pack:progress", map[string]interface{}{
|
||||
"step": "building",
|
||||
"percent": 60,
|
||||
"message": "正在编译应用...",
|
||||
"step": "copying",
|
||||
"percent": 30,
|
||||
"message": "正在复制网站文件...",
|
||||
})
|
||||
|
||||
// 使用Wails构建
|
||||
wailsCmd := envStatus.WailsPath
|
||||
if wailsCmd == "" {
|
||||
wailsCmd = "wails3"
|
||||
// 创建Wails项目结构
|
||||
if err := packService.CreateWailsProject(config, tempDir); err != nil {
|
||||
a.app.Event.Emit("pack:progress", map[string]interface{}{
|
||||
"step": "error",
|
||||
"percent": 0,
|
||||
"error": fmt.Sprintf("创建项目失败: %v", err),
|
||||
})
|
||||
return fmt.Errorf("创建Wails项目失败: %v", err)
|
||||
}
|
||||
|
||||
// 模拟构建过程(实际项目中这里应该真正调用wails build)
|
||||
// cmd := exec.Command(wailsCmd, "build", "-o", filepath.Join(outputDir, appName+".exe"))
|
||||
// cmd.Dir = tempDir
|
||||
|
||||
// 暂时模拟成功
|
||||
a.app.Event.Emit("pack:progress", map[string]interface{}{
|
||||
"step": "packaging",
|
||||
"percent": 80,
|
||||
"message": "正在生成安装包...",
|
||||
"step": "building",
|
||||
"percent": 50,
|
||||
"message": "正在编译应用(这可能需要几分钟)...",
|
||||
})
|
||||
|
||||
// 模拟延迟
|
||||
// time.Sleep(2 * time.Second)
|
||||
// 构建应用
|
||||
if err := packService.BuildWailsApp(tempDir, outputDir, platforms); err != nil {
|
||||
a.app.Event.Emit("pack:progress", map[string]interface{}{
|
||||
"step": "error",
|
||||
"percent": 0,
|
||||
"error": fmt.Sprintf("构建失败: %v", err),
|
||||
})
|
||||
return fmt.Errorf("构建应用失败: %v", err)
|
||||
}
|
||||
|
||||
a.app.Event.Emit("pack:progress", map[string]interface{}{
|
||||
"step": "completed",
|
||||
"percent": 100,
|
||||
"message": fmt.Sprintf("打包完成! 输出目录: %s", outputDir),
|
||||
"message": fmt.Sprintf("打包完成! 应用已保存到: %s", outputDir),
|
||||
})
|
||||
|
||||
log.Printf("打包完成! 输出目录: %s", outputDir)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -33,12 +33,7 @@ tasks:
|
||||
package:
|
||||
summary: Packages a production build of the application
|
||||
cmds:
|
||||
- |-
|
||||
if [ "{{.FORMAT | default "nsis"}}" = "msix" ]; then
|
||||
task: create:msix:package
|
||||
else
|
||||
task: create:nsis:installer
|
||||
fi
|
||||
- task: create:nsis:installer
|
||||
vars:
|
||||
FORMAT: '{{.FORMAT | default "nsis"}}'
|
||||
|
||||
|
||||
@@ -345,9 +345,15 @@
|
||||
<h3 class="step-title">准备打包</h3>
|
||||
<div class="pack-summary">
|
||||
<a-descriptions :column="1" bordered>
|
||||
<a-descriptions-item label="网站名称">{{ selectedSiteName }}</a-descriptions-item>
|
||||
<a-descriptions-item label="网站路径">
|
||||
<a-typography-paragraph :copyable="{ text: packConfig.sitePath }" style="margin: 0;">
|
||||
{{ packConfig.sitePath }}
|
||||
</a-typography-paragraph>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="应用名称">{{ appConfig.name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="版本号">{{ appConfig.version }}</a-descriptions-item>
|
||||
<a-descriptions-item label="作者">{{ appConfig.author || '未填写' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="描述">{{ appConfig.description || '未填写' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="目标平台">
|
||||
<a-tag v-for="platform in packConfig.platforms" :key="platform" color="blue">
|
||||
{{ platform }}
|
||||
@@ -356,14 +362,29 @@
|
||||
<a-descriptions-item label="窗口尺寸">
|
||||
{{ packConfig.width }} x {{ packConfig.height }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="输出目录">
|
||||
<a-typography-paragraph :copyable="{ text: packConfig.outputDir || 'site-dist' }" style="margin: 0;">
|
||||
{{ packConfig.outputDir || 'site-dist (默认)' }}
|
||||
</a-typography-paragraph>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<a-alert
|
||||
v-if="!envStatus.hasGo || !envStatus.hasWails"
|
||||
type="error"
|
||||
message="环境检查失败"
|
||||
description="打包需要 Go 和 Wails3 环境,请返回第一步安装所需环境。"
|
||||
show-icon
|
||||
style="margin-top: 16px;"
|
||||
/>
|
||||
|
||||
<div class="pack-action">
|
||||
<a-button
|
||||
type="primary"
|
||||
size="large"
|
||||
:loading="packing"
|
||||
@click="startPacking"
|
||||
:disabled="!envStatus.hasGo || !envStatus.hasWails"
|
||||
block
|
||||
>
|
||||
<template #icon><rocket-outlined /></template>
|
||||
@@ -372,9 +393,21 @@
|
||||
</div>
|
||||
|
||||
<!-- 打包进度 -->
|
||||
<div v-if="packing" class="pack-progress">
|
||||
<a-progress :percent="packProgress" status="active" />
|
||||
<div v-if="packing || packProgress > 0" class="pack-progress">
|
||||
<a-progress
|
||||
:percent="packProgress"
|
||||
:status="packProgress === 100 ? 'success' : 'active'"
|
||||
/>
|
||||
<p class="progress-text">{{ packProgressText }}</p>
|
||||
<a-alert
|
||||
v-if="packProgress < 100 && packProgress > 0"
|
||||
type="info"
|
||||
message="打包提示"
|
||||
description="首次打包需要下载依赖包,可能需要5-10分钟,请耐心等待..."
|
||||
show-icon
|
||||
closable
|
||||
style="margin-top: 12px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -410,16 +443,32 @@
|
||||
<div class="help-content">
|
||||
<p><strong>打包流程:</strong></p>
|
||||
<ol>
|
||||
<li>选择要打包的已下载网站</li>
|
||||
<li>确保已安装 Go 和 Wails3 开发环境</li>
|
||||
<li>选择要打包的网站文件夹(包含HTML、CSS、JS等静态文件)</li>
|
||||
<li>填写应用的基本信息(名称、版本、作者等)</li>
|
||||
<li>配置打包参数(目标平台、窗口大小等)</li>
|
||||
<li>确认信息无误后开始打包</li>
|
||||
<li>配置打包参数(目标平台、窗口大小、输出目录)</li>
|
||||
<li>点击"开始打包",等待编译完成</li>
|
||||
</ol>
|
||||
<p><strong>打包原理:</strong></p>
|
||||
<ul>
|
||||
<li>自动创建一个新的 Wails3 项目</li>
|
||||
<li>将您的静态网站文件嵌入到应用中</li>
|
||||
<li>编译生成独立的桌面应用程序(.exe)</li>
|
||||
<li>应用内置 WebView,无需浏览器即可运行</li>
|
||||
</ul>
|
||||
<p><strong>注意事项:</strong></p>
|
||||
<ul>
|
||||
<li>打包过程可能需要几分钟,请耐心等待</li>
|
||||
<li>确保有足够的磁盘空间用于存储打包文件</li>
|
||||
<li>不同平台的打包文件格式不同(Windows: .exe, macOS: .app, Linux: .AppImage)</li>
|
||||
<li>首次打包需要下载依赖,可能耗时5-10分钟</li>
|
||||
<li>确保有足够的磁盘空间(建议至少500MB)</li>
|
||||
<li>网站文件夹应包含index.html作为入口</li>
|
||||
<li>打包后的应用可独立运行,无需安装额外环境</li>
|
||||
<li>Windows平台生成.exe文件,macOS生成.app,Linux生成可执行文件</li>
|
||||
</ul>
|
||||
<p><strong>输出位置:</strong></p>
|
||||
<ul>
|
||||
<li>默认输出到项目的 <code>site-dist</code> 目录</li>
|
||||
<li>可自定义输出目录</li>
|
||||
<li>打包完成后会在输出目录中找到可执行文件</li>
|
||||
</ul>
|
||||
</div>
|
||||
</a-card>
|
||||
@@ -1232,6 +1281,19 @@ const goToDownload = () => {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.help-content code {
|
||||
background: #f5f5f5;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Consolas', monospace;
|
||||
font-size: 13px;
|
||||
color: #d63384;
|
||||
}
|
||||
|
||||
.help-content p {
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
/* 通用样式 */
|
||||
.full-width {
|
||||
width: 100%;
|
||||
|
||||
340
services/pack_service.go
Normal file
340
services/pack_service.go
Normal file
@@ -0,0 +1,340 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go-site-clone/utils"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// PackService 打包服务
|
||||
type PackService struct{}
|
||||
|
||||
// PackConfig 打包配置
|
||||
type PackConfig struct {
|
||||
SitePath string // 网站文件夹路径
|
||||
AppName string // 应用名称
|
||||
Version string // 版本号
|
||||
Author string // 作者
|
||||
Description string // 描述
|
||||
Platforms []string // 目标平台
|
||||
Width int // 窗口宽度
|
||||
Height int // 窗口高度
|
||||
OutputDir string // 输出目录
|
||||
}
|
||||
|
||||
// Wails项目模板
|
||||
const mainGoTemplate = `package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"log"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
//go:embed all:frontend
|
||||
var assets embed.FS
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "{{.AppName}}",
|
||||
Description: "{{.Description}}",
|
||||
Assets: application.AssetOptions{
|
||||
Handler: application.AssetFileServerFS(assets),
|
||||
},
|
||||
Mac: application.MacOptions{
|
||||
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
||||
},
|
||||
})
|
||||
|
||||
app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "{{.AppName}}",
|
||||
Mac: application.MacWindow{
|
||||
InvisibleTitleBarHeight: 50,
|
||||
Backdrop: application.MacBackdropTranslucent,
|
||||
TitleBar: application.MacTitleBarHiddenInset,
|
||||
},
|
||||
BackgroundColour: application.NewRGB(255, 255, 255),
|
||||
URL: "/",
|
||||
Width: {{.Width}},
|
||||
Height: {{.Height}},
|
||||
})
|
||||
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const goModTemplate = `module {{.ModuleName}}
|
||||
|
||||
go 1.21
|
||||
|
||||
require github.com/wailsapp/wails/v3 v3.0.0-alpha.27
|
||||
`
|
||||
|
||||
// CreateWailsProject 创建Wails项目结构
|
||||
func (p *PackService) CreateWailsProject(config PackConfig, projectDir string) error {
|
||||
// 1. 创建项目目录结构
|
||||
dirs := []string{
|
||||
projectDir,
|
||||
filepath.Join(projectDir, "frontend"),
|
||||
filepath.Join(projectDir, "build"),
|
||||
}
|
||||
for _, dir := range dirs {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("创建目录失败 %s: %v", dir, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 1.1 复制项目根目录的 Taskfile.yml
|
||||
currentDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取当前目录失败: %v", err)
|
||||
}
|
||||
srcTaskfile := filepath.Join(currentDir, "Taskfile.yml")
|
||||
dstTaskfile := filepath.Join(projectDir, "Taskfile.yml")
|
||||
if err := p.copyFile(srcTaskfile, dstTaskfile); err != nil {
|
||||
return fmt.Errorf("复制Taskfile.yml失败: %v", err)
|
||||
}
|
||||
|
||||
// 1.2 复制整个 build 目录
|
||||
srcBuildDir := filepath.Join(currentDir, "build")
|
||||
dstBuildDir := filepath.Join(projectDir, "build")
|
||||
if err := p.copyDirectory(srcBuildDir, dstBuildDir); err != nil {
|
||||
return fmt.Errorf("复制build目录失败: %v", err)
|
||||
}
|
||||
|
||||
// 2. 复制网站文件到frontend目录
|
||||
frontendDir := filepath.Join(projectDir, "frontend")
|
||||
if err := p.copyDirectory(config.SitePath, frontendDir); err != nil {
|
||||
return fmt.Errorf("复制网站文件失败: %v", err)
|
||||
}
|
||||
|
||||
// 2.1 创建一个空的 package.json,避免构建系统报错
|
||||
packageJSON := `{
|
||||
"name": "static-site",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "echo 'Static site, no build needed'",
|
||||
"generate": "echo 'Static site, no build needed'"
|
||||
}
|
||||
}`
|
||||
packageJSONPath := filepath.Join(frontendDir, "package.json")
|
||||
if err := os.WriteFile(packageJSONPath, []byte(packageJSON), 0644); err != nil {
|
||||
return fmt.Errorf("创建package.json失败: %v", err)
|
||||
}
|
||||
|
||||
// 2.2 创建一个空的 dist 目录(如果构建系统需要)
|
||||
distDir := filepath.Join(frontendDir, "dist")
|
||||
if err := os.MkdirAll(distDir, 0755); err != nil {
|
||||
return fmt.Errorf("创建dist目录失败: %v", err)
|
||||
}
|
||||
|
||||
// 3. 生成main.go
|
||||
mainGoPath := filepath.Join(projectDir, "main.go")
|
||||
tmpl, err := template.New("main").Parse(mainGoTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析main.go模板失败: %v", err)
|
||||
}
|
||||
|
||||
mainFile, err := os.Create(mainGoPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建main.go失败: %v", err)
|
||||
}
|
||||
defer mainFile.Close()
|
||||
|
||||
templateData := map[string]interface{}{
|
||||
"AppName": config.AppName,
|
||||
"Description": config.Description,
|
||||
"Width": config.Width,
|
||||
"Height": config.Height,
|
||||
}
|
||||
|
||||
if err := tmpl.Execute(mainFile, templateData); err != nil {
|
||||
return fmt.Errorf("生成main.go失败: %v", err)
|
||||
}
|
||||
|
||||
// 4. 生成go.mod
|
||||
goModPath := filepath.Join(projectDir, "go.mod")
|
||||
modTmpl, err := template.New("gomod").Parse(goModTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析go.mod模板失败: %v", err)
|
||||
}
|
||||
|
||||
modFile, err := os.Create(goModPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建go.mod失败: %v", err)
|
||||
}
|
||||
defer modFile.Close()
|
||||
|
||||
modData := map[string]interface{}{
|
||||
"ModuleName": strings.ToLower(strings.ReplaceAll(config.AppName, " ", "-")),
|
||||
}
|
||||
|
||||
if err := modTmpl.Execute(modFile, modData); err != nil {
|
||||
return fmt.Errorf("生成go.mod失败: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildWailsApp 构建Wails应用
|
||||
func (p *PackService) BuildWailsApp(projectDir string, outputDir string, platforms []string) error {
|
||||
// 获取环境状态
|
||||
envStatus, err := utils.GetEnvStatus()
|
||||
if err != nil {
|
||||
return fmt.Errorf("检查环境失败: %v", err)
|
||||
}
|
||||
|
||||
// 确定使用的Go命令
|
||||
goCmd := "go"
|
||||
if envStatus.GoPath != "" {
|
||||
goCmd = envStatus.GoPath
|
||||
}
|
||||
|
||||
// 1. 执行 go mod tidy
|
||||
tidyCmd := exec.Command(goCmd, "mod", "tidy")
|
||||
tidyCmd.Dir = projectDir
|
||||
if output, err := tidyCmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("go mod tidy 失败: %v\n%s", err, output)
|
||||
}
|
||||
|
||||
// 2. 直接使用 go build 编译,跳过复杂的构建流程
|
||||
// 创建输出目录
|
||||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||
return fmt.Errorf("创建输出目录失败: %v", err)
|
||||
}
|
||||
|
||||
// 确定输出文件名
|
||||
appName := filepath.Base(projectDir)
|
||||
outputFile := filepath.Join(outputDir, appName+".exe")
|
||||
|
||||
// 构建命令,使用生产环境标签
|
||||
buildCmd := exec.Command(goCmd, "build",
|
||||
"-tags", "production",
|
||||
"-ldflags", "-w -s -H windowsgui",
|
||||
"-o", outputFile,
|
||||
".")
|
||||
buildCmd.Dir = projectDir
|
||||
|
||||
// 设置环境变量
|
||||
buildCmd.Env = append(os.Environ(),
|
||||
"CGO_ENABLED=1",
|
||||
"GOOS=windows",
|
||||
)
|
||||
|
||||
output, err := buildCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("go build 失败: %v\n%s", err, output)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyDirectory 递归复制目录
|
||||
func (p *PackService) copyDirectory(src, dst string) error {
|
||||
// 获取源目录信息
|
||||
srcInfo, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建目标目录
|
||||
if err := os.MkdirAll(dst, srcInfo.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 读取源目录
|
||||
entries, err := os.ReadDir(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
srcPath := filepath.Join(src, entry.Name())
|
||||
dstPath := filepath.Join(dst, entry.Name())
|
||||
|
||||
if entry.IsDir() {
|
||||
// 递归复制子目录
|
||||
if err := p.copyDirectory(srcPath, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// 复制文件
|
||||
if err := p.copyFile(srcPath, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyFile 复制单个文件
|
||||
func (p *PackService) copyFile(src, dst string) error {
|
||||
srcFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
dstFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
if _, err := io.Copy(dstFile, srcFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 复制文件权限
|
||||
srcInfo, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Chmod(dst, srcInfo.Mode())
|
||||
}
|
||||
|
||||
// copyBuildOutput 复制构建输出
|
||||
func (p *PackService) copyBuildOutput(buildDir, outputDir string) error {
|
||||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 检查 buildDir 是否存在
|
||||
if _, err := os.Stat(buildDir); os.IsNotExist(err) {
|
||||
return fmt.Errorf("构建目录不存在: %s", buildDir)
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(buildDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
srcPath := filepath.Join(buildDir, entry.Name())
|
||||
dstPath := filepath.Join(outputDir, entry.Name())
|
||||
|
||||
if entry.IsDir() {
|
||||
if err := p.copyDirectory(srcPath, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := p.copyFile(srcPath, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user