From c8c460da82f178218e3bc981dc942cd48398e143 Mon Sep 17 00:00:00 2001 From: Rogee Date: Wed, 23 Sep 2026 17:20:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=B4=A6=E5=8F=B7=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E7=A7=BB=E9=99=A4=E5=9D=97=E7=BA=A7=E6=A0=87?= =?UTF-8?q?=E9=A2=98=E3=80=81=E5=88=B7=E6=96=B0=E6=94=B9=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=EF=BC=8C=E6=96=B0=E5=A2=9E=E8=B4=A6=E5=8F=B7=E5=90=8D=E7=A7=B0?= =?UTF-8?q?=E5=88=97=EF=BC=88=E6=88=90=E5=8A=9F=E4=BB=BB=E5=8A=A1=E5=9B=9E?= =?UTF-8?q?=E5=A1=AB=E4=BD=9C=E8=80=85=E5=90=8D=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/controlplane/api/creator.go | 2 +- internal/creator/competitor_share_jobs.go | 52 +++++++++++++++++++++++ internal/creator/models.go | 5 +++ web/src/pages/accounts/import.tsx | 23 ++++++---- 4 files changed, 73 insertions(+), 9 deletions(-) diff --git a/internal/controlplane/api/creator.go b/internal/controlplane/api/creator.go index 03acb0a..dc3287e 100644 --- a/internal/controlplane/api/creator.go +++ b/internal/controlplane/api/creator.go @@ -274,7 +274,7 @@ func registerCreatorWithServices(app *fiber.App, store *creator.Store, phaseASto return c.JSON(items) }) app.Get("/api/creator/competitor-share-jobs", func(c fiber.Ctx) error { - items, err := store.ListCompetitorShareJobs(c.Context(), c.Query("platform"), c.Query("status")) + items, err := store.ListCompetitorShareJobsWithAuthor(c.Context(), c.Query("platform"), c.Query("status")) if err != nil { return creatorError(c, err) } diff --git a/internal/creator/competitor_share_jobs.go b/internal/creator/competitor_share_jobs.go index 91288f8..bf64eaf 100644 --- a/internal/creator/competitor_share_jobs.go +++ b/internal/creator/competitor_share_jobs.go @@ -87,6 +87,41 @@ func (s *Store) GetCompetitorShareJob(ctx context.Context, id string) (Competito return result, rowError(err) } +// ListCompetitorShareJobsWithAuthor 附带作者名(join competitor 表),供导入页列表展示。 +func (s *Store) ListCompetitorShareJobsWithAuthor(ctx context.Context, platform, status string) ([]CompetitorShareJobView, error) { + if platform != "" && !ValidatePlatform(platform) || status != "" && !validateCompetitorShareJobStatus(status) { + return nil, ErrInvalid + } + query, args := competitorShareJobFilter(competitorShareJobSelect+` + LEFT JOIN creator_competitor ON creator_competitor.id = creator_competitor_share_job.competitor_id`, platform, status) + rows, err := s.db.QueryContext(ctx, query+` ORDER BY creator_competitor_share_job.created_at DESC, creator_competitor_share_job.id`, args...) + if err != nil { + return nil, databaseError(err) + } + defer rows.Close() + var items []CompetitorShareJobView + for rows.Next() { + var item CompetitorShareJobView + var tags pgtype.FlatArray[string] + var competitorID, nickname sql.NullString + var leaseUntil, lastAttemptAt, completedAt sql.NullTime + if err := rows.Scan(&item.ID, &item.Platform, &item.ShareURL, pgtype.NewMap().SQLScanner(&tags), + &item.Status, &item.Attempts, &competitorID, &item.FailureReason, &leaseUntil, &lastAttemptAt, + &completedAt, &item.CreatedAt, &item.UpdatedAt, &nickname); err != nil { + return nil, databaseError(err) + } + item.Tags = []string(tags) + if competitorID.Valid { + item.CompetitorID = competitorID.String + } + item.LastAttemptAt = nullableTime(lastAttemptAt) + item.CompletedAt = nullableTime(completedAt) + item.AuthorName = nickname.String + items = append(items, item) + } + return items, rows.Err() +} + func validateCompetitorShareJobStatus(status string) bool { switch status { case CompetitorShareJobQueued, CompetitorShareJobProcessing, CompetitorShareJobSucceeded, CompetitorShareJobFailed: @@ -96,6 +131,23 @@ func validateCompetitorShareJobStatus(status string) bool { } } +func competitorShareJobFilter(query, platform, status string) (string, []any) { + conditions := make([]string, 0, 2) + args := make([]any, 0, 2) + if platform != "" { + args = append(args, platform) + conditions = append(conditions, "creator_competitor_share_job.platform = $"+strconv.Itoa(len(args))) + } + if status != "" { + args = append(args, status) + conditions = append(conditions, "creator_competitor_share_job.status = $"+strconv.Itoa(len(args))) + } + if len(conditions) > 0 { + query += ` WHERE ` + strings.Join(conditions, ` AND `) + } + return query, args +} + func (s *Store) ListCompetitorShareJobs(ctx context.Context, platform, status string) ([]CompetitorShareJob, error) { if platform != "" && !ValidatePlatform(platform) || status != "" && !validateCompetitorShareJobStatus(status) { return nil, ErrInvalid diff --git a/internal/creator/models.go b/internal/creator/models.go index ef800d7..5039ef9 100644 --- a/internal/creator/models.go +++ b/internal/creator/models.go @@ -133,6 +133,11 @@ type CompetitorInput struct { Tags []string `json:"tags"` } +type CompetitorShareJobView struct { + CompetitorShareJob + AuthorName string `json:"author_name,omitempty"` +} + type CompetitorShareJob struct { ID string `json:"id"` Platform string `json:"platform"` diff --git a/web/src/pages/accounts/import.tsx b/web/src/pages/accounts/import.tsx index 4e1e1a8..853f005 100644 --- a/web/src/pages/accounts/import.tsx +++ b/web/src/pages/accounts/import.tsx @@ -28,6 +28,7 @@ interface Job { attempts?: number; failure_reason?: string; created_at?: string; + author_name?: string; } interface FormValues { @@ -115,6 +116,18 @@ export default function Page() { } const columns: ColumnsType = [ + { + title: '账号名称', + dataIndex: 'author_name', + render: (value?: string, job?: Job) => + value ? ( + {value} + ) : ( + + {job?.status === 'failed' ? '解析失败' : '解析中'} + + ), + }, { title: '分享链接', dataIndex: 'share_url', @@ -167,14 +180,8 @@ export default function Page() { return ( {contextHolder} -
-
- 分享链接解析任务 - 待处理和失败的分享链接独立展示;成功后才会进入竞品作者监控。 -
- +
+
{error ? ( 重试} />