diff --git a/CHANGELOG.md b/CHANGELOG.md index e25b146f..cdcf1d23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * 功能优化:失败的任务自动退回算力,而不需要在删除的时候再退回 * 功能新增:支持设置一个专门的模型来翻译提示词,提供 Mate 提示词生成功能 * Bug修复:修复图片对话的时候,上下文不起作用的Bug +* 功能新增:管理后台新增批量导出兑换码功能 ## v4.1.6 * 功能新增:**支持OpenAI实时语音对话功能** :rocket: :rocket: :rocket:, Beta 版,目前没有做算力计费控制,目前只有 VIP 用户可以使用。 diff --git a/api/handler/admin/redeem_handler.go b/api/handler/admin/redeem_handler.go index 3bce461c..b2559a9c 100644 --- a/api/handler/admin/redeem_handler.go +++ b/api/handler/admin/redeem_handler.go @@ -8,6 +8,8 @@ package admin // * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ import ( + "encoding/csv" + "fmt" "geekai/core" "geekai/core/types" "geekai/handler" @@ -35,12 +37,10 @@ func (h *RedeemHandler) List(c *gin.Context) { session := h.DB.Session(&gorm.Session{}) if code != "" { - session.Where("code LIKE ?", "%"+code+"%") + session = session.Where("code LIKE ?", "%"+code+"%") } - if status == 0 { - session.Where("redeem_at = ?", 0) - } else if status == 1 { - session.Where("redeem_at > ?", 0) + if status >= 0 { + session = session.Where("redeemed_at", status) } var total int64 @@ -80,6 +80,65 @@ func (h *RedeemHandler) List(c *gin.Context) { resp.SUCCESS(c, vo.NewPage(total, page, pageSize, items)) } +// Export 导出 CVS 文件 +func (h *RedeemHandler) Export(c *gin.Context) { + var data struct { + Status int `json:"status"` + Ids []int `json:"ids"` + } + if err := c.ShouldBindJSON(&data); err != nil { + resp.ERROR(c, types.InvalidArgs) + } + + session := h.DB.Session(&gorm.Session{}) + if data.Status >= 0 { + session = session.Where("redeemed_at", data.Status) + } + if len(data.Ids) > 0 { + session = session.Where("id IN ?", data.Ids) + } + + var items []model.Redeem + err := session.Order("id DESC").Find(&items).Error + if err != nil { + resp.ERROR(c, err.Error()) + return + } + + // 设置响应头,告诉浏览器这是一个附件,需要下载 + c.Header("Content-Disposition", "attachment; filename=output.csv") + c.Header("Content-Type", "text/csv") + + // 创建一个 CSV writer + writer := csv.NewWriter(c.Writer) + + // 写入 CSV 文件的标题行 + headers := []string{"名称", "兑换码", "算力", "创建时间"} + if err := writer.Write(headers); err != nil { + resp.ERROR(c, err.Error()) + return + } + + // 写入数据行 + records := make([][]string, 0) + for _, item := range items { + records = append(records, []string{item.Name, item.Code, fmt.Sprintf("%d", item.Power), item.CreatedAt.Format("2006-01-02 15:04:05")}) + } + for _, record := range records { + if err := writer.Write(record); err != nil { + resp.ERROR(c, err.Error()) + return + } + } + + // 确保所有数据都已写入响应 + writer.Flush() + if err := writer.Error(); err != nil { + resp.ERROR(c, err.Error()) + return + } +} + func (h *RedeemHandler) Create(c *gin.Context) { var data struct { Name string `json:"name"` diff --git a/api/main.go b/api/main.go index 50e748f1..843f17ca 100644 --- a/api/main.go +++ b/api/main.go @@ -350,6 +350,7 @@ func main() { group.POST("create", h.Create) group.POST("set", h.Set) group.GET("remove", h.Remove) + group.POST("export", h.Export) }), fx.Invoke(func(s *core.AppServer, h *admin.DashboardHandler) { group := s.Engine.Group("/api/admin/dashboard/") diff --git a/web/src/assets/css/admin/form.styl b/web/src/assets/css/admin/form.styl index dd3e4638..cdc4787d 100644 --- a/web/src/assets/css/admin/form.styl +++ b/web/src/assets/css/admin/form.styl @@ -3,7 +3,7 @@ display flex width 100% - .el-input,.el-select,.el-switch { + .el-input, .el-select, .el-switch { margin-right 10px } diff --git a/web/src/assets/css/main.styl b/web/src/assets/css/main.styl index b275eb11..4a80af43 100644 --- a/web/src/assets/css/main.styl +++ b/web/src/assets/css/main.styl @@ -172,6 +172,22 @@ body { } } +.mr-1 { + margin-right 5px +} + +.mr-2 { + margin-right 10px +} + +.ml-1 { + margin-left 5px +} + +.ml-2 { + margin-left 10px +} + diff --git a/web/src/utils/http.js b/web/src/utils/http.js index efac3d5a..4b9fe091 100644 --- a/web/src/utils/http.js +++ b/web/src/utils/http.js @@ -6,12 +6,12 @@ // * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ import axios from 'axios' -import {getAdminToken, getSessionId, getUserToken, removeAdminToken, removeUserToken} from "@/store/session"; +import {getAdminToken, getUserToken, removeAdminToken, removeUserToken} from "@/store/session"; axios.defaults.timeout = 180000 axios.defaults.baseURL = process.env.VUE_APP_API_HOST axios.defaults.withCredentials = true; -axios.defaults.headers.post['Content-Type'] = 'application/json' +//axios.defaults.headers.post['Content-Type'] = 'application/json' // HTTP拦截器 axios.interceptors.request.use( @@ -81,4 +81,19 @@ export function httpDownload(url) { reject(err) }) }) +} + +export function httpPostDownload(url, data) { + return new Promise((resolve, reject) => { + axios({ + method: 'POST', + url: url, + data: data, + responseType: 'blob' // 将响应类型设置为 `blob` + }).then(response => { + resolve(response) + }).catch(err => { + reject(err) + }) + }) } \ No newline at end of file diff --git a/web/src/views/Luma.vue b/web/src/views/Luma.vue index edce7526..b9bdacdd 100644 --- a/web/src/views/Luma.vue +++ b/web/src/views/Luma.vue @@ -5,7 +5,9 @@ - + + + @@ -39,25 +41,25 @@ - + 生成AI视频提示词 循环参考图 - + 提示词优化 - + - 你的作品 @@ -67,33 +69,35 @@ - + 您的浏览器不支持视频播放 - - + + - 任务执行失败:{{item.err_msg}},任务提示词:{{item.prompt}} - {{item.prompt}} + + 任务执行失败:{{ item.err_msg }},任务提示词:{{ item.prompt }} + + {{ item.prompt }} 发布 - + - + @@ -111,38 +115,39 @@ - + + layout="total,prev, pager, next" + :hide-on-single-page="true" + v-model:current-page="page" + v-model:page-size="pageSize" + @current-change="fetchData(page)" + :total="total"/> - + 您的浏览器不支持视频播放 - + -