GO实战项目:基于 `HTML/CSS/JS + Gin + Gorm + 文心一言API`AI 备忘录应用
AI 备忘录应用:完整项目目录与分步实现指南
基于 HTML/CSS/JS + Gin + Gorm + 文心一言API
构建,以下是完整项目目录结构和可落地的分步实现方法,确保零基础也能跟随操作。
一、完整项目目录结构
plaintext
ai-memo/ # 项目根目录
├── config/ # 配置文件目录
│ └── db.go # 数据库连接配置
├── model/ # 数据模型目录(Gorm)
│ └── task.go # 任务表模型定义
├── service/ # 业务服务目录
│ └── ai_service.go # AI接口调用服务(文心一言)
├── static/ # 前端静态资源目录
│ ├── css/
│ │ └── style.css # 页面样式文件
│ └── js/
│ └── main.js # 前端交互逻辑
├── templates/ # HTML模板目录(Gin渲染)
│ └── index.html # 首页(任务表单+列表)
├── go.mod # Go依赖管理文件
├── go.sum # Go依赖版本锁定文件
└── main.go # 后端主程序(路由+入口)
二、分步实现方法(共 9 步)
步骤 1:环境准备(必做)
先安装基础工具,确保后续操作无依赖问题:
-
安装 Go 环境
- 下载地址:https://go.dev/dl/(选择 Go 1.20 + 版本,适配 Gin 最新版)
- 验证:终端输入
go version
,显示go version go1.2x.x xxx
即成功。
-
安装 MySQL 数据库
- 下载地址:https://dev.mysql.com/downloads/mysql/(8.0 + 版本)
- 记住数据库用户名(默认 root)和密码(安装时设置)。
-
申请文心一言 API 密钥
- 访问:https://console.bce.baidu.com/qianfan/ (百度智能云千帆平台)
- 注册登录后,创建 “应用”,获取 API Key 和 Secret Key(后续配置用)。
-
安装代码编辑器
(可选,推荐 VS Code)
- 安装 Go 插件(搜索 “Go”)和 HTML/CSS 插件,方便编写代码。
步骤 2:创建项目目录(终端 / 命令行操作)
-
新建根目录
ai-memo
(路径可自定义,如桌面):
bash
# Windows(CMD) mkdir C:\Users\你的用户名\Desktop\ai-memo cd C:\Users\你的用户名\Desktop\ai-memo# Mac/Linux(终端) mkdir ~/Desktop/ai-memo cd ~/Desktop/ai-memo
-
创建子目录(按完整目录结构):
bash
# 批量创建子目录 mkdir config model service static/css static/js templates
-
验证:打开项目根目录,确认
config
、static
等文件夹已创建。
步骤 3:初始化 Go 模块(管理依赖)
在项目根目录执行以下命令,初始化 Go 项目并导入核心依赖:
-
初始化 Go 模块(
your-project-path
可自定义,如
github.com/your-name/ai-memo
):
bash
go mod init your-project-path
- 执行后,根目录会生成
go.mod
文件(依赖配置文件)。
- 执行后,根目录会生成
-
安装核心依赖(Gin、Gorm、MySQL 驱动、百度 API SDK):
bash
go get github.com/gin-gonic/gin@v1.10.0 go get gorm.io/gorm@v1.25.3 go get gorm.io/driver/mysql@v1.5.2 go get github.com/baidu-idl/bce-sdk-go@v0.10.233
- 执行后,根目录会生成
go.sum
文件(依赖版本锁定),且go.mod
会新增依赖记录。
- 执行后,根目录会生成
步骤 4:数据库配置与模型定义(后端核心)
4.1 创建数据库(MySQL 操作)
先在 MySQL 中创建项目专用数据库 ai_memo
(后续 Gorm 会自动建表):
-
打开 MySQL 终端(或用 Navicat、DataGrip 等工具):
bash
# 登录MySQL(输入密码时按回车,然后输入你的MySQL密码) mysql -u root -p
-
创建数据库
ai_memo
:
sql
CREATE DATABASE IF NOT EXISTS ai_memo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-
验证:输入
show databases;
,看到ai_memo
即成功,然后输入exit;
退出 MySQL。
4.2 编写数据库配置(config/db.go)
在 config
目录下新建 db.go
文件,粘贴以下代码(需修改 MySQL 连接信息):
go
运行
package configimport ("your-project-path/model" // 替换为你的Go模块名(步骤3中init的路径)"gorm.io/driver/mysql""gorm.io/gorm"
)var DB *gorm.DB // 全局数据库对象,供其他模块调用// InitDB 初始化数据库连接(自动建表)
func InitDB() error {// 1. 修改这里的MySQL配置:root:密码@tcp(localhost:3306)/ai_memo?xxxdsn := "root:你的MySQL密码@tcp(127.0.0.1:3306)/ai_memo?charset=utf8mb4&parseTime=True&loc=Local"// 2. 连接MySQLdb, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {return err // 连接失败返回错误}// 3. 自动迁移:根据model.Task创建task表(无表则建表,有表则更新结构)err = db.AutoMigrate(&model.Task{})if err != nil {return err}DB = db // 赋值给全局变量return nil
}
- 关键修改:将
dsn
中的你的MySQL密码
替换为实际 MySQL 密码(如root:123456@tcp(...)
)。
4.3 编写任务模型(model/task.go)
在 model
目录下新建 task.go
文件,粘贴以下代码(定义任务表结构):
go
运行
package modelimport ("time""gorm.io/gorm"
)// TaskStatus 任务状态枚举(限定值:pending/doing/done)
type TaskStatus string
const (StatusPending TaskStatus = "pending" // 待执行StatusDoing TaskStatus = "doing" // 执行中StatusDone TaskStatus = "done" // 已完成
)// Task 任务表模型(Gorm会根据此结构体创建task表)
type Task struct {gorm.Model // 内置字段:ID(主键)、CreatedAt(创建时间)、UpdatedAt(更新时间)、DeletedAt(软删除)Title string `gorm:"size:100;not null" json:"title"` // 任务标题(非空,最大100字符)Content string `gorm:"type:text" json:"content"` // 任务详情(长文本)DueTime time.Time `gorm:"not null" json:"due_time"` // 截止时间(非空)Status TaskStatus `gorm:"default:pending" json:"status"` // 任务状态(默认待执行)AIPlan string `gorm:"type:text" json:"ai_plan"` // AI生成的任务规划AIAdvice string `gorm:"type:text" json:"ai_advice"` // AI生成的执行建议
}
步骤 5:编写 AI 服务(调用文心一言 API)
在 service
目录下新建 ai_service.go
文件,粘贴以下代码(需替换 API 密钥):
go
运行
package serviceimport ("context""fmt""os""strings""github.com/baidubce/bce-qianfan-sdk/go/qianfan"
)// 初始化鉴权信息(通过环境变量设置AK/SK,符合SDK鉴权逻辑)
func init() {// 替换为你的百度智能云Access Key和Secret Key// 获取路径:百度智能云控制台 → 用户账户 → 安全认证 → 访问密钥err := os.Setenv("QIANFAN_ACCESS_KEY", "")if err != nil {return} // 你的Access Keyerr = os.Setenv("QIANFAN_SECRET_KEY", "")if err != nil {return} // 你的Secret Key
}// GetAIAdvice 调用百度千帆大模型API,生成任务规划和建议
// 参数:任务标题、任务详情、截止时间;返回:AI规划、AI建议、错误信息
func GetAIAdvice(title, content string, dueTime string) (plan string, advice string, err error) {// 1. 构造AI提示词(明确输出格式,便于后续解析)prompt := fmt.Sprintf(`请基于以下任务信息,严格按照【规划】和【建议】两部分生成内容,不要额外文字:1. 【规划】:包含任务优先级(高/中/低)、分步骤时间分配(适配截止时间%s);2. 【建议】:包含执行风险点(如可能拖延的环节)、资源推荐(如工具/资料)。任务标题:%s任务详情:%s`, dueTime, title, content)// 2. 初始化千帆SDK的聊天客户端(使用SDK中定义的NewChatCompletion)client := qianfan.NewChatCompletion()// 3. 构造大模型请求参数(使用SDK中定义的ChatCompletionRequest结构体)req := &qianfan.ChatCompletionRequest{Messages: []qianfan.ChatCompletionMessage{{Role: "user", // 角色:用户(发送提示词)Content: prompt,},},Temperature: 0.7, // 生成随机性(0-1,值越小越稳定)}// 4. 调用大模型API(使用SDK中定义的Do方法)resp, err := client.Do(context.Background(), req)if err != nil {return "", "", fmt.Errorf("大模型调用失败:%v", err)}// 5. 提取大模型生成结果(直接使用SDK响应结构体的Result字段)if resp.Result == "" {return "", "", fmt.Errorf("未获取到有效生成结果")}// 6. 分割规划和建议(按约定格式提取)parts := strings.Split(resp.Result, "【建议】")if len(parts) < 2 {return "", "", fmt.Errorf("AI返回格式不符合要求(缺少【建议】):%s", resp.Result)}plan = strings.TrimPrefix(parts[0], "【规划】") // 移除【规划】前缀advice = parts[1] // 提取建议内容return plan, advice, nil
}
- 关键修改:将
APIKey
和SecretKey
替换为步骤 1 中申请的百度智能云密钥。
步骤 6:编写后端主服务(路由 + 逻辑)
在项目根目录下新建 main.go
文件(后端入口文件),粘贴以下代码:
go
运行
package mainimport ("net/http""strconv""time""your-project-path/config" // 替换为你的Go模块名"your-project-path/model" // 替换为你的Go模块名"your-project-path/service" // 替换为你的Go模块名"github.com/gin-gonic/gin"
)func main() {// 第一步:初始化数据库(调用config中的InitDB)if err := config.InitDB(); err != nil {panic("数据库初始化失败:" + err.Error()) // 失败则退出程序}// 第二步:初始化Gin引擎(开启默认中间件:日志、恢复)r := gin.Default()// 第三步:配置Gin模板和静态文件r.LoadHTMLGlob("templates/*") // 加载templates目录下的HTML模板r.Static("/static", "./static") // 映射/static路径到static目录(前端加载CSS/JS)// 第四步:定义路由(接口+页面)// 1. 首页:GET请求,展示任务列表r.GET("/", func(c *gin.Context) {var tasks []model.Task// 从数据库查询所有任务(按创建时间倒序,最新的在前面)config.DB.Order("created_at desc").Find(&tasks)// 渲染templates/index.html,并传递tasks数据到前端c.HTML(http.StatusOK, "index.html", gin.H{"tasks": tasks})})// 2. 添加任务:POST请求,接收前端表单,调用AI生成规划r.POST("/task/add", func(c *gin.Context) {// 1. 接收前端表单参数(对应index.html中的表单name)title := c.PostForm("title") // 任务标题content := c.PostForm("content") // 任务详情dueTimeStr := c.PostForm("due_time")// 截止时间(前端传的是"2024-12-31T23:59"格式)// 2. 转换截止时间格式(前端T分隔 → 后端空格分隔)dueTime, err := time.Parse("2006-01-02T15:04", dueTimeStr)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"msg": "截止时间格式错误"})return}// 3. 调用AI服务生成规划和建议aiPlan, aiAdvice, err := service.GetAIAdvice(title, content, dueTime.Format("2006-01-02 15:04"), // 传给AI的时间格式)if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"msg": "AI调用失败:" + err.Error()})return}// 4. 保存任务到数据库(创建model.Task对象)task := model.Task{Title: title,Content: content,DueTime: dueTime,AIPlan: aiPlan,AIAdvice: aiAdvice,Status: model.StatusPending, // 默认状态:待执行}config.DB.Create(&task) // 插入数据库// 5. 返回成功响应给前端c.JSON(http.StatusOK, gin.H{"msg": "任务添加成功(AI规划已生成)"})})// 3. 更新任务状态:POST请求,接收任务ID和新状态r.POST("/task/status", func(c *gin.Context) {// 1. 接收前端参数(任务ID是字符串,需转为int)idStr := c.PostForm("id")id, err := strconv.Atoi(idStr)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"msg": "任务ID格式错误"})return}newStatus := model.TaskStatus(c.PostForm("status")) // 转换为TaskStatus类型// 2. 更新数据库中的任务状态(根据ID更新status字段)config.DB.Model(&model.Task{}).Where("id = ?", id).Update("status", newStatus)// 3. 返回成功响应c.JSON(http.StatusOK, gin.H{"msg": "任务状态更新成功"})})// 第五步:启动服务(监听8080端口,访问地址:http://localhost:8080)r.Run(":8080")
}
- 关键修改:将
your-project-path
替换为步骤 3 中go mod init
定义的模块名(如github.com/your-name/ai-memo
)。
步骤 7:编写前端代码(HTML/CSS/JS)
7.1 编写首页 HTML(templates/index.html)
在 templates
目录下新建 index.html
文件,粘贴以下代码:
html
预览
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>AI智能备忘录</title><!-- 加载静态CSS(对应static/css/style.css) --><link rel="stylesheet" href="/static/css/style.css">
</head>
<body><div class="container"><!-- 页面标题 --><h1>AI智能备忘录</h1><!-- 1. 任务添加表单 --><div class="add-task"><h3>添加新任务</h3><form id="taskForm"><div class="form-item"><label>任务标题:</label><input type="text" name="title" required placeholder="例如:完成Go项目报告"></div><div class="form-item"><label>任务详情:</label><textarea name="content" rows="3" placeholder="例如:包含需求分析、代码说明、测试报告"></textarea></div><div class="form-item"><label>截止时间:</label><input type="datetime-local" name="due_time" required></div><button type="submit" id="submitBtn">添加任务(AI自动规划)</button><!-- AI加载状态(默认隐藏) --><span id="loading" style="display: none; margin-left: 10px; color: #666;">AI正在生成规划...(约3秒)</span></form></div><!-- 2. 任务列表(后端传递的tasks数据会渲染这里) --><div class="task-list"><h3>我的任务</h3>{{if len .tasks}}<ul>{{range .tasks}}<li class="task-item"><!-- 任务标题+状态选择器 --><div class="task-header"><h4>{{.Title}}</h4><select class="status-select" data-id="{{.ID}}"><option value="pending" {{if eq .Status "pending"}}selected{{end}}>待执行</option><option value="doing" {{if eq .Status "doing"}}selected{{end}}>执行中</option><option value="done" {{if eq .Status "done"}}selected{{end}}>已完成</option></select></div><!-- 任务基础信息 --><div class="task-info"><p><span>详情:</span>{{if .Content}}{{.Content}}{{else}}无详情{{end}}</p><p><span>截止时间:</span>{{.DueTime.Format "2006-01-02 15:04"}}</p><p><span>创建时间:</span>{{.CreatedAt.Format "2006-01-02 15:04"}}</p></div><!-- AI生成的规划和建议 --><div class="ai-content"><p><span>AI规划:</span>{{.AIPlan}}</p><p><span>AI建议:</span>{{.AIAdvice}}</p></div></li>{{end}}</ul>{{else}}<!-- 无任务时显示 --><p class="empty">暂无任务,点击“添加新任务”开始吧!</p>{{end}}</div></div><!-- 加载静态JS(对应static/js/main.js) --><script src="/static/js/main.js"></script>
</body>
</html>
7.2 编写 CSS 样式(static/css/style.css)
在 static/css
目录下新建 style.css
文件,粘贴以下代码:
css
/* 全局样式重置 */
* {margin: 0;padding: 0;box-sizing: border-box;font-family: "Microsoft YaHei", Arial, sans-serif;
}body {background-color: #f5f7fa;color: #333;
}/* 容器样式(居中+宽度限制) */
.container {max-width: 1000px;margin: 30px auto;padding: 0 20px;
}h1 {color: #2c3e50;text-align: center;margin-bottom: 40px;font-size: 28px;
}h3 {color: #2c3e50;margin-bottom: 15px;font-size: 18px;
}/* 1. 任务添加表单样式 */
.add-task {background-color: white;padding: 25px;border-radius: 8px;box-shadow: 0 2px 8px rgba(0,0,0,0.05);margin-bottom: 30px;
}.form-item {margin-bottom: 20px;
}.form-item label {display: block;margin-bottom: 8px;color: #666;font-weight: 500;
}.form-item input,
.form-item textarea {width: 100%;padding: 10px 12px;border: 1px solid #ddd;border-radius: 4px;font-size: 14px;transition: border 0.3s;
}.form-item input:focus,
.form-item textarea:focus {outline: none;border-color: #3498db;
}.form-item textarea {resize: vertical; /* 只允许垂直拉伸 */
}button {background-color: #3498db;color: white;border: none;padding: 12px 24px;border-radius: 4px;font-size: 14px;cursor: pointer;transition: background 0.3s;
}button:hover {background-color: #2980b9;
}/* 2. 任务列表样式 */
.task-list {background-color: white;padding: 25px;border-radius: 8px;box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}.task-item {border-bottom: 1px solid #f0f0f0;padding: 20px 0;list-style: none;
}.task-item:last-child {border-bottom: none;
}.task-header {display: flex;justify-content: space-between;align-items: center;margin-bottom: 15px;
}.task-header h4 {color: #2c3e50;font-size: 16px;
}.status-select {padding: 6px 10px;border: 1px solid #ddd;border-radius: 4px;font-size: 13px;cursor: pointer;transition: border 0.3s;
}.status-select:focus {outline: none;border-color: #3498db;
}.task-info {margin-bottom: 15px;font-size: 14px;color: #666;line-height: 1.8;
}.task-info span {color: #999;margin-right: 8px;
}/* AI内容样式(突出显示) */
.ai-content {background-color: #f8f9fa;padding: 15px;border-radius: 4px;font-size: 14px;line-height: 1.8;color: #555;
}.ai-content span {color: #3498db;font-weight: 500;margin-right: 8px;
}/* 无任务提示样式 */
.empty {text-align: center;padding: 40px 0;color: #999;font-size: 14px;background-color: #f8f9fa;border-radius: 4px;
}
7.3 编写前端 JS(static/js/main.js)
在 static/js
目录下新建 main.js
文件,粘贴以下代码:
javascript
运行
// 1. 任务表单提交逻辑(调用后端/add接口,带加载状态)
const taskForm = document.getElementById('taskForm');
const submitBtn = document.getElementById('submitBtn');
const loading = document.getElementById('loading');taskForm.addEventListener('submit', async (e) => {e.preventDefault(); // 阻止表单默认刷新行为// 显示加载状态,禁用按钮(防止重复提交)submitBtn.disabled = true;loading.style.display = 'inline-block';try {// 收集表单数据(转为URL编码格式,适配后端POST接收)const formData = new FormData(taskForm);const formParams = new URLSearchParams(formData);// 调用后端添加任务接口const response = await fetch('/task/add', {method: 'POST',body: formParams, // 自动设置Content-Type为application/x-www-form-urlencoded});// 解析后端响应(JSON格式)const result = await response.json();// 提示用户并刷新页面(显示新任务)alert(result.msg);window.location.reload();} catch (error) {// 捕获错误并提示alert('添加任务失败:' + error.message);} finally {// 恢复按钮和加载状态(无论成功/失败)submitBtn.disabled = false;loading.style.display = 'none';}
});// 2. 任务状态更新逻辑(实时修改,无需刷新页面)
const statusSelects = document.querySelectorAll('.status-select');statusSelects.forEach(select => {// 记录初始状态(用于更新失败时恢复)const oldStatus = select.value;// 监听下拉框变化select.addEventListener('change', async (e) => {const taskId = e.target.dataset.id; // 获取任务ID(从HTML的data-id属性)const newStatus = e.target.value; // 获取新状态try {// 调用后端更新状态接口await fetch('/task/status', {method: 'POST',body: new URLSearchParams({id: taskId,status: newStatus})});// 更新成功:提示用户(可选)// alert('状态已更新');} catch (error) {// 更新失败:恢复初始状态并提示e.target.value = oldStatus;alert('更新状态失败:' + error.message);}});
});
步骤 8:测试运行项目
-
启动 MySQL 服务
- Windows:打开 “服务”,找到 “MySQL”,确保状态为 “正在运行”;
- Mac:通过 “系统偏好设置”→“MySQL”,点击 “Start MySQL Server”;
- Linux:执行
sudo systemctl start mysql
。
-
启动后端服务
在项目根目录执行以下命令(终端 / CMD):
bash
go run main.go
- 成功提示:
[GIN-debug] Listening and serving HTTP on :8080
(表示服务已启动,监听 8080 端口)。
- 成功提示:
-
访问应用
打开浏览器,输入地址:
http://localhost:8080
,即可看到 AI 备忘录页面:- 功能 1:填写任务标题、详情、截止时间,点击 “添加任务”,等待 AI 生成规划(约 3 秒),成功后刷新页面显示新任务;
- 功能 2:在任务列表的下拉框中修改状态(待执行→执行中→已完成),实时更新数据库;
- 功能 3:查看 AI 生成的 “规划” 和 “建议”(在每个任务的下方)。
步骤 9:常见问题排查
- 数据库连接失败
- 错误提示:
数据库初始化失败:Error 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
- 解决:检查
config/db.go
中的dsn
,确认 MySQL 密码正确(如root:123456
中的123456
是你的实际密码)。
- 错误提示:
- AI 调用失败
- 错误提示:
AI调用失败:AI返回错误:18-Invalid API Key
- 解决:检查
service/ai_service.go
中的APIKey
和SecretKey
,确认与百度智能云的应用密钥一致(注意大小写)。
- 错误提示:
- 前端样式不加载
- 现象:页面无样式,CSS/JS 路径报错;
- 解决:确保
main.go
中r.Static("/static", "./static")
配置正确,且 HTML 中link
和script
的路径是/static/css/style.css
(开头带/
)。
- 端口被占用
- 错误提示:
bind: address already in use
- 解决:修改
main.go
中r.Run(":8080")
为其他端口(如r.Run(":8081")
),访问时用http://localhost:8081
。
- 错误提示:
三、功能扩展建议(可选)
- 任务删除 / 编辑:在
main.go
中添加/task/delete
和/task/edit
接口,前端增加删除按钮和编辑表单; - AI 多轮交互:在任务详情页增加 “追问 AI” 输入框,支持用户进一步细化规划(如 “如何优化这个任务步骤?”);
- 到期提醒:集成 Go 的定时任务库(如
github.com/robfig/cron
),每天检查到期任务,通过邮件 / 短信提醒; - 数据导出:添加 “导出任务” 功能,将任务列表导出为 Excel(使用
github.com/xuri/excelize/v2
库)。
按照以上步骤操作,即可完成一个完整的 “AI 自动规划任务” 备忘录应用,前后端全链路打通,支持核心功能使用。
编辑分享
待改进V2.0
功能扩展一:任务删除功能
目标:实现 “删除任务” 按钮,点击后从数据库删除任务并刷新列表。
步骤 1:后端添加删除任务路由(修改 main.go
)
在现有路由后新增删除逻辑:
go
运行
// 4. 删除任务:POST请求,接收任务ID并删除
r.POST("/task/delete", func(c *gin.Context) {// 解析任务IDidStr := c.PostForm("id")id, err := strconv.Atoi(idStr)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"msg": "任务ID格式错误"})return}// GORM删除任务(根据ID)config.DB.Delete(&model.Task{}, id)c.JSON(http.StatusOK, gin.H{"msg": "任务删除成功"})
})
步骤 2:前端页面添加删除按钮(修改 templates/index.html
)
在任务项的「状态选择器」旁添加删除按钮:
html
预览
<div class="task-header"><h4>{{.Title}}</h4><div><select class="status-select" data-id="{{.ID}}"><option value="pending" {{if eq .Status "pending"}}selected{{end}}>待执行</option><option value="doing" {{if eq .Status "doing"}}selected{{end}}>执行中</option><option value="done" {{if eq .Status "done"}}selected{{end}}>已完成</option></select><button class="delete-btn" data-id="{{.ID}}">删除</button></div>
</div>
步骤 3:前端 JS 处理删除逻辑(修改 static/js/main.js
)
添加删除按钮的点击事件:
javascript
运行
// 3. 任务删除逻辑
const deleteBtns = document.querySelectorAll('.delete-btn');
deleteBtns.forEach(btn => {btn.addEventListener('click', async (e) => {const taskId = e.target.dataset.id;if (confirm('确定要删除该任务吗?')) {try {await fetch('/task/delete', {method: 'POST',body: new URLSearchParams({ id: taskId })});window.location.reload(); // 刷新页面} catch (error) {alert('删除失败:' + error.message);}}});
});
步骤 4:添加删除按钮样式(修改 static/css/style.css
)
css
.delete-btn {background-color: #e74c3c;color: white;border: none;padding: 6px 12px;border-radius: 4px;font-size: 13px;cursor: pointer;margin-left: 10px;transition: background 0.3s;
}
.delete-btn:hover {background-color: #c0392b;
}
功能扩展二:AI 多轮交互(任务详情追问)
目标:在任务详情页添加输入框,向 AI 追问并获取实时建议。
步骤 1:后端添加 “追问 AI” 路由(修改 main.go
)
新增路由处理追问请求:
go
运行
// 5. AI追问:接收任务ID和问题,生成新建议
r.POST("/task/ask", func(c *gin.Context) {taskIdStr := c.PostForm("taskId")taskId, err := strconv.Atoi(taskIdStr)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"msg": "任务ID错误"})return}question := c.PostForm("question") // 追问内容// 查询任务详情var task model.Taskif err := config.DB.First(&task, taskId).Error; err != nil {c.JSON(http.StatusNotFound, gin.H{"msg": "任务不存在"})return}// 构造包含任务上下文的提示词prompt := fmt.Sprintf(`任务标题:%s详情:%s截止时间:%sAI原规划:%sAI原建议:%s我的问题:%s`, task.Title, task.Content, task.DueTime.Format("2006-01-02 15:04"), task.AIPlan, task.AIAdvice, question)// 调用AI服务(复用GetAIAdvice,忽略title和content,用prompt传完整问题)_, aiReply, err := service.GetAIAdvice("", "", prompt)if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"msg": "AI调用失败"})return}c.JSON(http.StatusOK, gin.H{"reply": aiReply})
})
步骤 2:前端页面添加追问输入框(修改 templates/index.html
)
在任务的「AI 内容」下方新增输入框:
html
预览
<div class="ai-ask"><input type="text" id="askInput" placeholder="向AI追问(如:如何优化步骤?)" data-id="{{.ID}}"><button class="ask-btn" data-id="{{.ID}}">追问AI</button><div id="aiReply" style="display: none; margin-top: 10px; padding: 10px; background: #f8f9fa; border-radius: 4px;"><span>AI回复:</span><span id="replyContent"></span></div>
</div>
步骤 3:前端 JS 处理追问逻辑(修改 static/js/main.js
)
添加追问按钮的点击事件:
javascript
运行
// 4. AI追问逻辑
const askBtns = document.querySelectorAll('.ask-btn');
askBtns.forEach(btn => {btn.addEventListener('click', async (e) => {const taskId = e.target.dataset.id;const input = document.querySelector(`#askInput[data-id="${taskId}"]`);const question = input.value.trim();if (!question) {alert('请输入追问内容');return;}// 显示加载状态const loading = document.createElement('span');loading.textContent = 'AI思考中...';loading.style.marginLeft = '10px';btn.parentNode.appendChild(loading);try {const res = await fetch('/task/ask', {method: 'POST',body: new URLSearchParams({ taskId, question })});const data = await res.json();// 显示AI回复const replyDiv = document.querySelector(`#aiReply[data-id="${taskId}"]`);const replyContent = document.querySelector(`#replyContent[data-id="${taskId}"]`);replyContent.textContent = data.reply;replyDiv.style.display = 'block';} catch (err) {alert('追问失败:' + err.message);} finally {loading.remove(); // 移除加载状态}});
});
步骤 4:添加追问样式(修改 static/css/style.css
)
css
.ai-ask {margin-top: 20px;
}
.ai-ask input {width: calc(100% - 120px);padding: 8px 12px;border: 1px solid #ddd;border-radius: 4px;font-size: 14px;
}
.ask-btn {background-color: #2ecc71;color: white;border: none;padding: 8px 16px;border-radius: 4px;font-size: 14px;cursor: pointer;margin-left: 5px;transition: background 0.3s;
}
.ask-btn:hover {background-color: #27ae60;
}
功能扩展三:到期提醒(定时任务 + 邮件通知)
目标:每天自动检查到期任务,通过邮件提醒用户。
步骤 1:安装依赖
bash
go get github.com/robfig/cron/v3 # 定时任务库
go get github.com/jordan-wright/email # 邮件库
步骤 2:配置邮件服务(新增 config/email.go
)
在 config
目录新建 email.go
,配置邮件服务器(以 QQ 邮箱为例):
go
运行
package configimport ("github.com/jordan-wright/email""net/smtp"
)// 邮件配置(需替换为你的实际信息)
const (EmailHost = "smtp.qq.com" // QQ邮箱SMTP服务器EmailPort = 587 // SMTP端口EmailUsername = "你的QQ邮箱@qq.com" // 邮箱账号EmailPassword = "你的SMTP授权码" // QQ邮箱需生成“SMTP授权码”(非登录密码)EmailFrom = "你的QQ邮箱@qq.com" // 发件人地址
)// SendEmail 发送邮件
func SendEmail(to, subject, body string) error {e := email.NewEmail()e.From = EmailFrome.To = []string{to}e.Subject = subjecte.Text = []byte(body)return e.Send(EmailHost+":"+string(EmailPort), smtp.PlainAuth("", EmailUsername, EmailPassword, EmailHost))
}
步骤 3:添加定时任务(修改 main.go
)
导入依赖并启动定时任务:
go
运行
package mainimport ("aimemo/config""aimemo/model""aimemo/service""time""github.com/gin-gonic/gin""github.com/robfig/cron/v3" // 新增:定时任务库"net/http""strconv""fmt" // 新增:用于日志打印
)func main() {// ... 原有初始化代码(数据库、Gin引擎)...// 启动定时任务cronJob := cron.New()// 每天9:00执行(Cron表达式:"0 9 * * *")_, err := cronJob.AddFunc("0 9 * * *", checkDueTasks)if err != nil {panic("定时任务创建失败:" + err.Error())}cronJob.Start()// ... 原有路由代码 ...// 启动服务r.Run(":8080")
}// checkDueTasks 检查当天到期的任务并发送邮件
func checkDueTasks() {var tasks []model.TasktodayEnd := time.Now().Truncate(24 * time.Hour).Add(23*time.Hour + 59*time.Minute + 59*time.Second)config.DB.Where("due_time <= ?", todayEnd).Find(&tasks)for _, task := range tasks {userEmail := "接收提醒的邮箱@xxx.com" // 需替换为实际用户邮箱(可扩展User模型存储)subject := "任务到期提醒:" + task.Titlebody := fmt.Sprintf(`任务标题:%s详情:%s截止时间:%sAI规划:%sAI建议:%s`, task.Title, task.Content, task.DueTime.Format("2006-01-02 15:04"), task.AIPlan, task.AIAdvice)if err := config.SendEmail(userEmail, subject, body); err != nil {fmt.Printf("邮件发送失败,任务ID:%d,错误:%v\n", task.ID, err)}}
}
步骤 4:测试定时任务(可选)
若需快速测试,临时修改 Cron 表达式为 */1 * * * *
(每分钟执行一次),等待 1 分钟后查看邮箱是否收到提醒,测试后改回 0 9 * * *
。
功能扩展四:任务导出为 Excel
目标:添加按钮,将任务列表导出为 Excel 文件。
步骤 1:安装 Excel 依赖
bash
go get github.com/xuri/excelize/v2
步骤 2:后端添加导出路由(修改 main.go
)
新增导出 Excel 的路由:
go
运行
// 6. 导出任务为Excel
r.GET("/task/export", func(c *gin.Context) {var tasks []model.Taskconfig.DB.Order("created_at desc").Find(&tasks)f := excelize.NewFile()defer f.Close()// 设置表头f.SetCellValue("Sheet1", "A1", "ID")f.SetCellValue("Sheet1", "B1", "标题")f.SetCellValue("Sheet1", "C1", "详情")f.SetCellValue("Sheet1", "D1", "截止时间")f.SetCellValue("Sheet1", "E1", "状态")f.SetCellValue("Sheet1", "F1", "AI规划")f.SetCellValue("Sheet1", "G1", "AI建议")f.SetCellValue("Sheet1", "H1", "创建时间")// 填充任务数据for i, task := range tasks {row := i + 2f.SetCellValue("Sheet1", fmt.Sprintf("A%d", row), task.ID)f.SetCellValue("Sheet1", fmt.Sprintf("B%d", row), task.Title)f.SetCellValue("Sheet1", fmt.Sprintf("C%d", row), task.Content)f.SetCellValue("Sheet1", fmt.Sprintf("D%d", row), task.DueTime.Format("2006-01-02 15:04"))f.SetCellValue("Sheet1", fmt.Sprintf("E%d", row), task.Status)f.SetCellValue("Sheet1", fmt.Sprintf("F%d", row), task.AIPlan)f.SetCellValue("Sheet1", fmt.Sprintf("G%d", row), task.AIAdvice)f.SetCellValue("Sheet1", fmt.Sprintf("H%d", row), task.CreatedAt.Format("2006-01-02 15:04"))}// 设置下载响应头c.Header("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")c.Header("Content-Disposition", "attachment; filename=tasks.xlsx")if err := f.WriteTo(c.Writer); err != nil {c.JSON(http.StatusInternalServerError, gin.H{"msg": "导出失败"})}
})
步骤 3:前端添加导出按钮(修改 templates/index.html
)
在任务列表标题旁添加按钮:
html
预览
<div class="task-list"><h3>我的任务</h3><button id="exportBtn">导出任务为Excel</button>{{if len .tasks}}<ul>...</ul>{{else}}<p class="empty">暂无任务</p>{{end}}
</div>
步骤 4:前端 JS 处理导出逻辑(修改 static/js/main.js
)
添加导出按钮的点击事件:
javascript
运行
// 5. 导出任务为Excel
const exportBtn = document.getElementById('exportBtn');
if (exportBtn) {exportBtn.addEventListener('click', () => {window.location.href = '/task/export'; // 跳转到导出接口,触发文件下载});
}
步骤 5:添加导出按钮样式(修改 static/css/style.css
)
css
#exportBtn {background-color: #3498db;color: white;border: none;padding: 10px 16px;border-radius: 4px;font-size: 14px;cursor: pointer;margin-bottom: 20px;transition: background 0.3s;
}
#exportBtn:hover {background-color: #2980b9;
}
总结
通过以上步骤,依次实现了任务删除、AI 多轮交互、到期提醒、Excel 导出四大扩展功能。每个功能从后端路由、前端页面、JS 逻辑、样式美化等维度逐步修改,确保功能完整且交互友好。实际部署时,需根据自身环境替换数据库密码、AI 密钥、邮件服务器配置等敏感信息。