完成前测素材,模板列表

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
+35 -35
View File
@@ -1,37 +1,37 @@
<!doctype html> <!doctype html>
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
<title>后台管理</title> <title>后台管理</title>
<script> <script>
(function() { (function() {
var cached = localStorage.getItem('siteInfo'); var cached = localStorage.getItem('siteInfo');
if (cached) { if (cached) {
try { try {
var info = JSON.parse(cached); var info = JSON.parse(cached);
if (info.siteName) { if (info.siteName) {
document.title = info.siteName + ' - 管理后台'; document.title = info.siteName + ' - 管理后台';
} }
if (info.siteLogo) { if (info.siteLogo) {
var link = document.querySelector('link[rel="icon"]'); var link = document.querySelector('link[rel="icon"]');
if (link) { if (link) {
link.href = info.siteLogo; link.href = info.siteLogo;
link.type = 'image/png'; link.type = 'image/png';
} }
} }
} catch (e) {} } catch (e) {}
} }
})(); })();
</script> </script>
<script type="module" crossorigin src="/assets/index-Dx-bvo36.js"></script> <script type="module" crossorigin src="/assets/index-Dx-bvo36.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D7ShJUt4.css"> <link rel="stylesheet" crossorigin href="/assets/index-D7ShJUt4.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
</body> </body>
</html> </html>
+17
View File
@@ -999,6 +999,23 @@ export async function getMaterialList(params: MaterialListParams): Promise<any>
return api.get(`/material-admin/material-list?${query.toString()}`); 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<any> {
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 ──────────────────────────────── // ── Private Portrait Admin ────────────────────────────────
export async function adminGetPrivatePortraitProjects(params: { userId?: string; keyword?: string; status?: string; page?: number; pageSize?: number } = {}): Promise<PrivatePortraitProjectListOut> { export async function adminGetPrivatePortraitProjects(params: { userId?: string; keyword?: string; status?: string; page?: number; pageSize?: number } = {}): Promise<PrivatePortraitProjectListOut> {
const query = new URLSearchParams(); const query = new URLSearchParams();
@@ -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<PreResultDisplayProps> = ({ preResult }) => {
const [parsedData, setParsedData] = useState<PreResultData | null>(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 <span style={{ color: '#64748b' }}>-</span>;
}
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: (
<Tag
key={key}
color={displayColor}
style={{ marginRight: 6, fontSize: 12, borderRadius: 4 }}
>
{displayLabel}
</Tag>
),
detail: {
label: config.label.replace('优质', '优质素材'),
value: displayLabel,
color: displayColor,
},
};
};
const content = (
<div style={{ maxWidth: 500, padding: '8px 0' }}>
<Typography.Text strong style={{ fontSize: 14, display: 'block', marginBottom: 12, color: '#1e293b' }}></Typography.Text>
<Descriptions column={1} size="small" style={{ marginBottom: 12 }}>
<Descriptions.Item label="视频ID" labelStyle={{ fontWeight: 500, color: '#64748b' }} contentStyle={{ color: '#1e293b' }}>{parsedData.video_id}</Descriptions.Item>
<Descriptions.Item label="广告主ID" labelStyle={{ fontWeight: 500, color: '#64748b' }} contentStyle={{ color: '#1e293b' }}>{parsedData.advertiser_id}</Descriptions.Item>
<Descriptions.Item label="素材ID" labelStyle={{ fontWeight: 500, color: '#64748b' }} contentStyle={{ color: '#1e293b' }}>{parsedData.material_id}</Descriptions.Item>
</Descriptions>
<Typography.Text strong style={{ fontSize: 12, color: '#64748b', display: 'block', marginBottom: 8 }}></Typography.Text>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginBottom: 12 }}>
{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 (
<Tag
key={config.key}
color={displayColor}
style={{ fontSize: 12, borderRadius: 4 }}
>
{displayLabel}
</Tag>
);
})}
</div>
{parsedData.not_ad_high_quality_reason && parsedData.not_ad_high_quality_reason.length > 0 && (
<div style={{ marginBottom: 12 }}>
<Typography.Text strong style={{ fontSize: 12, color: '#ef4444', display: 'block', marginBottom: 8 }}>
AD非优质原因
</Typography.Text>
<List
dataSource={parsedData.not_ad_high_quality_reason}
renderItem={(item, index) => (
<List.Item key={index} style={{ padding: '4px 0', fontSize: 12, color: '#64748b' }}>
{index + 1}. {item}
</List.Item>
)}
size="small"
/>
</div>
)}
{parsedData.not_ecp_high_quality_reason && parsedData.not_ecp_high_quality_reason.length > 0 && (
<div>
<Typography.Text strong style={{ fontSize: 12, color: '#ef4444', display: 'block', marginBottom: 8 }}>
</Typography.Text>
<List
dataSource={parsedData.not_ecp_high_quality_reason}
renderItem={(item, index) => (
<List.Item key={index} style={{ padding: '4px 0', fontSize: 12, color: '#64748b' }}>
{index + 1}. {item}
</List.Item>
)}
size="small"
/>
</div>
)}
</div>
);
return (
<Popover
content={content}
title={null}
trigger="hover"
placement="topLeft"
overlayStyle={{ borderRadius: 12, boxShadow: '0 8px 24px rgba(0,0,0,0.12)' }}
>
<div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 4}}>
{fieldConfig.map(config => {
const display = getIndicatorDisplay(config.key);
return display?.tag;
})}
</div>
</Popover>
);
};
export default PreResultDisplay;
@@ -1,7 +1,8 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Button, Input, Select, Table, Pagination, Tag, Typography } from 'antd'; import { Button, Input, Select, Table, Pagination, Tag, Typography, Tooltip } from 'antd';
import { FileTextOutlined, SearchOutlined } from '@ant-design/icons'; import { FileTextOutlined } from '@ant-design/icons';
import { getMaterialList } from '../api'; import { getMaterialList } from '../api';
import PreResultDisplay from '../components/PreResultDisplay';
const formatDateTime = (dateStr: string) => { const formatDateTime = (dateStr: string) => {
if (!dateStr) return ''; if (!dateStr) return '';
const date = new Date(dateStr); const date = new Date(dateStr);
@@ -48,6 +49,16 @@ const AdminMaterialList: React.FC = () => {
title: '备注', title: '备注',
dataIndex: 'note', dataIndex: 'note',
key: 'note', key: 'note',
width: 200,
render: (text: string) => (
<Input.TextArea
value={text || ''}
readOnly
autoSize={{ minRows: 1, maxRows: 4 }}
style={{ color: '#94a3b8', resize: 'none', border: 'none', background: 'transparent', padding: 0 }}
placeholder="-"
/>
),
}, },
{ {
title: '授权ID', title: '授权ID',
@@ -58,6 +69,8 @@ const AdminMaterialList: React.FC = () => {
title: '预测试结果', title: '预测试结果',
dataIndex: 'preResult', dataIndex: 'preResult',
key: 'preResult', key: 'preResult',
width: 120,
render: (text: string) => <PreResultDisplay preResult={text} />,
}, },
{ {
title: '预测试模板ID', title: '预测试模板ID',
@@ -94,7 +107,15 @@ const AdminMaterialList: React.FC = () => {
title: '上传ID', title: '上传ID',
dataIndex: 'uploadId', dataIndex: 'uploadId',
key: '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 (
<Tooltip title={v} placement="topLeft">
<Typography.Text style={{ fontSize: 12 }}>{short}</Typography.Text>
</Tooltip>
);
},
}, },
{ {
title: '资源类型', title: '资源类型',
@@ -145,13 +166,13 @@ const AdminMaterialList: React.FC = () => {
page: currentPage, page: currentPage,
page_size: pageSize, page_size: pageSize,
}); });
const data = res.items || []; const data = res.data || [];
const tableData = data.map((item: any, index: number) => ({ const tableData = data.map((item: any, index: number) => ({
...item, ...item,
index: (currentPage - 1) * pageSize + index + 1, index: (currentPage - 1) * pageSize + index + 1,
})); }));
setTableData(tableData); setTableData(tableData);
setTotal(res.total || 0); setTotal(res.pagination?.total || 0);
} catch (error) { } catch (error) {
console.error('加载数据失败:', error); console.error('加载数据失败:', error);
} finally { } finally {
@@ -1,18 +1,29 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Button, Input, Select, Table, Pagination, Tag, Typography } from 'antd'; import { Button, Input, Table, Pagination, Tag, Typography, DatePicker } from 'antd';
import { FileTextOutlined, SearchOutlined } from '@ant-design/icons'; 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 { Text } = Typography;
const AdminPreTestTemplates: React.FC = () => { const AdminPreTestTemplates: React.FC = () => {
const [tableData, setTableData] = useState<any[]>([]); const [tableData, setTableData] = useState<any[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(10); const [pageSize, setPageSize] = useState(10);
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [searchPlatform, setSearchPlatform] = useState(''); const [searchId, setSearchId] = useState('');
const [searchName, setSearchName] = useState(''); const [searchPhone, setSearchPhone] = useState('');
const [searchCreatedAt, setSearchCreatedAt] = useState<[dayjs.Dayjs, dayjs.Dayjs] | undefined>();
const columns = [ const columns = [
{ {
title: '序号', title: '序号',
@@ -22,92 +33,180 @@ const AdminPreTestTemplates: React.FC = () => {
render: (text: number) => <span style={{ color: '#64748b' }}>{text}</span>, 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', dataIndex: 'name',
key: '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', dataIndex: 'platform',
key: 'platform', key: 'platform',
width: 120, width: 100,
render: (text: string) => (
<Tag color={text === 'AD' ? 'blue' : text === 'QIANCHUAN' ? 'green' : 'orange'}>
{text === 'AD' ? 'AD' : text === 'QIANCHUAN' ? '千川' : '本地推'}
</Tag>
),
}, },
{ {
title: '状态', title: '定价类型',
dataIndex: 'status', dataIndex: 'pricingType',
key: 'status', key: 'pricingType',
width: 100, width: 100,
render: (text: string) => ( },
<Tag color={text === 'active' ? 'green' : 'red'}> {
{text === 'active' ? '启用' : '禁用'} title: '目标成本',
</Tag> 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: '创建时间', title: '创建时间',
dataIndex: 'createdAt', dataIndex: 'createdAt',
key: 'createdAt', key: 'createdAt',
width: 180, width: 180,
render: (text: string) => <span style={{ color: '#64748b' }}>{text}</span>, render: (text: string) => <span style={{ color: '#64748b' }}>{formatDateTime(text)}</span>,
}, },
{ {
title: '更新时间', title: '更新时间',
dataIndex: 'updatedAt', dataIndex: 'updatedAt',
key: 'updatedAt', key: 'updatedAt',
width: 180, width: 180,
render: (text: string) => <span style={{ color: '#64748b' }}>{text}</span>, render: (text: string) => <span style={{ color: '#64748b' }}>{formatDateTime(text)}</span>,
}, },
]; ];
const loadRecords = async () => { const loadRecords = async () => {
setLoading(true); setLoading(true);
try { try {
const params = new URLSearchParams(); const res = await getPreTestTemplateList({
params.set('page', String(currentPage)); id: searchId || undefined,
params.set('page_size', String(pageSize)); phone: searchPhone || undefined,
if (searchPlatform) params.set('platform', searchPlatform); created_at: searchCreatedAt ? [searchCreatedAt[0].format('YYYY-MM-DD'), searchCreatedAt[1].format('YYYY-MM-DD')] : undefined,
if (searchName) params.set('name', searchName); page: currentPage,
const mockData = { page_size: pageSize,
items: Array.from({ length: pageSize }, (_, i) => ({ });
id: `${currentPage}-${i}`, const data = res.data || [];
index: (currentPage - 1) * pageSize + i + 1, const tableData = data.map((item: any, index: number) => ({
name: `前测模板${(currentPage - 1) * pageSize + i + 1}`, ...item,
platform: ['AD', 'QIANCHUAN', 'LOCAL'][i % 3], index: (currentPage - 1) * pageSize + index + 1,
status: i % 5 === 0 ? 'inactive' : 'active', }));
createdAt: '2026-07-01 10:00:00', setTableData(tableData);
updatedAt: '2026-07-02 14:30:00', setTotal(res.pagination.total || 0);
})),
total: 50,
};
setTableData(mockData.items);
setTotal(mockData.total);
} catch (error) { } catch (error) {
console.error('加载数据失败:', error); console.error('加载数据失败:', error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
useEffect(() => { useEffect(() => {
loadRecords(); loadRecords();
}, [currentPage, pageSize]); }, [currentPage, pageSize]);
const handlePageChange = (page: number, size: number) => { const handlePageChange = (page: number, size: number) => {
setCurrentPage(page); setCurrentPage(page);
setPageSize(size); setPageSize(size);
}; };
const handleSearch = () => { const handleSearch = () => {
setCurrentPage(1); setCurrentPage(1);
loadRecords(); loadRecords();
}; };
return ( return (
<div> <div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
@@ -115,26 +214,34 @@ const AdminPreTestTemplates: React.FC = () => {
<Text strong style={{ fontSize: 16 }}></Text> <Text strong style={{ fontSize: 16 }}></Text>
</div> </div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}> <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 <Input
placeholder="模板名称" placeholder="ID"
value={searchName} value={searchId}
onChange={(e) => setSearchName(e.target.value)} onChange={(e) => setSearchId(e.target.value)}
style={{ width: 180 }} style={{ width: 160 }}
allowClear allowClear
onPressEnter={handleSearch} onPressEnter={handleSearch}
/> />
<Select <Input
placeholder="投放平台" placeholder="手机号"
value={searchPlatform} value={searchPhone}
onChange={(value) => setSearchPlatform(value)} onChange={(e) => setSearchPhone(e.target.value)}
style={{ width: 140 }} style={{ width: 160 }}
allowClear allowClear
options={[ onPressEnter={handleSearch}
{ value: 'AD', label: 'AD' }, />
{ value: 'QIANCHUAN', label: '千川' }, <DatePicker.RangePicker
{ value: 'LOCAL', label: '本地推' }, placeholder={['开始日期', '结束日期']}
]} value={searchCreatedAt}
onChange={(dates) => {
if (dates && dates[0] && dates[1]) {
setSearchCreatedAt([dates[0], dates[1]]);
} else {
setSearchCreatedAt(undefined);
}
}}
style={{ width: 260 }}
/> />
<Button <Button
type="primary" type="primary"
@@ -187,5 +294,4 @@ const AdminPreTestTemplates: React.FC = () => {
</div> </div>
); );
}; };
export default AdminPreTestTemplates; export default AdminPreTestTemplates;
+1 -1
View File
@@ -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"} {"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"}