Files
goauto/server/app/goauto/task/admin_handler.go
T
QiuSWandClaude Opus 5 03647dd7dd feat(web): 采集任务页增加来源筛选与「取消未开始的任务」按钮 (#298)
页面混显三种来源却只有 goodsId 和状态两个筛选,批量取消时无法限定范围。

- 服务端 AdminList 增加 source 过滤,非法值报参数错误而非静默忽略。
- 前端增加来源筛选与来源列;状态补 cancelled(info 色,取消不是错误,不与
  failed 共用红色)。
- 「取消未开始的任务」按钮带实时数量,确认框列出将被取消的任务,并写明范围是
  整个筛选条件而非当前页。hasMore 时提示还有未处理的任务。

`[必须]` 取消范围包含 goodsId。少了这一维,按 goods_id 筛出两条、按钮却取消
三十几条——那正是当初放弃「两个固定按钮」、改用「筛选 + 一个按钮」想避免的事。
BatchCancel 的 goodsId 与 AdminList 用同一种匹配方式,否则两边范围会悄悄错开。

实施:sonnet 子代理;goodsId 范围一致性由复核补入。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-16 16:39:07 +08:00

235 lines
5.8 KiB
Go

package task
import (
"encoding/json"
"errors"
"io"
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
)
func (handler Handler) AdminList(c *gin.Context) {
page, err := positiveQuery(c.Query("page"), 1)
if err != nil {
writeError(c, serviceError("INVALID_REQUEST", "page 无效"))
return
}
pageSize, err := positiveQuery(c.Query("pageSize"), 20)
if err != nil {
writeError(c, serviceError("INVALID_REQUEST", "pageSize 无效"))
return
}
deviceID, err := optionalUint(c.Query("deviceId"))
if err != nil {
writeError(c, serviceError("INVALID_REQUEST", "deviceId 无效"))
return
}
service, _, ok := handler.service(c)
if !ok {
return
}
response, err := service.AdminList(c.Request.Context(), AdminListRequest{
Page: page, PageSize: pageSize, Status: strings.TrimSpace(c.Query("status")),
GoodsID: strings.TrimSpace(c.Query("goodsId")), Source: strings.TrimSpace(c.Query("source")), DeviceID: deviceID,
})
if err != nil {
writeError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
}
func (handler Handler) AdminCreate(c *gin.Context) {
var request CreateRequest
if err := decodeStrict(c, &request); err != nil {
writeError(c, serviceError("INVALID_REQUEST", "请求 JSON 无效"))
return
}
service, _, ok := handler.service(c)
if !ok {
return
}
response, err := service.Create(c.Request.Context(), request)
if err != nil {
writeError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
}
func (handler Handler) AdminBatchCreate(c *gin.Context) {
var request BatchCreateRequest
if err := decodeStrict(c, &request); err != nil {
writeError(c, serviceError("INVALID_REQUEST", "请求 JSON 无效"))
return
}
service, _, ok := handler.service(c)
if !ok {
return
}
response, err := service.BatchCreate(c.Request.Context(), request)
if err != nil {
writeError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
}
func (handler Handler) AdminBatchCreateImageSearch(c *gin.Context) {
var request ImageSearchBatchRequest
if err := decodeStrict(c, &request); err != nil {
writeError(c, serviceError("INVALID_REQUEST", "请求 JSON 无效"))
return
}
service, _, ok := handler.service(c)
if !ok {
return
}
response, err := service.BatchCreateImageSearch(c.Request.Context(), request)
if err != nil {
writeError(c, err)
return
}
c.Header("Cache-Control", "no-store")
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
}
func (handler Handler) AdminDetail(c *gin.Context) {
id, err := taskID(c)
if err != nil || id == 0 {
writeError(c, serviceError("INVALID_REQUEST", "taskId 无效"))
return
}
service, _, ok := handler.service(c)
if !ok {
return
}
response, err := service.Detail(c.Request.Context(), id)
if err != nil {
writeError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
}
func (handler Handler) AdminReset(c *gin.Context) {
id, err := taskID(c)
if err != nil || id == 0 {
writeError(c, serviceError("INVALID_REQUEST", "taskId 无效"))
return
}
var request ActionRequest
if err := decodeStrict(c, &request); err != nil {
writeError(c, serviceError("INVALID_REQUEST", "请求 JSON 无效"))
return
}
service, _, ok := handler.service(c)
if !ok {
return
}
response, err := service.Reset(c.Request.Context(), id, request)
if err != nil {
writeError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
}
func (handler Handler) AdminDelete(c *gin.Context) {
id, err := taskID(c)
if err != nil || id == 0 {
writeError(c, serviceError("INVALID_REQUEST", "taskId 无效"))
return
}
var request ActionRequest
if err := decodeStrict(c, &request); err != nil {
writeError(c, serviceError("INVALID_REQUEST", "请求 JSON 无效"))
return
}
service, _, ok := handler.service(c)
if !ok {
return
}
response, err := service.DeleteFailed(c.Request.Context(), id, request)
if err != nil {
writeError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
}
func (handler Handler) AdminCancel(c *gin.Context) {
id, err := taskID(c)
if err != nil || id == 0 {
writeError(c, serviceError("INVALID_REQUEST", "taskId 无效"))
return
}
var request ActionRequest
if err := decodeStrict(c, &request); err != nil {
writeError(c, serviceError("INVALID_REQUEST", "请求 JSON 无效"))
return
}
service, _, ok := handler.service(c)
if !ok {
return
}
response, err := service.Cancel(c.Request.Context(), id, request)
if err != nil {
writeError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
}
func (handler Handler) AdminBatchCancel(c *gin.Context) {
var request BatchCancelRequest
if err := decodeStrict(c, &request); err != nil {
writeError(c, serviceError("INVALID_REQUEST", "请求 JSON 无效"))
return
}
service, _, ok := handler.service(c)
if !ok {
return
}
response, err := service.BatchCancel(c.Request.Context(), request)
if err != nil {
writeError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
}
func decodeStrict(c *gin.Context, destination any) error {
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 1<<20)
decoder := json.NewDecoder(c.Request.Body)
decoder.DisallowUnknownFields()
if err := decoder.Decode(destination); err != nil {
return err
}
if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) {
return errors.New("one object required")
}
return nil
}
func positiveQuery(raw string, fallback int) (int, error) {
if strings.TrimSpace(raw) == "" {
return fallback, nil
}
value, err := strconv.Atoi(raw)
if err != nil || value < 1 {
return 0, errors.New("invalid positive integer")
}
return value, nil
}
func optionalUint(raw string) (uint64, error) {
if strings.TrimSpace(raw) == "" {
return 0, nil
}
return strconv.ParseUint(raw, 10, 64)
}