diff --git a/video-gen-admin/src/pages/AdminConsume.tsx b/video-gen-admin/src/pages/AdminConsume.tsx index 706cc612..dc72431d 100644 --- a/video-gen-admin/src/pages/AdminConsume.tsx +++ b/video-gen-admin/src/pages/AdminConsume.tsx @@ -115,12 +115,13 @@ const ConsumePage: React.FC = () => {
+ */} 消耗记录 -
diff --git a/video-gen-app/src/api/index.ts b/video-gen-app/src/api/index.ts index e78ee332..8213edf3 100644 --- a/video-gen-app/src/api/index.ts +++ b/video-gen-app/src/api/index.ts @@ -645,7 +645,20 @@ export async function getMaterialConsumpList(params: MaterialConsumpListParams): return api.get(`/material-consumption/list?${query.toString()}`); } -// 查询素材消耗列表 +// 拉取消耗 +export async function syncMaterialConsumption(params: { date: string; advertiser_id?: string }): Promise { + const query = new URLSearchParams(); + if (params.advertiser_id) query.set('advertiser_id', params.advertiser_id); + if (params.date) query.set('date', params.date); + return api.get(`/material-consumption/sync?${query.toString()}`); +} + +// 获取消耗字段列表 +export async function getMaterialConsumptionFields(): Promise { + return api.get('/material-consumption/fields'); +} + +// 查询上传素材列表 export interface ResourcesMaterialListParams { advertiser_id?: string; material_id?: string; diff --git a/video-gen-app/src/pages/ConsumePage.tsx b/video-gen-app/src/pages/ConsumePage.tsx index c3b8c8ad..d8d2dcf6 100644 --- a/video-gen-app/src/pages/ConsumePage.tsx +++ b/video-gen-app/src/pages/ConsumePage.tsx @@ -1,130 +1,85 @@ import React, { useState, useEffect } from 'react'; -import { Table, Tag, Button, Pagination, Typography, DatePicker, App, Input } from 'antd'; -import { ArrowLeftOutlined, DollarOutlined } from '@ant-design/icons'; -import { useNavigate } from 'react-router-dom'; -import { getMaterialConsumpList } from '../api'; +import { Table, Tag, Button, Pagination, Typography, Modal, Checkbox, Input, DatePicker, App } from 'antd'; +import { ArrowLeftOutlined, DollarOutlined, SettingOutlined, SyncOutlined } from '@ant-design/icons'; +import { useNavigate, useSearchParams } from 'react-router-dom'; +import { getMaterialConsumpList, getMaterialConsumptionFields, syncMaterialConsumption } from '../api'; +import dayjs from 'dayjs'; const { RangePicker } = DatePicker; -// 消耗类型配置 -const consumptionTypeConfig: Record = { - video: { label: '视频生成', color: 'blue' }, - audio: { label: '音频转换', color: 'purple' }, - image: { label: '图片处理', color: 'green' }, -}; +const toCamelCase = (str: string) => str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); interface ConsumptionRecord { id: string; - authorizationId: string; - amount: number; - type: string; - description: string; - createdAt: string; + advertiser_id?: string; + user_id?: string; + consume_date?: string; + [key: string]: any; } const ConsumePage: React.FC = () => { const navigate = useNavigate(); + const [searchParams] = useSearchParams(); const { message } = App.useApp(); const [consumptionRecords, setConsumptionRecords] = useState([]); + const [total, setTotal] = useState(0); const [loading, setLoading] = useState(false); - const [listLoading, setListLoading] = useState(false); + const [syncLoading, setSyncLoading] = useState(false); + const [showModal, setShowModal] = useState(false); + const [columnsData, setColumnsData] = useState<{ name: string; description: string }[]>([]); + const [selectedColumns, setSelectedColumns] = useState([]); + const [searchText, setSearchText] = useState(''); const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(10); - const [total, setTotal] = useState(0); - const [searchParams, setSearchParams] = useState({ - advertiser_id: '', - consume_date: undefined as [string, string] | undefined, - }); + const [advertiserId, setAdvertiserId] = useState(searchParams.get('accountId') || ''); + const [consumeDateRange, setConsumeDateRange] = useState<[string, string] | undefined>(); + const [syncDate, setSyncDate] = useState(dayjs().subtract(1, 'day').format('YYYY-MM-DD')); + const [syncAdvertiserId, setSyncAdvertiserId] = useState(''); useEffect(() => { - loadConsumptionList(); + loadColumns(); + loadData(); }, []); - const loadConsumptionList = async (page = 1, pageSizeNum = 10, params = searchParams) => { - setListLoading(true); + const loadData = async (page = 1, pageSizeNum = 10) => { + setLoading(true); try { - const response = await getMaterialConsumpList({ + const res = await getMaterialConsumpList({ + advertiser_id: advertiserId || undefined, + consume_date: consumeDateRange, page, page_size: pageSizeNum, - advertiser_id: params.advertiser_id || undefined, - consume_date: params.consume_date, }); - if (response) { - if (response.data) { - setConsumptionRecords(response.data.data || response.data); - setTotal(response.pagination?.total || 0); - setCurrentPage(response.pagination?.page || 1); - setPageSize(response.pagination?.pageSize || 10); - } else { - setConsumptionRecords(response.data || response || []); - setTotal(Array.isArray(response) ? response.length : 0); - } - } else { - setConsumptionRecords([]); - setTotal(0); - } - } catch (error) { - message.error('获取消耗列表失败'); - console.error('获取消耗列表失败:', error); + setConsumptionRecords(res.data || []); + setTotal(res.pagination?.total || 0); + setCurrentPage(res.pagination?.page || 1); + setPageSize(res.pagination?.pageSize || 10); + } catch (e: any) { + message.error('加载数据失败'); } finally { - setListLoading(false); + setLoading(false); } }; - // 表头配置 - const columns = [ - { - title: '序号', - dataIndex: 'index', - key: 'index', - width: 80, - fixed: 'left' as const, - render: (text: number) => {text}, - }, - { - title: '消耗ID', - dataIndex: 'id', - key: 'id', - ellipsis: true, - render: (text: string) => {text}, - }, - { - title: '授权ID', - dataIndex: 'authorizationId', - key: 'authorizationId', - ellipsis: true, - }, - { - title: '消耗类型', - dataIndex: 'type', - key: 'type', - width: 120, - render: (text: string) => { - const config = consumptionTypeConfig[text]; - return {config?.label || text}; - }, - }, - { - title: '消耗金额', - dataIndex: 'amount', - key: 'amount', - width: 120, - render: (text: number) => {text} 元, - }, - { - title: '消耗描述', - dataIndex: 'description', - key: 'description', - ellipsis: true, - }, - { - title: '消耗时间', - dataIndex: 'createdAt', - key: 'createdAt', - width: 160, - fixed: 'right' as const, - }, - ]; + const loadColumns = async () => { + try { + const res = await getMaterialConsumptionFields(); + 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(formattedFields.map((item: { name: string }) => item.name)); + } + } catch (e: any) { + message.error('加载表头字段失败'); + } + }; const tableData = consumptionRecords.map((item, index) => ({ ...item, @@ -136,58 +91,130 @@ const ConsumePage: React.FC = () => { navigate('/authorization'); }; + const handleSearch = () => { + setCurrentPage(1); + loadData(1, pageSize); + }; + + const handleReset = () => { + setAdvertiserId(''); + setConsumeDateRange(undefined); + setCurrentPage(1); + loadData(1, pageSize); + }; + + const handleSync = () => { + Modal.confirm({ + title: '拉取消耗数据', + content: ( +
+
+ 同步日期: + setSyncDate(date ? date.format('YYYY-MM-DD') : dayjs().subtract(1, 'day').format('YYYY-MM-DD'))} + style={{ width: '100%' }} + /> +
+
+ 广告主ID(可选): + setSyncAdvertiserId(e.target.value)} + /> +
+
+ ), + onOk: async () => { + setSyncLoading(true); + try { + await syncMaterialConsumption({ + date: syncDate, + advertiser_id: syncAdvertiserId || undefined, + }); + message.success('拉取消耗数据成功'); + loadData(currentPage, pageSize); + } catch (e: any) { + message.error('拉取消耗数据失败'); + } finally { + setSyncLoading(false); + } + }, + okText: '开始拉取', + cancelText: '取消', + }); + }; + + 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, + key: col.name, + ellipsis: true, + })); + return (
- {/* 页面标题 */}
- {/* 搜索筛选 */} -
- setSearchParams(prev => ({ ...prev, advertiser_id: e.target.value }))} - style={{ width: 200 }} - onPressEnter={() => { setCurrentPage(1); loadConsumptionList(1, pageSize); }} - /> - { - if (dates && dateStrings[0] && dateStrings[1]) { - setSearchParams(prev => ({ ...prev, consume_date: [dateStrings[0], dateStrings[1]] })); - } else { - setSearchParams(prev => ({ ...prev, consume_date: undefined })); - } - }} - style={{ width: 280 }} - /> - - +
+
+ setAdvertiserId(e.target.value)} + style={{ width: 180 }} + onPressEnter={() => { setCurrentPage(1); loadData(1, pageSize); }} + /> + { + if (dates && dates[0] && dates[1]) { + setConsumeDateRange([dates[0].format('YYYY-MM-DD'), dates[1].format('YYYY-MM-DD')]); + } else { + setConsumeDateRange(undefined); + } + }} + /> + + +
+
+ + +
- {/* 表格 */}
{ onChange={(page, size) => { setCurrentPage(page); setPageSize(size); - loadConsumptionList(page, size); + loadData(page, size); }} size="small" /> + + { + localStorage.setItem('consumeColumns', JSON.stringify(selectedColumns)); + message.success('表头设置已保存'); + setShowModal(false); + }} + onCancel={() => { + setShowModal(false); + setSearchText(''); + }} + okText="确定" + cancelText="取消" + width={800} + > +
+
+ setSearchText(e.target.value)} + style={{ marginBottom: 8 }} + /> + + 输入指标名称进行搜索 + +
+
+ 任务详情指标 +
+ 0} + indeterminate={selectedColumns.length > 0 && selectedColumns.length < columnsData.length} + onChange={(e) => { + if (e.target.checked) { + setSelectedColumns(columnsData.map(item => item.name)); + } else { + setSelectedColumns([]); + } + }} + style={{ marginBottom: 12 }} + > + 全选 + +
+ {columnsData + .filter(item => + item.description.toLowerCase().includes(searchText.toLowerCase()) || + item.name.toLowerCase().includes(searchText.toLowerCase()) + ) + .map(item => ( + { + if (e.target.checked) { + setSelectedColumns(prev => [...prev, item.name]); + } else { + setSelectedColumns(prev => prev.filter(col => col !== item.name)); + } + }} + > + {item.description} + + ))} +
+
+
+
+
+ + 已添加({selectedColumns.length}) + +
+ + +
+
+
+ {selectedColumns.length === 0 ? ( + + 暂无已选指标 + + ) : ( +
+ {selectedColumns.map((colName, index) => { + const col = columnsData.find(c => c.name === colName); + return ( +
{ + e.dataTransfer.setData('index', String(index)); + }} + onDragOver={(e) => { + e.preventDefault(); + }} + onDrop={(e) => { + const fromIndex = parseInt(e.dataTransfer.getData('index')); + const toIndex = index; + if (fromIndex !== toIndex) { + const newSelected = [...selectedColumns]; + const [removed] = newSelected.splice(fromIndex, 1); + newSelected.splice(toIndex, 0, removed); + setSelectedColumns(newSelected); + } + }} + > + {col?.description || colName} + + {index + 1} + +
+ ); + })} +
+ )} +
+
+
+
); };