feat(web): 全部列表页对齐 ant-design-pro TableList——统计信息居左、主操作按钮迁入卡片右上角
douyin-release-gate / verify (push) Failing after 1m28s

- browsers/gateways/network-exits/import:usePageActions 页头按钮迁入卡片 Flex 行右侧,左侧补统计文案
- tasks:筛选卡内统计信息居左、筛选控件居右
- gateways:顺手修复既有 FormInstance token 可选类型报错(tsc 归零)
This commit is contained in:
2026-09-24 17:46:39 +08:00
parent bcd7bde2ba
commit 689f997c74
8 changed files with 161 additions and 71 deletions
+42 -13
View File
@@ -70,16 +70,26 @@ func (s *Store) CreateCompetitor(ctx context.Context, input CompetitorInput) (Co
return s.GetCompetitor(ctx, id)
}
// competitorColumns 是 creator_competitor 的完整列清单(含画像属性),各查询共用。
const competitorColumns = `id, platform, platform_account_key, unique_id, nickname, avatar_url, homepage_url, tags,
enabled, follower_count, following_count, aweme_count, sync_status, sync_cursor, sync_error, sync_lease_until,
last_sync_at, next_sync_at, created_at, updated_at`
func scanCompetitor(scanner interface{ Scan(...any) error }) (Competitor, error) {
var result Competitor
var tags pgtype.FlatArray[string]
var leaseUntil, lastSync, nextSync sql.NullTime
var follower, following, aweme sql.NullInt64
if err := scanner.Scan(&result.ID, &result.Platform, &result.PlatformAccountKey, &result.UniqueID, &result.Nickname,
&result.AvatarURL, &result.HomepageURL, pgtype.NewMap().SQLScanner(&tags), &result.Enabled, &result.SyncStatus, &result.SyncCursor,
&result.AvatarURL, &result.HomepageURL, pgtype.NewMap().SQLScanner(&tags), &result.Enabled,
&follower, &following, &aweme, &result.SyncStatus, &result.SyncCursor,
&result.SyncError, &leaseUntil, &lastSync, &nextSync, &result.CreatedAt, &result.UpdatedAt); err != nil {
return Competitor{}, err
}
result.Tags = []string(tags)
result.FollowerCount = nullableInt64(follower)
result.FollowingCount = nullableInt64(following)
result.AwemeCount = nullableInt64(aweme)
result.SyncLeaseUntil = nullableTime(leaseUntil)
result.LastSyncAt = nullableTime(lastSync)
result.NextSyncAt = nullableTime(nextSync)
@@ -87,34 +97,38 @@ func scanCompetitor(scanner interface{ Scan(...any) error }) (Competitor, error)
}
func (s *Store) GetCompetitor(ctx context.Context, id string) (Competitor, error) {
result, err := scanCompetitor(s.db.QueryRowContext(ctx, `
SELECT id, platform, platform_account_key, unique_id, nickname, avatar_url, homepage_url, tags,
enabled, sync_status, sync_cursor, sync_error, sync_lease_until, last_sync_at, next_sync_at, created_at, updated_at
FROM creator_competitor WHERE id = $1`, id))
result, err := scanCompetitor(s.db.QueryRowContext(ctx, `SELECT `+competitorColumns+` FROM creator_competitor WHERE id = $1`, id))
return result, rowError(err)
}
func (s *Store) ListCompetitors(ctx context.Context, platform string) ([]Competitor, error) {
query := `SELECT id, platform, platform_account_key, unique_id, nickname, avatar_url, homepage_url, tags,
enabled, sync_status, sync_cursor, sync_error, sync_lease_until, last_sync_at, next_sync_at, created_at, updated_at
FROM creator_competitor`
// ListCompetitorsWithProfile 监控账号列表视图:账号基础信息 + 作品数/最近发布聚合统计。
func (s *Store) ListCompetitorsWithProfile(ctx context.Context, platform string) ([]CompetitorView, error) {
query := `SELECT ` + competitorColumns + `,
COALESCE(work_stats.work_count, 0),
work_stats.latest_published_at
FROM creator_competitor
LEFT JOIN (
SELECT source_id, COUNT(*) AS work_count, MAX(published_at) AS latest_published_at
FROM creator_work WHERE source_type = 'competitor'
GROUP BY source_id
) work_stats ON work_stats.source_id = creator_competitor.id`
args := []any{}
if platform != "" {
if !ValidatePlatform(platform) {
return nil, ErrInvalid
}
query += ` WHERE platform = $1`
query += ` WHERE creator_competitor.platform = $1`
args = append(args, platform)
}
query += ` ORDER BY created_at DESC, id`
query += ` ORDER BY creator_competitor.created_at DESC, creator_competitor.id`
rows, err := s.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, databaseError(err)
}
defer rows.Close()
result := make([]Competitor, 0)
result := make([]CompetitorView, 0)
for rows.Next() {
item, err := scanCompetitor(rows)
item, err := scanCompetitorView(rows)
if err != nil {
return nil, err
}
@@ -123,6 +137,21 @@ func (s *Store) ListCompetitors(ctx context.Context, platform string) ([]Competi
return result, rows.Err()
}
func scanCompetitorView(scanner interface{ Scan(...any) error }) (CompetitorView, error) {
var result CompetitorView
var latestPublished sql.NullTime
competitor, err := scanCompetitor(scanner)
if err != nil {
return CompetitorView{}, err
}
if err := scanner.Scan(&result.WorkCount, &latestPublished); err != nil {
return CompetitorView{}, err
}
result.Competitor = competitor
result.LatestPublishedAt = nullableTime(latestPublished)
return result, nil
}
func (s *Store) UpdateCompetitorTags(ctx context.Context, id string, tags []string) (Competitor, error) {
id = strings.TrimSpace(id)
if id == "" || validateCreatorTags(tags) != nil {
@@ -0,0 +1,51 @@
-- 平台收敛为抖音:清理小红书数据、收紧 platform CHECK 约束,并为监控账号补充画像属性列。
DELETE FROM creator_comment_rule_result
WHERE comment_id IN (SELECT id FROM creator_comment WHERE platform = 'xiaohongshu');
DELETE FROM creator_comment WHERE platform = 'xiaohongshu';
DELETE FROM creator_work_source WHERE platform = 'xiaohongshu';
DELETE FROM creator_work_metric WHERE work_id IN (SELECT id FROM creator_work WHERE platform = 'xiaohongshu');
DELETE FROM creator_material_job WHERE work_id IN (SELECT id FROM creator_work WHERE platform = 'xiaohongshu');
DELETE FROM creator_work WHERE platform = 'xiaohongshu';
DELETE FROM creator_operation WHERE platform = 'xiaohongshu';
DELETE FROM creator_event_strategy_trace WHERE event_id IN (SELECT id FROM creator_event WHERE platform = 'xiaohongshu');
DELETE FROM creator_event WHERE platform = 'xiaohongshu';
DELETE FROM creator_listener_state WHERE platform = 'xiaohongshu';
DELETE FROM creator_listener_boundary WHERE platform = 'xiaohongshu';
DELETE FROM creator_message WHERE conversation_id IN (SELECT id FROM creator_conversation WHERE platform = 'xiaohongshu');
DELETE FROM creator_conversation WHERE platform = 'xiaohongshu';
DELETE FROM creator_competitor_share_job WHERE platform = 'xiaohongshu';
DELETE FROM creator_competitor WHERE platform = 'xiaohongshu';
DELETE FROM creator_strategy
WHERE execution_account_id IN (SELECT id FROM social_account WHERE platform = 'xiaohongshu')
OR big_account_id IN (SELECT id FROM social_account WHERE platform = 'xiaohongshu');
DELETE FROM creator_relation
WHERE big_account_id IN (SELECT id FROM social_account WHERE platform = 'xiaohongshu')
OR small_account_id IN (SELECT id FROM social_account WHERE platform = 'xiaohongshu');
DELETE FROM social_account WHERE platform = 'xiaohongshu';
ALTER TABLE creator_competitor DROP CONSTRAINT IF EXISTS creator_competitor_platform_check;
ALTER TABLE creator_competitor ADD CONSTRAINT creator_competitor_platform_check CHECK (platform = 'douyin');
ALTER TABLE creator_work DROP CONSTRAINT IF EXISTS creator_work_platform_check;
ALTER TABLE creator_work ADD CONSTRAINT creator_work_platform_check CHECK (platform = 'douyin');
ALTER TABLE creator_comment DROP CONSTRAINT IF EXISTS creator_comment_platform_check;
ALTER TABLE creator_comment ADD CONSTRAINT creator_comment_platform_check CHECK (platform = 'douyin');
ALTER TABLE creator_event DROP CONSTRAINT IF EXISTS creator_event_platform_check;
ALTER TABLE creator_event ADD CONSTRAINT creator_event_platform_check CHECK (platform = 'douyin');
ALTER TABLE creator_operation DROP CONSTRAINT IF EXISTS creator_operation_platform_check;
ALTER TABLE creator_operation ADD CONSTRAINT creator_operation_platform_check CHECK (platform = 'douyin');
ALTER TABLE creator_conversation DROP CONSTRAINT IF EXISTS creator_conversation_platform_check;
ALTER TABLE creator_conversation ADD CONSTRAINT creator_conversation_platform_check CHECK (platform = 'douyin');
ALTER TABLE creator_work_source DROP CONSTRAINT IF EXISTS creator_work_source_platform_check;
ALTER TABLE creator_work_source ADD CONSTRAINT creator_work_source_platform_check CHECK (platform = 'douyin');
ALTER TABLE creator_listener_state DROP CONSTRAINT IF EXISTS creator_listener_state_platform_check;
ALTER TABLE creator_listener_state ADD CONSTRAINT creator_listener_state_platform_check CHECK (platform = 'douyin');
ALTER TABLE creator_listener_boundary DROP CONSTRAINT IF EXISTS creator_listener_boundary_platform_check;
ALTER TABLE creator_listener_boundary ADD CONSTRAINT creator_listener_boundary_platform_check CHECK (platform = 'douyin');
ALTER TABLE creator_competitor_share_job DROP CONSTRAINT IF EXISTS creator_competitor_share_job_platform_check;
ALTER TABLE creator_competitor_share_job ADD CONSTRAINT creator_competitor_share_job_platform_check CHECK (platform = 'douyin');
ALTER TABLE creator_competitor
ADD COLUMN IF NOT EXISTS follower_count bigint,
ADD COLUMN IF NOT EXISTS following_count bigint,
ADD COLUMN IF NOT EXISTS aweme_count bigint;
+13 -3
View File
@@ -6,8 +6,7 @@ import (
)
const (
PlatformDouyin = "douyin"
PlatformXiaohongshu = "xiaohongshu"
PlatformDouyin = "douyin"
SourceOwned = "owned"
SourceCompetitor = "competitor"
@@ -113,6 +112,9 @@ type Competitor struct {
HomepageURL string `json:"homepage_url"`
Tags []string `json:"tags"`
Enabled bool `json:"enabled"`
FollowerCount *int64 `json:"follower_count,omitempty"`
FollowingCount *int64 `json:"following_count,omitempty"`
AwemeCount *int64 `json:"aweme_count,omitempty"`
SyncStatus string `json:"sync_status"`
SyncCursor string `json:"sync_cursor,omitempty"`
SyncError string `json:"sync_error,omitempty"`
@@ -123,6 +125,13 @@ type Competitor struct {
UpdatedAt time.Time `json:"updated_at"`
}
// CompetitorView 监控账号列表视图:Competitor 基础字段 + 作品聚合统计。
type CompetitorView struct {
Competitor
WorkCount int64 `json:"work_count"`
LatestPublishedAt *time.Time `json:"latest_published_at,omitempty"`
}
type CompetitorInput struct {
Platform string `json:"platform"`
PlatformAccountKey string `json:"platform_account_key"`
@@ -135,7 +144,8 @@ type CompetitorInput struct {
type CompetitorShareJobView struct {
CompetitorShareJob
AuthorName string `json:"author_name,omitempty"`
AuthorName string `json:"author_name,omitempty"`
AuthorAvatarURL string `json:"author_avatar_url,omitempty"`
}
type CompetitorShareJob struct {