From 4da83e4859b960e10e8e527c5e11e5dc1dbbd933 Mon Sep 17 00:00:00 2001 From: Lrd <13001933075@sina.cn> Date: Mon, 6 Jul 2026 18:07:45 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=89=8D=E6=B5=8B=E7=B4=A0?= =?UTF-8?q?=E6=9D=90=EF=BC=8C=E6=A8=A1=E6=9D=BF=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- video-gen-admin/dist/index.html | 70 +++--- video-gen-admin/src/api/index.ts | 17 ++ .../src/components/PreResultDisplay.tsx | 180 +++++++++++++ .../src/pages/AdminMaterialList.tsx | 31 ++- .../src/pages/AdminPreTestTemplates.tsx | 238 +++++++++++++----- video-gen-admin/tsconfig.tsbuildinfo | 2 +- 6 files changed, 431 insertions(+), 107 deletions(-) create mode 100644 video-gen-admin/src/components/PreResultDisplay.tsx diff --git a/video-gen-admin/dist/index.html b/video-gen-admin/dist/index.html index 55353140..d6673102 100644 --- a/video-gen-admin/dist/index.html +++ b/video-gen-admin/dist/index.html @@ -1,37 +1,37 @@ - - - - - - - - - - 后台管理 - + + + + + + + + + + 后台管理 + - - -
- - + + +
+ + diff --git a/video-gen-admin/src/api/index.ts b/video-gen-admin/src/api/index.ts index 5c1b2399..ede895ca 100644 --- a/video-gen-admin/src/api/index.ts +++ b/video-gen-admin/src/api/index.ts @@ -999,6 +999,23 @@ export async function getMaterialList(params: MaterialListParams): Promise return api.get(`/material-admin/material-list?${query.toString()}`); } +export interface PreTestTemplateListParams { + id?: string; + phone?: string; + created_at?: [string, string]; + page?: number; + page_size?: number; +} +export async function getPreTestTemplateList(params: PreTestTemplateListParams): Promise { + const query = new URLSearchParams(); + if (params.id) query.set('id', params.id); + if (params.phone) query.set('phone', params.phone); + if (params.created_at !== undefined) query.set('created_at', params.created_at[0] + ',' + params.created_at[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-admin/pre-test-template-list?${query.toString()}`); +} + // ── Private Portrait Admin ──────────────────────────────── export async function adminGetPrivatePortraitProjects(params: { userId?: string; keyword?: string; status?: string; page?: number; pageSize?: number } = {}): Promise { const query = new URLSearchParams(); diff --git a/video-gen-admin/src/components/PreResultDisplay.tsx b/video-gen-admin/src/components/PreResultDisplay.tsx new file mode 100644 index 00000000..d032b02a --- /dev/null +++ b/video-gen-admin/src/components/PreResultDisplay.tsx @@ -0,0 +1,180 @@ +import React, { useState } from 'react'; +import { Popover, Tag, List, Descriptions, Typography } from 'antd'; + +interface PreResultData { + video_id: string; + advertiser_id: number; + material_id: string; + is_ad_high_quality_material: string; + is_ecp_high_quality_material: string; + is_inefficient_material: string; + is_first_publish_material: string; + is_local_high_quality_material: string; + not_ad_high_quality_reason: string[] | null; + not_ecp_high_quality_reason: string[] | null; +} + +interface PreResultDisplayProps { + preResult: string; +} + +const fieldConfig = [ + { key: 'is_ad_high_quality_material', label: 'AD优质', noLabel: 'AD非优质素材', unknownLabel: 'AD未知', yesColor: 'green', noColor: 'red', unknownColor: 'default' }, + { key: 'is_ecp_high_quality_material', label: '千川优质', noLabel: '千川非优质素材', unknownLabel: '千川未知', yesColor: 'green', noColor: 'red', unknownColor: 'default' }, + { key: 'is_local_high_quality_material', label: '本地推优质', noLabel: '本地推非优质', unknownLabel: '本地推未知', yesColor: 'green', noColor: 'red', unknownColor: 'default' }, + { key: 'is_inefficient_material', label: '低效', noLabel: '非低效', unknownLabel: '是否低效未知', yesColor: 'red', noColor: 'green', unknownColor: 'default' }, + { key: 'is_first_publish_material', label: '首发', noLabel: '非首发', unknownLabel: '是否首发未知', yesColor: 'blue', noColor: 'default', unknownColor: 'default' }, +]; + +const PreResultDisplay: React.FC = ({ preResult }) => { + const [parsedData, setParsedData] = useState(null); + const [hasError, setHasError] = useState(false); + + React.useEffect(() => { + if (!preResult) { + setParsedData(null); + setHasError(false); + return; + } + try { + const data = JSON.parse(preResult); + setParsedData(data); + setHasError(false); + } catch { + setParsedData(null); + setHasError(true); + } + }, [preResult]); + + if (!preResult || hasError || !parsedData) { + return -; + } + + const getIndicatorDisplay = (key: string) => { + const config = fieldConfig.find(c => c.key === key); + if (!config) return null; + const value = parsedData[key as keyof PreResultData]; + const isYes = value === 'YES'; + const isNo = value === 'NO'; + const isUnknown = value === 'UNKNOWN'; + if (isUnknown) return null; + let displayLabel, displayColor; + if (isYes) { + displayLabel = config.label; + displayColor = config.yesColor; + } else if (isNo) { + displayLabel = config.noLabel; + displayColor = config.noColor; + } + return { + tag: ( + + {displayLabel} + + ), + detail: { + label: config.label.replace('优质', '优质素材'), + value: displayLabel, + color: displayColor, + }, + }; + }; + + const content = ( +
+ 前测结果详情 + + + {parsedData.video_id} + {parsedData.advertiser_id} + {parsedData.material_id} + + + 质量评估 +
+ {fieldConfig.map(config => { + const value = parsedData[config.key as keyof PreResultData]; + const isYes = value === 'YES'; + const isNo = value === 'NO'; + const isUnknown = value === 'UNKNOWN'; + if (isUnknown) return null; + let displayLabel, displayColor; + if (isYes) { + displayLabel = config.label; + displayColor = config.yesColor; + } else if (isNo) { + displayLabel = config.noLabel; + displayColor = config.noColor; + } + + return ( + + {displayLabel} + + ); + })} +
+ + {parsedData.not_ad_high_quality_reason && parsedData.not_ad_high_quality_reason.length > 0 && ( +
+ + AD非优质原因 + + ( + + {index + 1}. {item} + + )} + size="small" + /> +
+ )} + + {parsedData.not_ecp_high_quality_reason && parsedData.not_ecp_high_quality_reason.length > 0 && ( +
+ + 千川非优质原因 + + ( + + {index + 1}. {item} + + )} + size="small" + /> +
+ )} +
+ ); + + return ( + +
+ {fieldConfig.map(config => { + const display = getIndicatorDisplay(config.key); + return display?.tag; + })} +
+
+ ); +}; + +export default PreResultDisplay; \ No newline at end of file diff --git a/video-gen-admin/src/pages/AdminMaterialList.tsx b/video-gen-admin/src/pages/AdminMaterialList.tsx index 8394d224..af1bfecc 100644 --- a/video-gen-admin/src/pages/AdminMaterialList.tsx +++ b/video-gen-admin/src/pages/AdminMaterialList.tsx @@ -1,7 +1,8 @@ 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, Select, Table, Pagination, Tag, Typography, Tooltip } from 'antd'; +import { FileTextOutlined } from '@ant-design/icons'; import { getMaterialList } from '../api'; +import PreResultDisplay from '../components/PreResultDisplay'; const formatDateTime = (dateStr: string) => { if (!dateStr) return ''; const date = new Date(dateStr); @@ -48,6 +49,16 @@ const AdminMaterialList: React.FC = () => { title: '备注', dataIndex: 'note', key: 'note', + width: 200, + render: (text: string) => ( + + ), }, { title: '授权ID', @@ -58,6 +69,8 @@ const AdminMaterialList: React.FC = () => { title: '预测试结果', dataIndex: 'preResult', key: 'preResult', + width: 120, + render: (text: string) => , }, { title: '预测试模板ID', @@ -94,7 +107,15 @@ const AdminMaterialList: React.FC = () => { title: '上传ID', dataIndex: 'uploadId', key: 'uploadId', - width: 160, + render: (v: string) => { + if (!v) return '-'; + const short = v.length > 12 ? `${v.slice(0, 6)}...${v.slice(-4)}` : v; + return ( + + {short} + + ); + }, }, { title: '资源类型', @@ -145,13 +166,13 @@ const AdminMaterialList: React.FC = () => { page: currentPage, page_size: pageSize, }); - const data = res.items || []; + const data = res.data || []; const tableData = data.map((item: any, index: number) => ({ ...item, index: (currentPage - 1) * pageSize + index + 1, })); setTableData(tableData); - setTotal(res.total || 0); + setTotal(res.pagination?.total || 0); } catch (error) { console.error('加载数据失败:', error); } finally { diff --git a/video-gen-admin/src/pages/AdminPreTestTemplates.tsx b/video-gen-admin/src/pages/AdminPreTestTemplates.tsx index 338925f7..d0ec8e7e 100644 --- a/video-gen-admin/src/pages/AdminPreTestTemplates.tsx +++ b/video-gen-admin/src/pages/AdminPreTestTemplates.tsx @@ -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([]); 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) => {text}, }, { - 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) => ( - - {text === 'AD' ? 'AD' : text === 'QIANCHUAN' ? '千川' : '本地推'} - - ), + width: 100, }, { - title: '状态', - dataIndex: 'status', - key: 'status', + title: '定价类型', + dataIndex: 'pricingType', + key: 'pricingType', width: 100, - render: (text: string) => ( - - {text === 'active' ? '启用' : '禁用'} - - ), + }, + { + 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) => {text ? '是' : '否'}, }, { title: '创建时间', dataIndex: 'createdAt', key: 'createdAt', width: 180, - render: (text: string) => {text}, + render: (text: string) => {formatDateTime(text)}, }, { title: '更新时间', dataIndex: 'updatedAt', key: 'updatedAt', width: 180, - render: (text: string) => {text}, + render: (text: string) => {formatDateTime(text)}, }, ]; - 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 (
@@ -115,26 +214,34 @@ const AdminPreTestTemplates: React.FC = () => { 素材前测模板
-
+
setSearchName(e.target.value)} - style={{ width: 180 }} + placeholder="ID" + value={searchId} + onChange={(e) => setSearchId(e.target.value)} + style={{ width: 160 }} allowClear onPressEnter={handleSearch} /> - setSearchPhone(e.target.value)} + style={{ width: 160 }} allowClear - options={[ - { value: 'AD', label: 'AD' }, - { value: 'QIANCHUAN', label: '千川' }, - { value: 'LOCAL', label: '本地推' }, - ]} + onPressEnter={handleSearch} + /> + { + if (dates && dates[0] && dates[1]) { + setSearchCreatedAt([dates[0], dates[1]]); + } else { + setSearchCreatedAt(undefined); + } + }} + style={{ width: 260 }} />
); }; - export default AdminPreTestTemplates; \ No newline at end of file diff --git a/video-gen-admin/tsconfig.tsbuildinfo b/video-gen-admin/tsconfig.tsbuildinfo index 33ac246a..e8be770b 100644 --- a/video-gen-admin/tsconfig.tsbuildinfo +++ b/video-gen-admin/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/env.d.ts","./src/main.tsx","./src/api/client.ts","./src/api/crypto.ts","./src/api/index.ts","./src/pages/adminauthoriz.tsx","./src/pages/adminconsume.tsx","./src/pages/admincontactrequests.tsx","./src/pages/admincreditratios.tsx","./src/pages/admincreditrecords.tsx","./src/pages/admindashboard.tsx","./src/pages/admingenerationairecords.tsx","./src/pages/admingenerationrecords.tsx","./src/pages/adminhomematerials.tsx","./src/pages/adminhotopeningreplicationdetail.tsx","./src/pages/adminhotopeningreplications.tsx","./src/pages/adminimageengines.tsx","./src/pages/adminindustries.tsx","./src/pages/adminlayout.tsx","./src/pages/adminloginpage.tsx","./src/pages/adminmateriallist.tsx","./src/pages/adminmenuconfig.tsx","./src/pages/adminmodels.tsx","./src/pages/adminnotificationmanager.tsx","./src/pages/adminoauthlist.tsx","./src/pages/adminoauthapplist.tsx","./src/pages/adminoperationlogs.tsx","./src/pages/adminpaymentconfig.tsx","./src/pages/adminpaymentstats.tsx","./src/pages/adminplatform.tsx","./src/pages/adminpretesttemplates.tsx","./src/pages/adminprivateportraitprojects.tsx","./src/pages/adminrechargepackages.tsx","./src/pages/adminreplicationprojectdetail.tsx","./src/pages/adminsettings.tsx","./src/pages/adminshotreplications.tsx","./src/pages/adminshottasksetdetail.tsx","./src/pages/adminteams.tsx","./src/pages/adminusers.tsx","./src/pages/adminvideoengines.tsx","./src/pages/adminvideopromptschemaconfig.tsx","./src/pages/adminreplication/components/jsoncollapse.tsx","./src/pages/adminreplication/components/mediapreview.tsx","./src/pages/adminreplication/components/statustag.tsx","./src/pages/adminreplication/components/videopromptschemaviewer.tsx","./src/pages/homematerials/homematerialassettable.tsx","./src/pages/homematerials/homematerialcategorypanel.tsx","./src/pages/homematerials/homematerialuploadmodal.tsx","./src/pages/homematerials/watermarkeditor.tsx","./src/pages/homematerials/watermarklibrarymodal.tsx","./src/pages/homematerials/watermarkpreview.tsx","./src/store/index.ts","./src/types/index.ts","./src/types/xlsx-js-style.d.ts","./src/utils/excelexport.ts","./src/utils/formatdate.ts","./src/utils/resourceurl.ts","./src/utils/videopromptschema.ts"],"version":"6.0.3"} \ No newline at end of file +{"root":["./src/app.tsx","./src/env.d.ts","./src/main.tsx","./src/api/client.ts","./src/api/crypto.ts","./src/api/index.ts","./src/components/preresultdisplay.tsx","./src/pages/adminauthoriz.tsx","./src/pages/adminconsume.tsx","./src/pages/admincontactrequests.tsx","./src/pages/admincreditratios.tsx","./src/pages/admincreditrecords.tsx","./src/pages/admindashboard.tsx","./src/pages/admingenerationairecords.tsx","./src/pages/admingenerationrecords.tsx","./src/pages/adminhomematerials.tsx","./src/pages/adminhotopeningreplicationdetail.tsx","./src/pages/adminhotopeningreplications.tsx","./src/pages/adminimageengines.tsx","./src/pages/adminindustries.tsx","./src/pages/adminlayout.tsx","./src/pages/adminloginpage.tsx","./src/pages/adminmateriallist.tsx","./src/pages/adminmenuconfig.tsx","./src/pages/adminmodels.tsx","./src/pages/adminnotificationmanager.tsx","./src/pages/adminoauthlist.tsx","./src/pages/adminoauthapplist.tsx","./src/pages/adminoperationlogs.tsx","./src/pages/adminpaymentconfig.tsx","./src/pages/adminpaymentstats.tsx","./src/pages/adminplatform.tsx","./src/pages/adminpretesttemplates.tsx","./src/pages/adminprivateportraitprojects.tsx","./src/pages/adminrechargepackages.tsx","./src/pages/adminreplicationprojectdetail.tsx","./src/pages/adminsettings.tsx","./src/pages/adminshotreplications.tsx","./src/pages/adminshottasksetdetail.tsx","./src/pages/adminteams.tsx","./src/pages/adminusers.tsx","./src/pages/adminvideoengines.tsx","./src/pages/adminvideopromptschemaconfig.tsx","./src/pages/adminreplication/components/jsoncollapse.tsx","./src/pages/adminreplication/components/mediapreview.tsx","./src/pages/adminreplication/components/statustag.tsx","./src/pages/adminreplication/components/videopromptschemaviewer.tsx","./src/pages/homematerials/homematerialassettable.tsx","./src/pages/homematerials/homematerialcategorypanel.tsx","./src/pages/homematerials/homematerialuploadmodal.tsx","./src/pages/homematerials/watermarkeditor.tsx","./src/pages/homematerials/watermarklibrarymodal.tsx","./src/pages/homematerials/watermarkpreview.tsx","./src/store/index.ts","./src/types/index.ts","./src/types/xlsx-js-style.d.ts","./src/utils/excelexport.ts","./src/utils/formatdate.ts","./src/utils/resourceurl.ts","./src/utils/videopromptschema.ts"],"version":"6.0.3"} \ No newline at end of file