完成前测素材,模板列表

This commit is contained in:
Lrd
2026-07-06 18:07:45 +08:00
parent f4d278eae4
commit 4da83e4859
6 changed files with 431 additions and 107 deletions
@@ -1,18 +1,29 @@
import React, { useEffect, useState } from 'react';
import { Button, Input, Select, Table, Pagination, Tag, Typography } from 'antd';
import { FileTextOutlined, SearchOutlined } from '@ant-design/icons';
import { Button, Input, Table, Pagination, Tag, Typography, DatePicker } from 'antd';
import { FileTextOutlined } from '@ant-design/icons';
import { getPreTestTemplateList } from '../api';
import dayjs from 'dayjs';
const formatDateTime = (dateStr: string) => {
if (!dateStr) return '';
const date = new Date(dateStr);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
const { Text } = Typography;
const AdminPreTestTemplates: React.FC = () => {
const [tableData, setTableData] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [total, setTotal] = useState(0);
const [searchPlatform, setSearchPlatform] = useState('');
const [searchName, setSearchName] = useState('');
const [searchId, setSearchId] = useState('');
const [searchPhone, setSearchPhone] = useState('');
const [searchCreatedAt, setSearchCreatedAt] = useState<[dayjs.Dayjs, dayjs.Dayjs] | undefined>();
const columns = [
{
title: '序号',
@@ -22,92 +33,180 @@ const AdminPreTestTemplates: React.FC = () => {
render: (text: number) => <span style={{ color: '#64748b' }}>{text}</span>,
},
{
title: '模板名称',
title: 'ID',
dataIndex: 'id',
key: 'id',
},
{
title: '手机号',
dataIndex: 'phone',
key: 'phone',
width: 150,
},
{
title: '受众年龄',
dataIndex: 'audienceAge',
key: 'audienceAge',
width: 100,
},
{
title: '受众性别',
dataIndex: 'audienceGender',
key: 'audienceGender',
width: 100,
},
{
title: '受众网络',
dataIndex: 'audienceNetwork',
key: 'audienceNetwork',
width: 100,
},
{
title: '受众区域',
dataIndex: 'audienceRegion',
key: 'audienceRegion',
width: 100,
},
{
title: '预算',
dataIndex: 'budget',
key: 'budget',
width: 100,
},
{
title: '成本上限',
dataIndex: 'costCap',
key: 'costCap',
width: 100,
},
{
title: 'CPA预算',
dataIndex: 'cpaBid',
key: 'cpaBid',
width: 100,
},
{
title: 'CPC预算',
dataIndex: 'cpcBid',
key: 'cpcBid',
width: 100,
},
{
title: '客户名称',
dataIndex: 'cusName',
key: 'cusName',
width: 150,
},
{
title: '外部操作',
dataIndex: 'externalAction',
key: 'externalAction',
width: 100,
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
width: 200,
width: 150,
},
{
title: '投放平台',
title: '名称',
dataIndex: 'nobid',
key: 'nobid',
width: 150,
},
{
title: '备注',
dataIndex: 'note',
key: 'note',
width: 150,
},
{
title: '平台',
dataIndex: 'platform',
key: 'platform',
width: 120,
render: (text: string) => (
<Tag color={text === 'AD' ? 'blue' : text === 'QIANCHUAN' ? 'green' : 'orange'}>
{text === 'AD' ? 'AD' : text === 'QIANCHUAN' ? '千川' : '本地推'}
</Tag>
),
width: 100,
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
title: '定价类型',
dataIndex: 'pricingType',
key: 'pricingType',
width: 100,
render: (text: string) => (
<Tag color={text === 'active' ? 'green' : 'red'}>
{text === 'active' ? '启用' : '禁用'}
</Tag>
),
},
{
title: '目标成本',
dataIndex: 'targetCost',
key: 'targetCost',
width: 100,
},
{
title: '用户ID',
dataIndex: 'userId',
key: 'userId',
width: 100,
},
{
title: '用户手机号',
dataIndex: 'userPhone',
key: 'userPhone',
width: 150,
},
{
title: '是否默认',
dataIndex: 'isDefault',
key: 'isDefault',
width: 100,
render: (text: boolean) => <Tag color={text ? 'green' : 'red'}>{text ? '是' : '否'}</Tag>,
},
{
title: '创建时间',
dataIndex: 'createdAt',
key: 'createdAt',
width: 180,
render: (text: string) => <span style={{ color: '#64748b' }}>{text}</span>,
render: (text: string) => <span style={{ color: '#64748b' }}>{formatDateTime(text)}</span>,
},
{
title: '更新时间',
dataIndex: 'updatedAt',
key: 'updatedAt',
width: 180,
render: (text: string) => <span style={{ color: '#64748b' }}>{text}</span>,
render: (text: string) => <span style={{ color: '#64748b' }}>{formatDateTime(text)}</span>,
},
];
const loadRecords = async () => {
setLoading(true);
try {
const params = new URLSearchParams();
params.set('page', String(currentPage));
params.set('page_size', String(pageSize));
if (searchPlatform) params.set('platform', searchPlatform);
if (searchName) params.set('name', searchName);
const mockData = {
items: Array.from({ length: pageSize }, (_, i) => ({
id: `${currentPage}-${i}`,
index: (currentPage - 1) * pageSize + i + 1,
name: `前测模板${(currentPage - 1) * pageSize + i + 1}`,
platform: ['AD', 'QIANCHUAN', 'LOCAL'][i % 3],
status: i % 5 === 0 ? 'inactive' : 'active',
createdAt: '2026-07-01 10:00:00',
updatedAt: '2026-07-02 14:30:00',
})),
total: 50,
};
setTableData(mockData.items);
setTotal(mockData.total);
const res = await getPreTestTemplateList({
id: searchId || undefined,
phone: searchPhone || undefined,
created_at: searchCreatedAt ? [searchCreatedAt[0].format('YYYY-MM-DD'), searchCreatedAt[1].format('YYYY-MM-DD')] : undefined,
page: currentPage,
page_size: pageSize,
});
const data = res.data || [];
const tableData = data.map((item: any, index: number) => ({
...item,
index: (currentPage - 1) * pageSize + index + 1,
}));
setTableData(tableData);
setTotal(res.pagination.total || 0);
} catch (error) {
console.error('加载数据失败:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadRecords();
}, [currentPage, pageSize]);
const handlePageChange = (page: number, size: number) => {
setCurrentPage(page);
setPageSize(size);
};
const handleSearch = () => {
setCurrentPage(1);
loadRecords();
};
return (
<div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
@@ -115,26 +214,34 @@ const AdminPreTestTemplates: React.FC = () => {
<Text strong style={{ fontSize: 16 }}></Text>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
<div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
<div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
<Input
placeholder="模板名称"
value={searchName}
onChange={(e) => setSearchName(e.target.value)}
style={{ width: 180 }}
placeholder="ID"
value={searchId}
onChange={(e) => setSearchId(e.target.value)}
style={{ width: 160 }}
allowClear
onPressEnter={handleSearch}
/>
<Select
placeholder="投放平台"
value={searchPlatform}
onChange={(value) => setSearchPlatform(value)}
style={{ width: 140 }}
<Input
placeholder="手机号"
value={searchPhone}
onChange={(e) => setSearchPhone(e.target.value)}
style={{ width: 160 }}
allowClear
options={[
{ value: 'AD', label: 'AD' },
{ value: 'QIANCHUAN', label: '千川' },
{ value: 'LOCAL', label: '本地推' },
]}
onPressEnter={handleSearch}
/>
<DatePicker.RangePicker
placeholder={['开始日期', '结束日期']}
value={searchCreatedAt}
onChange={(dates) => {
if (dates && dates[0] && dates[1]) {
setSearchCreatedAt([dates[0], dates[1]]);
} else {
setSearchCreatedAt(undefined);
}
}}
style={{ width: 260 }}
/>
<Button
type="primary"
@@ -187,5 +294,4 @@ const AdminPreTestTemplates: React.FC = () => {
</div>
);
};
export default AdminPreTestTemplates;