feat: Tag管理功能 + 打包脚本优化 + CI/CD发布流水线

- 新增标签管理: 列表/创建/删除/推送标签 (后端API + 前端UI)
- 前端新增标签Tab页,支持创建、删除、推送操作
- config.go 改为从可执行文件同目录加载 config.yaml
- 各平台构建脚本自动复制 config.yaml 到输出目录
- Windows 打包改为 ZIP 格式 (不依赖 NSIS)
- 新增 GitHub Actions Release 工作流 (三平台自动构建+发布)
This commit is contained in:
zyj
2026-03-09 17:18:53 +08:00
parent 29f1c0514a
commit 1b20fc2c51
14 changed files with 778 additions and 10 deletions

View File

@@ -701,3 +701,97 @@ func (s *AppService) SwitchBranch(path, branch string) error {
_, err := s.gitClient.Checkout(path, strings.TrimSpace(branch))
return err
}
// TagInfo 标签信息
type TagInfo struct {
Name string `json:"name"`
Hash string `json:"hash"`
Timestamp int64 `json:"timestamp"`
Message string `json:"message"`
}
// GetTags 获取所有标签
func (s *AppService) GetTags(path string) ([]TagInfo, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil, fmt.Errorf("项目路径不存在: %s", path)
}
out, err := s.gitClient.TagList(path)
if err != nil {
return nil, fmt.Errorf("获取标签列表失败: %w", err)
}
var tags []TagInfo
for _, line := range strings.Split(strings.TrimSpace(out), "\n") {
if strings.TrimSpace(line) == "" {
continue
}
parts := strings.SplitN(line, "\t", 5)
if len(parts) < 3 {
continue
}
var ts int64
fmt.Sscanf(parts[2], "%d", &ts)
hash := parts[1]
// 注释标签的实际提交哈希在 *objectname
if len(parts) > 3 && parts[3] != "" {
hash = parts[3]
}
message := ""
if len(parts) > 4 {
message = parts[4]
}
tags = append(tags, TagInfo{
Name: parts[0],
Hash: hash,
Timestamp: ts,
Message: message,
})
}
return tags, nil
}
// CreateTag 创建标签
func (s *AppService) CreateTag(path, name, message string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return fmt.Errorf("项目路径不存在: %s", path)
}
name = strings.TrimSpace(name)
if name == "" {
return fmt.Errorf("标签名不能为空")
}
if strings.TrimSpace(message) == "" {
message = name
}
_, err := s.gitClient.CreateTag(path, name, message)
return err
}
// DeleteTag 删除标签(本地+远程)
func (s *AppService) DeleteTag(path, name string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return fmt.Errorf("项目路径不存在: %s", path)
}
name = strings.TrimSpace(name)
if name == "" {
return fmt.Errorf("标签名不能为空")
}
// 删除本地标签
if _, err := s.gitClient.DeleteTag(path, name); err != nil {
return fmt.Errorf("删除本地标签失败: %w", err)
}
// 尝试删除远程标签(忽略错误,可能未推送过)
s.gitClient.DeleteRemoteTag(path, name)
return nil
}
// PushTag 推送标签到远程
func (s *AppService) PushTag(path, name string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return fmt.Errorf("项目路径不存在: %s", path)
}
name = strings.TrimSpace(name)
if name == "" {
return fmt.Errorf("标签名不能为空")
}
_, err := s.gitClient.PushTag(path, name)
return err
}

View File

@@ -129,6 +129,32 @@ func (g *GitClient) RevertCommit(path, hash string) (string, error) {
return g.Run(path, "revert", "--no-edit", hash)
}
// TagList 获取所有标签(按版本号降序,带创建时间和提交哈希)
func (g *GitClient) TagList(path string) (string, error) {
return g.Run(path, "tag", "-l", "--sort=-version:refname",
"--format=%(refname:short)\t%(objectname:short)\t%(creatordate:unix)\t%(*objectname:short)\t%(contents:subject)")
}
// CreateTag 创建注释标签
func (g *GitClient) CreateTag(path, name, message string) (string, error) {
return g.Run(path, "tag", "-a", name, "-m", message)
}
// DeleteTag 删除本地标签
func (g *GitClient) DeleteTag(path, name string) (string, error) {
return g.Run(path, "tag", "-d", name)
}
// PushTag 推送标签到远程
func (g *GitClient) PushTag(path, name string) (string, error) {
return g.Run(path, "push", "origin", name)
}
// DeleteRemoteTag 删除远程标签
func (g *GitClient) DeleteRemoteTag(path, name string) (string, error) {
return g.Run(path, "push", "origin", "--delete", name)
}
// BranchList 获取所有本地分支
func (g *GitClient) BranchList(path string) (string, error) {
return g.Run(path, "branch", "--format=%(refname:short)\t%(HEAD)")