feat(background): 添加删除应用功能

- 添加DELETE /apps/:id API端点用于删除指定应用
- 实现deleteApp函数,支持文件和数据的完整删除
- 在getApps函数中增加文件存在性验证,自动清理无效应用记录
- 优化appsMutex使用,确保并发安全的数据操作
- 添加文件路径验证和错误处理机制

feat(frontend): 实现前端删除应用交互功能

- 将deleteApp函数改为异步处理,支持API调用
- 添加确认对话框提升用户体验
- 实现DELETE请求发送和响应处理
- 添加错误处理和用户反馈提示
- 删除成功后自动刷新应用列表
```
This commit is contained in:
2026-01-07 00:05:16 +08:00
parent 20ccfbc205
commit 8240d8cf94
2 changed files with 110 additions and 5 deletions

View File

@@ -129,6 +129,7 @@ func main() {
{ {
api.GET("/apps", getApps) api.GET("/apps", getApps)
api.POST("/apps", uploadApp) api.POST("/apps", uploadApp)
api.DELETE("/apps/:id", deleteApp) // 添加删除应用的API端点
api.GET("/apps/:id", downloadApp) api.GET("/apps/:id", downloadApp)
api.GET("/apps/:id/", downloadApp) // 处理以/结尾的URL api.GET("/apps/:id/", downloadApp) // 处理以/结尾的URL
api.GET("/docs", getDocs) // 新增获取文档的API端点 api.GET("/docs", getDocs) // 新增获取文档的API端点
@@ -143,8 +144,33 @@ func main() {
// 获取App列表 // 获取App列表
func getApps(c *gin.Context) { func getApps(c *gin.Context) {
appsMutex.RLock() appsMutex.Lock()
defer appsMutex.RUnlock() defer appsMutex.Unlock()
// 验证文件是否存在,如果不存在则从切片中删除
appsToKeep := []App{}
appsChanged := false
for _, app := range apps {
filePath := filepath.Join(filesDir, app.FilePath)
if _, err := os.Stat(filePath); os.IsNotExist(err) {
// 文件不存在跳过这个app即从逻辑上删除它
appsChanged = true
fmt.Printf("文件不存在,从列表中移除: %s, 文件路径: %s\n", app.Name, filePath)
} else {
// 文件存在保留这个app
appsToKeep = append(appsToKeep, app)
}
}
if appsChanged {
// 更新apps切片
apps = appsToKeep
// 保存到文件
if err := saveApps(); err != nil {
fmt.Printf("保存apps.json失败: %v\n", err)
}
}
c.JSON(http.StatusOK, apps) c.JSON(http.StatusOK, apps)
} }
@@ -242,6 +268,57 @@ func downloadApp(c *gin.Context) {
c.FileAttachment(filePath, targetApp.FileName) c.FileAttachment(filePath, targetApp.FileName)
} }
// 删除指定App
func deleteApp(c *gin.Context) {
// 获取ID
id := c.Param("id")
// 查找App
appsMutex.Lock()
var targetApp *App
targetIndex := -1
for i := range apps {
if apps[i].ID == id {
targetApp = &apps[i]
targetIndex = i
break
}
}
appsMutex.Unlock()
if targetApp == nil {
c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "App不存在"})
return
}
// 构建文件路径
filePath := filepath.Join(filesDir, targetApp.FilePath)
// 删除文件
if err := os.Remove(filePath); err != nil {
if os.IsNotExist(err) {
// 文件不存在,可能已经被删除,继续执行
fmt.Printf("文件已经不存在: %s\n", filePath)
} else {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "删除文件失败: " + err.Error()})
return
}
}
// 从apps切片中删除该应用
appsMutex.Lock()
apps = append(apps[:targetIndex], apps[targetIndex+1:]...)
appsMutex.Unlock()
// 保存到文件
if err := saveApps(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "保存数据失败: " + err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"success": true, "message": "删除成功"})
}
// 读取文档内容 // 读取文档内容
func readDocFile(docName string) (string, error) { func readDocFile(docName string) (string, error) {
filePath := filepath.Join(docsDir, docName+".txt") filePath := filepath.Join(docsDir, docName+".txt")

View File

@@ -238,11 +238,39 @@ async function uploadApp() {
} }
// 删除App函数 // 删除App函数
function deleteApp(appId) { async function deleteApp(appId) {
// 显示确认对话框,包含特殊问题验证 // 显示确认对话框
if (!confirm('如果确定删除此APP,请将矩阵 A = [[3,1,0],[1,2,1],[0,1,1]]的特征值、特征向量并验证A是正定矩阵的解题步骤做出来然后我也没做')) { if (!confirm('确定删除这个应用吗?此操作不可撤销!')) {
return; return;
} }
try {
// 发送DELETE请求删除App
const response = await fetch(`${API_BASE_URL}/apps/${appId}`, {
method: 'DELETE',
headers: {
'Accept': 'application/json'
}
});
// 检查响应状态
if (!response.ok) {
throw new Error(`HTTP错误状态${response.status}`);
}
const result = await response.json(); // 解析响应数据
// 处理删除结果
if (result.success) {
alert('删除成功!');
loadAppList(); // 重新加载App列表
} else {
alert('删除失败:' + (result.message || '未知错误'));
}
} catch (error) {
console.error('删除失败:', error);
alert('删除失败:' + error.message);
}
} }
// 下载App函数 // 下载App函数