管理员系统完成消耗列表及表头接口对接
This commit is contained in:
@@ -624,6 +624,25 @@ export async function uploadImage(file: File): Promise<{ url: string; filename:
|
||||
return { url: data.url, filename: data.filename };
|
||||
}
|
||||
|
||||
// 自定义表头字段
|
||||
export async function getFields(): Promise<any> {
|
||||
return api.get('/material-consumption/fields');
|
||||
}
|
||||
|
||||
// 查询素材消耗列表
|
||||
export interface MaterialConsumpParams {
|
||||
advertiser_id?: string;
|
||||
user_id?: string;
|
||||
consume_date?: [string, string];
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
}
|
||||
export async function getMaterialConsumpList(params: MaterialConsumpParams): Promise<any> {
|
||||
const query = new URLSearchParams();
|
||||
if (params.advertiser_id) query.set('advertiser_id', params.advertiser_id);
|
||||
if (params.user_id) query.set('user_id', params.user_id);
|
||||
if (params.consume_date !== undefined) query.set('consume_date', params.consume_date[0] + ',' + params.consume_date[1]);
|
||||
if (params.page !== undefined) query.set('page', String(params.page));
|
||||
if (params.page_size !== undefined) query.set('page_size', String(params.page_size));
|
||||
return api.get(`/material-consumption/admin/list?${query.toString()}`);
|
||||
}
|
||||
@@ -1,67 +1,81 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Card, Space, Table, Button, Typography, Modal, Checkbox, Input, message } from 'antd';
|
||||
import { ArrowLeftOutlined, DollarOutlined, SettingOutlined } from '@ant-design/icons';
|
||||
import { Card, Space, Table, Button, Typography, Modal, Checkbox, Input, message, DatePicker } from 'antd';
|
||||
import { ArrowLeftOutlined, DollarOutlined, SettingOutlined, SearchOutlined, ReloadOutlined } from '@ant-design/icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { getFields, getMaterialConsumpList } from '../api';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
// 表头字段模拟数据
|
||||
const mockColumnsData = [
|
||||
{ name: 'id', description: 'id' },
|
||||
{ name: 'star_id', description: '星图ID' },
|
||||
{ name: 'demand_id', description: '任务ID' },
|
||||
{ name: 'demand_name', description: '任务名称' },
|
||||
{ name: 'demander_name', description: '客户名称' },
|
||||
{ name: 'brand', description: '品牌' },
|
||||
{ name: 'max_publish_count', description: '最大发布数量' },
|
||||
{ name: 'product_name', description: '产品名称' },
|
||||
{ name: 'product_information', description: '产品信息' },
|
||||
];
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
const toCamelCase = (str: string) => str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
||||
|
||||
interface ConsumptionRecord {
|
||||
id: string;
|
||||
star_id?: string;
|
||||
demand_id?: string;
|
||||
demand_name?: string;
|
||||
demander_name?: string;
|
||||
brand?: string;
|
||||
max_publish_count?: number;
|
||||
product_name?: string;
|
||||
product_information?: string;
|
||||
advertiser_id?: string;
|
||||
user_id?: string;
|
||||
consume_date?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const mockConsumptionRecords: ConsumptionRecord[] = [
|
||||
{ id: 'C001', star_id: 'ST001', demand_id: 'DM001', demand_name: '产品推广任务', demander_name: 'XX品牌方', brand: 'XX品牌', max_publish_count: 10, product_name: 'XX产品', product_information: '产品信息描述' },
|
||||
{ id: 'C002', star_id: 'ST002', demand_id: 'DM002', demand_name: '新品发布任务', demander_name: 'YY品牌方', brand: 'YY品牌', max_publish_count: 5, product_name: 'YY产品', product_information: '新品信息描述' },
|
||||
{ id: 'C003', star_id: 'ST003', demand_id: 'DM003', demand_name: '品牌宣传任务', demander_name: 'ZZ品牌方', brand: 'ZZ品牌', max_publish_count: 8, product_name: 'ZZ产品', product_information: '品牌信息描述' },
|
||||
{ id: 'C004', star_id: 'ST001', demand_id: 'DM004', demand_name: '活动促销任务', demander_name: 'XX品牌方', brand: 'XX品牌', max_publish_count: 15, product_name: 'XX活动产品', product_information: '活动产品信息' },
|
||||
{ id: 'C005', star_id: 'ST004', demand_id: 'DM005', demand_name: '节日营销任务', demander_name: 'AA品牌方', brand: 'AA品牌', max_publish_count: 20, product_name: 'AA节日产品', product_information: '节日产品信息' },
|
||||
];
|
||||
|
||||
const ConsumePage: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const [consumptionRecords, setConsumptionRecords] = useState<ConsumptionRecord[]>(mockConsumptionRecords);
|
||||
const [consumptionRecords, setConsumptionRecords] = useState<ConsumptionRecord[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [columnsData, setColumnsData] = useState<{ name: string; description: string }[]>([]);
|
||||
const [selectedColumns, setSelectedColumns] = useState<string[]>([]);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [advertiserId, setAdvertiserId] = useState('');
|
||||
const [userId, setUserId] = useState('');
|
||||
const [consumeDateRange, setConsumeDateRange] = useState<[string, string] | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
setTimeout(() => {
|
||||
setConsumptionRecords(mockConsumptionRecords);
|
||||
setLoading(false);
|
||||
}, 500);
|
||||
loadColumns();
|
||||
loadData();
|
||||
}, [page, pageSize]);
|
||||
|
||||
setTimeout(() => {
|
||||
setColumnsData(mockColumnsData);
|
||||
const loadData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await getMaterialConsumpList({
|
||||
advertiser_id: advertiserId || undefined,
|
||||
user_id: userId || undefined,
|
||||
consume_date: consumeDateRange,
|
||||
page,
|
||||
page_size: pageSize,
|
||||
});
|
||||
console.log(res);
|
||||
setConsumptionRecords(res.data || []);
|
||||
setTotal(res.pagination?.total || 0);
|
||||
} catch (e: any) {
|
||||
message.error('加载数据失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const loadColumns = async () => {
|
||||
try {
|
||||
const res = await getFields();
|
||||
const fields = res.data || [];
|
||||
const formattedFields = fields.map((item: { field: string; description: string }) => ({
|
||||
name: toCamelCase(item.field),
|
||||
description: item.description,
|
||||
}));
|
||||
setColumnsData(formattedFields);
|
||||
const saved = localStorage.getItem('consumeColumns');
|
||||
if (saved) {
|
||||
setSelectedColumns(JSON.parse(saved));
|
||||
} else {
|
||||
setSelectedColumns(mockColumnsData.map(item => item.name));
|
||||
setSelectedColumns(formattedFields.map((item: { name: string }) => item.name));
|
||||
}
|
||||
}, 300);
|
||||
}, []);
|
||||
} catch (e: any) {
|
||||
message.error('加载表头字段失败');
|
||||
}
|
||||
};
|
||||
|
||||
const tableData = consumptionRecords.map((item, index) => ({
|
||||
...item,
|
||||
@@ -73,8 +87,22 @@ const ConsumePage: React.FC = () => {
|
||||
navigate('/authorization');
|
||||
};
|
||||
|
||||
const dynamicColumns = columnsData
|
||||
.filter(col => selectedColumns.includes(col.name))
|
||||
const handleSearch = () => {
|
||||
setPage(1);
|
||||
loadData();
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setAdvertiserId('');
|
||||
setUserId('');
|
||||
setConsumeDateRange(undefined);
|
||||
setPage(1);
|
||||
loadData();
|
||||
};
|
||||
|
||||
const dynamicColumns = selectedColumns
|
||||
.map(colName => columnsData.find(col => col.name === colName))
|
||||
.filter((col): col is { name: string; description: string } => !!col)
|
||||
.map(col => ({
|
||||
title: col.description,
|
||||
dataIndex: col.name,
|
||||
@@ -84,7 +112,7 @@ const ConsumePage: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div style={{ minHeight: '94vh' }}>
|
||||
<Card bordered={false} style={{ borderRadius: 12, border: '1px solid #f0f0f5' }}>
|
||||
<Card variant="outlined" style={{ borderRadius: 12, border: '1px solid #f0f0f5' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||
<Space>
|
||||
<DollarOutlined style={{ fontSize: 18, color: '#6366f1' }} />
|
||||
@@ -95,6 +123,34 @@ const ConsumePage: React.FC = () => {
|
||||
<Button icon={<ArrowLeftOutlined />} onClick={handleBack}>返回</Button>
|
||||
</Space>
|
||||
</div>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Space wrap>
|
||||
<Input
|
||||
placeholder="广告主ID"
|
||||
value={advertiserId}
|
||||
onChange={(e) => setAdvertiserId(e.target.value)}
|
||||
style={{ width: 200 }}
|
||||
/>
|
||||
<Input
|
||||
placeholder="用户ID"
|
||||
value={userId}
|
||||
onChange={(e) => setUserId(e.target.value)}
|
||||
style={{ width: 200 }}
|
||||
/>
|
||||
<RangePicker
|
||||
value={consumeDateRange ? [dayjs(consumeDateRange[0]), dayjs(consumeDateRange[1])] : undefined}
|
||||
onChange={(dates) => {
|
||||
if (dates && dates[0] && dates[1]) {
|
||||
setConsumeDateRange([dates[0].format('YYYY-MM-DD'), dates[1].format('YYYY-MM-DD')]);
|
||||
} else {
|
||||
setConsumeDateRange(undefined);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button type="primary" icon={<SearchOutlined />} onClick={handleSearch}>搜索</Button>
|
||||
<Button icon={<ReloadOutlined />} onClick={handleReset}>重置</Button>
|
||||
</Space>
|
||||
</div>
|
||||
<Table
|
||||
dataSource={tableData}
|
||||
columns={dynamicColumns}
|
||||
@@ -103,15 +159,19 @@ const ConsumePage: React.FC = () => {
|
||||
bordered={false}
|
||||
scroll={{ x: 'max-content' }}
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
total: consumptionRecords.length,
|
||||
current: page,
|
||||
pageSize,
|
||||
total,
|
||||
showSizeChanger: true,
|
||||
showTotal: (t) => `共 ${t} 条记录`,
|
||||
size: 'small',
|
||||
onChange: (p, ps) => {
|
||||
setPage(p);
|
||||
setPageSize(ps);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Modal
|
||||
title="自定义表头"
|
||||
open={showModal}
|
||||
@@ -127,13 +187,13 @@ const ConsumePage: React.FC = () => {
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
width={800}
|
||||
bodyStyle={{ padding: 16 }}
|
||||
>
|
||||
<div style={{ display: 'flex', gap: 20, height: 400 }}>
|
||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
|
||||
<Input
|
||||
placeholder="搜索指标..."
|
||||
value={searchText}
|
||||
allowClear={true}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
style={{ marginBottom: 8 }}
|
||||
/>
|
||||
@@ -197,11 +257,21 @@ const ConsumePage: React.FC = () => {
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => {
|
||||
message.info('更新表头字段');
|
||||
setTimeout(() => {
|
||||
setColumnsData(mockColumnsData);
|
||||
}, 300);
|
||||
onClick={async () => {
|
||||
try {
|
||||
const res = await getFields();
|
||||
const fields = res.data || [];
|
||||
const formattedFields = fields.map((item: { field: string; description: string }) => ({
|
||||
name: toCamelCase(item.field),
|
||||
description: item.description,
|
||||
}));
|
||||
setColumnsData(formattedFields);
|
||||
setSelectedColumns(formattedFields.map((item: { name: string }) => item.name));
|
||||
localStorage.removeItem('consumeColumns');
|
||||
message.success('表头字段已更新');
|
||||
} catch (e: any) {
|
||||
message.error('更新表头字段失败');
|
||||
}
|
||||
}}
|
||||
>
|
||||
更新表头字段
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
|
||||
VITE_API_BASE=http://ceshi.apiforeign.minzhong.cn
|
||||
# VITE_API_BASE=http://192.168.120.17:8000
|
||||
VITE_API_BASE=http://ceshi.apiforeign.minzhong.cn
|
||||
VITE_USE_MOCK=false
|
||||
# Encryption disabled for dev — enable in production
|
||||
VITE_ENCRYPTION_KEY=
|
||||
|
||||
Reference in New Issue
Block a user