feat: 账号导入列表移除块级标题、刷新改图标,新增账号名称列(成功任务回填作者名)
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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>} />
|
||||
|
||||
Reference in New Issue
Block a user