feat(web): 全部列表页对齐 ant-design-pro TableList——统计信息居左、主操作按钮迁入卡片右上角
douyin-release-gate / verify (push) Failing after 1m28s
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:
+42
-13
@@ -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;
|
||||
@@ -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 {
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
Flex,
|
||||
Form,
|
||||
Input,
|
||||
Modal,
|
||||
@@ -17,7 +18,6 @@ import {
|
||||
import { PlusOutlined, ReloadOutlined } from '@ant-design/icons';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { create, getList, creatorAction } from '@/services/api';
|
||||
import { usePageActions } from '@/components/PageActions';
|
||||
import { conflictMessage, dateTime, extractShareURL, platformForShareURL, platformLabel, shareJobStatus } from '@/utils/helpers';
|
||||
|
||||
interface Job {
|
||||
@@ -180,18 +180,18 @@ export default function Page() {
|
||||
},
|
||||
];
|
||||
|
||||
usePageActions(
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => setCreateOpen(true)}>
|
||||
新建导入任务
|
||||
</Button>,
|
||||
);
|
||||
|
||||
return (
|
||||
<Card variant="outlined">
|
||||
{contextHolder}
|
||||
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Button icon={<ReloadOutlined />} onClick={loadShareJobs} disabled={pending} aria-label="刷新" />
|
||||
</div>
|
||||
<Flex justify="space-between" align="center" style={{ marginBottom: 16 }}>
|
||||
<Typography.Text type="secondary">共 {jobs.length} 个导入任务</Typography.Text>
|
||||
<Space>
|
||||
<Button icon={<ReloadOutlined />} onClick={loadShareJobs} disabled={pending} aria-label="刷新" />
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => setCreateOpen(true)}>
|
||||
新建导入任务
|
||||
</Button>
|
||||
</Space>
|
||||
</Flex>
|
||||
{error ? (
|
||||
<Alert type="error" showIcon style={{ marginBottom: 16 }} title={error.message} action={<Button size="small" onClick={loadShareJobs}>重试</Button>} />
|
||||
) : null}
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
// 动作保持显式动词(start/stop/recycle),启停禁用条件与归档版一致。
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { history } from '@umijs/max';
|
||||
import { Alert, Button, Card, Popconfirm, Space, Table, Tag, Typography, message } from 'antd';
|
||||
import { Alert, Button, Card, Flex, Popconfirm, Space, Table, Tag, Typography, message } from 'antd';
|
||||
import { CopyOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { browserAction, getList } from '@/services/api';
|
||||
import { usePageActions } from '@/components/PageActions';
|
||||
|
||||
const stateText: Record<string, string> = {
|
||||
created: '已创建',
|
||||
@@ -160,15 +159,15 @@ export default function Page() {
|
||||
},
|
||||
];
|
||||
|
||||
usePageActions(
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => history.push('/browsers/new')}>
|
||||
创建环境
|
||||
</Button>,
|
||||
);
|
||||
|
||||
return (
|
||||
<Card variant="outlined">
|
||||
{contextHolder}
|
||||
<Flex justify="space-between" align="center" style={{ marginBottom: 16 }}>
|
||||
<Typography.Text type="secondary">共 {runtimes.length} 个运行环境</Typography.Text>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => history.push('/browsers/new')}>
|
||||
创建环境
|
||||
</Button>
|
||||
</Flex>
|
||||
{error ? (
|
||||
<Alert type="error" showIcon style={{ marginBottom: 16 }} title={error.message} action={<Button size="small" onClick={load}>重试</Button>} />
|
||||
) : null}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// 网关管理:语义对齐 web.archived GatewaysPage.jsx(注册/编辑 Modal、令牌显隐复制、删除)。
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Alert, Button, Card, Form, Input, Modal, Popconfirm, Space, Table, Typography, message } from 'antd';
|
||||
import { Alert, Button, Card, Flex, Form, Input, Modal, Popconfirm, Space, Table, Typography, message } from 'antd';
|
||||
import { CopyOutlined, EyeInvisibleOutlined, EyeOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { create, getList, remove, update } from '@/services/api';
|
||||
import { conflictMessage } from '@/utils/helpers';
|
||||
import { usePageActions } from '@/components/PageActions';
|
||||
|
||||
const namePattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
||||
const tokenPattern = /^[A-Za-z0-9][A-Za-z0-9._-]{15,127}$/;
|
||||
@@ -23,7 +22,7 @@ function GatewayFormModal({
|
||||
busy: boolean;
|
||||
error: any;
|
||||
onCancel: () => void;
|
||||
onSubmit: (values: { name: string; endpoint: string; token: string }) => Promise<boolean>;
|
||||
onSubmit: (values: { name: string; endpoint: string; token?: string }) => Promise<boolean>;
|
||||
}) {
|
||||
const editing = initial !== null;
|
||||
const [form] = Form.useForm<{ name: string; endpoint: string; token?: string }>();
|
||||
@@ -99,7 +98,7 @@ export default function Page() {
|
||||
load();
|
||||
}, [load]);
|
||||
|
||||
async function createGateway(values: { name: string; endpoint: string; token: string }): Promise<boolean> {
|
||||
async function createGateway(values: { name: string; endpoint: string; token?: string }): Promise<boolean> {
|
||||
setBusy('create');
|
||||
try {
|
||||
const record = await create('gateways', values);
|
||||
@@ -115,7 +114,7 @@ export default function Page() {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateGateway(values: { name: string; endpoint: string; token: string }): Promise<boolean> {
|
||||
async function updateGateway(values: { name: string; endpoint: string; token?: string }): Promise<boolean> {
|
||||
if (!editing) return false;
|
||||
setBusy('edit');
|
||||
try {
|
||||
@@ -170,15 +169,15 @@ export default function Page() {
|
||||
},
|
||||
];
|
||||
|
||||
usePageActions(
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => setCreateOpen(true)}>
|
||||
注册网关
|
||||
</Button>,
|
||||
);
|
||||
|
||||
return (
|
||||
<Card variant="outlined">
|
||||
{contextHolder}
|
||||
<Flex justify="space-between" align="center" style={{ marginBottom: 16 }}>
|
||||
<Typography.Text type="secondary">共 {gateways.length} 个网关</Typography.Text>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => setCreateOpen(true)}>
|
||||
注册网关
|
||||
</Button>
|
||||
</Flex>
|
||||
{created ? (
|
||||
<Alert
|
||||
type="success"
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
// 网络出口:语义对齐 web.archived NetworkExitsPage.jsx(列表 + 创建/编辑 Modal + 检测/启停/删除动作 + 出口详情页)。
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { history } from '@umijs/max';
|
||||
import { Alert, Button, Card, Form, Input, InputNumber, Modal, Popconfirm, Select, Space, Table, Tag, Typography, message } from 'antd';
|
||||
import { Alert, Button, Card, Flex, Form, Input, InputNumber, Modal, Popconfirm, Select, Space, Table, Tag, Typography, message } from 'antd';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { create, getOne, getList, networkExitAction, remove, update } from '@/services/api';
|
||||
import { conflictMessage, dateTime } from '@/utils/helpers';
|
||||
import { usePageActions } from '@/components/PageActions';
|
||||
|
||||
const healthText: Record<string, string> = {
|
||||
unchecked: '未检测',
|
||||
@@ -278,15 +277,15 @@ export default function Page() {
|
||||
},
|
||||
];
|
||||
|
||||
usePageActions(
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => setCreateOpen(true)}>
|
||||
创建网络出口
|
||||
</Button>,
|
||||
);
|
||||
|
||||
return (
|
||||
<Card variant="outlined">
|
||||
{contextHolder}
|
||||
<Flex justify="space-between" align="center" style={{ marginBottom: 16 }}>
|
||||
<Typography.Text type="secondary">共 {exits.length} 个网络出口</Typography.Text>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => setCreateOpen(true)}>
|
||||
创建网络出口
|
||||
</Button>
|
||||
</Flex>
|
||||
{error ? (
|
||||
<Alert type="error" showIcon style={{ marginBottom: 16 }} title={error.message} action={<Button size="small" onClick={load}>重试</Button>} />
|
||||
) : null}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 任务中心列表:语义对齐 web.archived TasksPage.jsx 的 TaskList。
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { history } from '@umijs/max';
|
||||
import { Alert, Button, Card, Input, Select, Space, Table, Tag, Typography } from 'antd';
|
||||
import { Alert, Button, Card, Flex, Input, Select, Space, Table, Tag, Typography } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { getList } from '@/services/api';
|
||||
import { dateTime } from '@/utils/helpers';
|
||||
@@ -112,23 +112,26 @@ export default function Page() {
|
||||
return (
|
||||
<div>
|
||||
<Card variant="outlined" style={{ marginBottom: 16 }}>
|
||||
<Space wrap>
|
||||
<Input
|
||||
style={{ width: 280 }}
|
||||
placeholder="账号 ID"
|
||||
value={accountFilter}
|
||||
onChange={(event) => setAccountFilter(event.target.value)}
|
||||
allowClear
|
||||
/>
|
||||
<Select
|
||||
style={{ width: 200 }}
|
||||
placeholder="全部状态"
|
||||
allowClear
|
||||
value={stateFilter}
|
||||
onChange={setStateFilter}
|
||||
options={Object.entries(stateText).map(([value, label]) => ({ value, label }))}
|
||||
/>
|
||||
</Space>
|
||||
<Flex justify="space-between" align="center">
|
||||
<Typography.Text type="secondary">共 {rows.length} 个任务</Typography.Text>
|
||||
<Space wrap>
|
||||
<Input
|
||||
style={{ width: 280 }}
|
||||
placeholder="账号 ID"
|
||||
value={accountFilter}
|
||||
onChange={(event) => setAccountFilter(event.target.value)}
|
||||
allowClear
|
||||
/>
|
||||
<Select
|
||||
style={{ width: 200 }}
|
||||
placeholder="全部状态"
|
||||
allowClear
|
||||
value={stateFilter}
|
||||
onChange={setStateFilter}
|
||||
options={Object.entries(stateText).map(([value, label]) => ({ value, label }))}
|
||||
/>
|
||||
</Space>
|
||||
</Flex>
|
||||
</Card>
|
||||
<Card variant="outlined">
|
||||
{error ? <Alert type="error" showIcon style={{ marginBottom: 16 }} title={error.message} action={<Button size="small" onClick={load}>重试</Button>} /> : null}
|
||||
|
||||
Reference in New Issue
Block a user