feat: 账号导入列表移除块级标题、刷新改图标,新增账号名称列(成功任务回填作者名)

This commit is contained in:
2026-09-23 17:20:20 +08:00
parent 45c61818f3
commit c8c460da82
4 changed files with 73 additions and 9 deletions
+1 -1
View File
@@ -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)
}
+52
View File
@@ -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
+5
View File
@@ -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"`
+15 -8
View File
@@ -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<Job> = [
{
title: '账号名称',
dataIndex: 'author_name',
render: (value?: string, job?: Job) =>
value ? (
<Typography.Text strong>{value}</Typography.Text>
) : (
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
{job?.status === 'failed' ? '解析失败' : '解析中'}
</Typography.Text>
),
},
{
title: '分享链接',
dataIndex: 'share_url',
@@ -167,14 +180,8 @@ export default function Page() {
return (
<Card variant="outlined">
{contextHolder}
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
<div>
<Typography.Title level={5} style={{ marginBottom: 4 }}>分享链接解析任务</Typography.Title>
<Typography.Text type="secondary">待处理和失败的分享链接独立展示;成功后才会进入竞品作者监控。</Typography.Text>
</div>
<Button icon={<ReloadOutlined />} onClick={loadShareJobs} disabled={pending}>
刷新
</Button>
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'flex-end' }}>
<Button icon={<ReloadOutlined />} onClick={loadShareJobs} disabled={pending} aria-label="刷新" />
</div>
{error ? (
<Alert type="error" showIcon style={{ marginBottom: 16 }} title={error.message} action={<Button size="small" onClick={loadShareJobs}>重试</Button>} />