素材列表添加多选功能

This commit is contained in:
Lrd
2026-06-30 17:45:43 +08:00
parent 571067f8ed
commit 976fa1b5a6
5 changed files with 636 additions and 34 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -28,7 +28,7 @@
}
})();
</script>
<script type="module" crossorigin src="/assets/index-BhNIfJ-Y.js"></script>
<script type="module" crossorigin src="/assets/index-BvgCzUzu.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BhPFzWLH.css">
</head>
<body>
+6 -14
View File
@@ -449,53 +449,45 @@ export async function getOAuthList(params: OAuthListParams): Promise<any> {
}
// 前测模板列表
// /api/pre-test-template/list
export interface PreTestListParams {
platform?: string;
page?: number;
pageSize?: number;
}
export async function getPreTestList(params?: PreTestListParams): Promise<any> {
if (USE_MOCK) return mock.mockGetPreTestList(params);
const page = params?.page || 1;
const pageSize = Math.min(params?.pageSize || 10, 100);
return api.get(`/pre-test-template/list?page=${page}&page_size=${pageSize}`);
const query = new URLSearchParams();
if (params.platform) query.set('platform', params.platform);
if (params.page !== undefined) query.set('page', String(params.page));
if (params.pageSize !== undefined) query.set('page_size', String(params.pageSize));
return api.get(`/pre-test-template/list?${query.toString()}`);
}
// 获取前测字段列表
// /api/pre-test-template/fields
export async function getPreTestFields(): Promise<any> {
if (USE_MOCK) return mock.mockGetPreTestFields();
return api.get(`/pre-test-template/fields`);
}
// 创建前测模板
// /api/pre-test-template/create
export async function createPreTest(params: any): Promise<any> {
if (USE_MOCK) return mock.mockCreatePreTest(params);
return api.post(`/pre-test-template/create`, params);
}
// 前测模板详情
// /api/pre-test-template/select/{template_id}
export async function getPreTestDetail(templateId: string): Promise<any> {
return api.get(`/pre-test-template/select/${templateId}`);
}
// 更新前测模板
// /api/pre-test-template/update/{template_id}
export async function updatePreTest(templateId: string, params: any): Promise<any> {
return api.post(`/pre-test-template/update/${templateId}`, params);
}
// 删除前测模板
// /api/pre-test-template/delete/{template_id}
export async function deletePreTest(templateId: string): Promise<any> {
return api.get(`/pre-test-template/delete/${templateId}`);
}
// 获取默认前测模板
// /api/pre-test-template/default
export async function getDefaultPreTest(): Promise<any> {
return api.get(`/pre-test-template/default`);
}
@@ -76,7 +76,6 @@ const AuthorizationPage: React.FC = () => {
const list = async () => {
try {
const res = await getDefaultPreTest();
console.log(res);
} catch (error) {
}
};
+187 -18
View File
@@ -10,6 +10,8 @@ interface PreTestRecord {
selectedFields: string[];
status: string;
createdAt: string;
name: string;
platform: string;
}
interface PreTestField {
@@ -147,9 +149,16 @@ const PreTest: React.FC = () => {
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [platform, setPlatform] = useState<string>('AD');
const [searchPlatform, setSearchPlatform] = useState<string | undefined>(undefined);
const [areaOptions, setAreaOptions] = useState<any[]>([]);
const [cityCodeMap, setCityCodeMap] = useState<Record<string, string>>({});
const [provinceCityMap, setProvinceCityMap] = useState<Record<string, string[]>>({});
const [selectedRows, setSelectedRows] = useState<Set<string>>(new Set());
const [pushModalOpen, setPushModalOpen] = useState(false);
const [pushIsPreTest, setPushIsPreTest] = useState<'1' | '2'>('2');
const [pushPreTestTemplate, setPushPreTestTemplate] = useState<string>('');
const [pushTemplates, setPushTemplates] = useState<any[]>([]);
const [pushTemplatesLoading, setPushTemplatesLoading] = useState(false);
useEffect(() => {
loadRecords();
@@ -162,7 +171,7 @@ const PreTest: React.FC = () => {
const loadRecords = async () => {
setLoading(true);
try {
const res = await getPreTestList({ page: currentPage, pageSize });
const res = await getPreTestList({ page: currentPage, pageSize, platform: searchPlatform });
setRecords(res.data || []);
setTotal(res.pagination?.total || 0);
setCurrentPage(res.pagination?.page || 1);
@@ -615,29 +624,111 @@ const PreTest: React.FC = () => {
setPageSize(size);
};
const handleOpenPushModal = async () => {
if (selectedRows.size === 0) {
message.warning('请先选择要推送的前测模板');
return;
}
setPushTemplatesLoading(true);
try {
const res = await getPreTestList({ page: 1, pageSize: 100 });
setPushTemplates(res.data || []);
} catch (error) {
message.error('获取前测模板列表失败');
} finally {
setPushTemplatesLoading(false);
}
setPushIsPreTest('2');
setPushPreTestTemplate('');
setPushModalOpen(true);
};
const handlePushSubmit = async () => {
if (selectedRows.size === 0) {
message.warning('请先选择要推送的前测模板');
return;
}
if (pushIsPreTest === '1' && !pushPreTestTemplate) {
message.warning('请选择前测模板');
return;
}
message.success('推送前测成功');
setPushModalOpen(false);
setSelectedRows(new Set());
};
const selectedRecords = records.filter(record => selectedRows.has(record.id));
return (
<div style={{ minHeight: 'calc(100vh - 80px)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
<FileTextOutlined style={{ color: '#6366f1', fontSize: 16 }} />
<Typography.Text strong style={{ fontSize: 16 }}></Typography.Text>
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12, marginBottom: 16 }}>
<Button
type="primary"
size="medium"
icon={<PlusOutlined />}
loading={loading}
onClick={handleOpenModal}
style={{
borderRadius: 12,
fontSize: 14,
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
border: 'none',
boxShadow: '0 4px 14px rgba(99,102,241,0.3)',
}}
>
</Button>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
<div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
<Select
placeholder="投放平台"
value={searchPlatform}
onChange={(value) => setSearchPlatform(value)}
style={{ width: 140 }}
allowClear
options={[
{ value: 'AD', label: 'AD' },
{ value: 'QIANCHUAN', label: '千川' },
{ value: 'LOCAL', label: '本地推' },
]}
/>
<Button
type="primary"
onClick={() => { setCurrentPage(1); loadRecords(); }}
>
</Button>
<Button
onClick={() => {
setSearchPlatform(undefined);
setCurrentPage(1);
loadRecords();
}}
>
</Button>
</div>
<div style={{ display: 'flex', gap: 12 }}>
<Button
type="primary"
size="medium"
loading={pushTemplatesLoading}
onClick={handleOpenPushModal}
disabled={selectedRows.size === 0}
style={{
borderRadius: 12,
fontSize: 14,
background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
border: 'none',
boxShadow: '0 4px 14px rgba(16,185,129,0.3)',
}}
>
({selectedRows.size})
</Button>
<Button
type="primary"
size="medium"
icon={<PlusOutlined />}
loading={loading}
onClick={handleOpenModal}
style={{
borderRadius: 12,
fontSize: 14,
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
border: 'none',
boxShadow: '0 4px 14px rgba(99,102,241,0.3)',
}}
>
</Button>
</div>
</div>
<div style={{
background: '#fff',
@@ -653,6 +744,13 @@ const PreTest: React.FC = () => {
rowKey="id"
bordered={false}
scroll={{ x: 'max-content' }}
rowSelection={{
type: 'checkbox',
selectedRowKeys: Array.from(selectedRows),
onChange: (keys) => {
setSelectedRows(new Set(keys as string[]));
},
}}
onRow={() => ({
style: {
cursor: 'pointer',
@@ -985,6 +1083,77 @@ const PreTest: React.FC = () => {
</Form.Item>
</Form>
</Modal>
<Modal
title="推送前测"
open={pushModalOpen}
onCancel={() => { setPushModalOpen(false); setSelectedRows(new Set()); }}
onOk={handlePushSubmit}
okText="推送"
cancelText="取消"
width={600}
style={{ borderRadius: 16 }}
>
<div style={{ display: 'flex', gap: 16, marginBottom: 24 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<Typography.Text style={{ fontSize: 14, color: '#475569' }}></Typography.Text>
<Select
value={pushIsPreTest}
onChange={(value) => {
setPushIsPreTest(value);
if (value !== '1') {
setPushPreTestTemplate('');
}
}}
style={{ width: 120 }}
options={[
{ value: '1', label: '是' },
{ value: '2', label: '否' },
]}
/>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<Typography.Text style={{ fontSize: 14, color: '#475569' }}></Typography.Text>
<Select
value={pushPreTestTemplate}
onChange={(value) => setPushPreTestTemplate(value)}
style={{ width: 200 }}
disabled={pushIsPreTest !== '1'}
loading={pushTemplatesLoading}
placeholder={pushIsPreTest !== '1' ? '请先开启前测' : '选择前测模板'}
options={pushTemplates.map((item) => ({
value: item.id,
label: item.name,
}))}
/>
</div>
</div>
<div style={{ marginBottom: 12 }}>
<Typography.Text strong style={{ fontSize: 14, color: '#1e293b', display: 'block', marginBottom: 12 }}>
({selectedRows.size})
</Typography.Text>
<div style={{ maxHeight: 200, overflowY: 'auto' }}>
{selectedRecords.length > 0 ? (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{selectedRecords.map(record => (
<div key={record.id} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: 12, background: '#f8fafc', borderRadius: 8 }}>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<Typography.Text strong style={{ fontSize: 14, color: '#1e293b' }}>{record.name}</Typography.Text>
<Typography.Text style={{ fontSize: 12, color: '#94a3b8' }}>ID: {record.id}</Typography.Text>
</div>
<Tag color={record.platform === 'AD' ? 'blue' : record.platform === 'QIANCHUAN' ? 'green' : 'orange'} style={{ borderRadius: 6, fontSize: 12 }}>
{record.platform === 'AD' ? 'AD' : record.platform === 'QIANCHUAN' ? '千川' : '本地推'}
</Tag>
</div>
))}
</div>
) : (
<Typography.Text style={{ color: '#94a3b8' }}></Typography.Text>
)}
</div>
</div>
</Modal>
</div>
);
};