添加城市地区缓存
This commit is contained in:
+442
File diff suppressed because one or more lines are too long
+442
File diff suppressed because one or more lines are too long
+442
File diff suppressed because one or more lines are too long
+442
File diff suppressed because one or more lines are too long
+442
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -28,7 +28,7 @@
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-Cs6AfEXe.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-DNQfr5S_.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BhPFzWLH.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -2,27 +2,29 @@ import React, { useState } from 'react';
|
||||
import { Popover, Tag, List, Descriptions, Typography } from 'antd';
|
||||
|
||||
interface PreResultData {
|
||||
video_id: string; //视频id
|
||||
advertiser_id: number; //广告主id
|
||||
material_id: string; //素材id
|
||||
is_ad_high_quality_material: string; //是否优质素材
|
||||
is_ecp_high_quality_material: string; //是否千川优质素材
|
||||
is_inefficient_material: string; //是否低效素材
|
||||
is_first_publish_material: string; //是否首发素材
|
||||
not_ad_high_quality_reason: string[] | null; //AD非优质原因
|
||||
not_ecp_high_quality_reason: string[] | null; //千川非优质原因
|
||||
is_local_high_quality_material: string; //是否本地推优质素材
|
||||
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 qualityConfig: Record<string, { color: string; label: string }> = {
|
||||
YES: { color: 'green', label: '是' },
|
||||
NO: { color: 'red', label: '否' },
|
||||
UNKNOWN: { color: 'default', label: '未知' },
|
||||
};
|
||||
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);
|
||||
@@ -48,51 +50,79 @@ const PreResultDisplay: React.FC<PreResultDisplayProps> = ({ preResult }) => {
|
||||
return <span style={{ color: '#64748b' }}>-</span>;
|
||||
}
|
||||
|
||||
const allQualityFields = [
|
||||
parsedData.is_ad_high_quality_material,
|
||||
parsedData.is_ecp_high_quality_material,
|
||||
parsedData.is_local_high_quality_material,
|
||||
];
|
||||
|
||||
const hasNoQuality = allQualityFields.some((val) => val === 'NO');
|
||||
const allYes = allQualityFields.every((val) => val === 'YES');
|
||||
|
||||
let statusTag;
|
||||
if (hasNoQuality) {
|
||||
statusTag = <Tag color="red">非优质</Tag>;
|
||||
} else if (allYes) {
|
||||
statusTag = <Tag color="green">优质</Tag>;
|
||||
} else {
|
||||
statusTag = <Tag color="default">待评估</Tag>;
|
||||
}
|
||||
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 isUnknown = value === 'UNKNOWN';
|
||||
let displayLabel, displayColor;
|
||||
if (isUnknown) {
|
||||
displayLabel = config.unknownLabel;
|
||||
displayColor = config.unknownColor;
|
||||
} else if (isYes) {
|
||||
displayLabel = config.label;
|
||||
displayColor = config.yesColor;
|
||||
} else {
|
||||
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 }}>
|
||||
<Typography.Text strong style={{ fontSize: 14, display: 'block', marginBottom: 12 }}>前测结果详情</Typography.Text>
|
||||
<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">{parsedData.video_id}</Descriptions.Item>
|
||||
<Descriptions.Item label="广告主ID">{parsedData.advertiser_id}</Descriptions.Item>
|
||||
<Descriptions.Item label="素材ID">{parsedData.material_id}</Descriptions.Item>
|
||||
<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 }}>
|
||||
<Tag color={qualityConfig[parsedData.is_ad_high_quality_material]?.color}>
|
||||
AD优质素材: {qualityConfig[parsedData.is_ad_high_quality_material]?.label}
|
||||
</Tag>
|
||||
<Tag color={qualityConfig[parsedData.is_ecp_high_quality_material]?.color}>
|
||||
千川优质素材: {qualityConfig[parsedData.is_ecp_high_quality_material]?.label}
|
||||
</Tag>
|
||||
<Tag color={qualityConfig[parsedData.is_local_high_quality_material]?.color}>
|
||||
本地推优质素材: {qualityConfig[parsedData.is_local_high_quality_material]?.label}
|
||||
</Tag>
|
||||
<Tag color={qualityConfig[parsedData.is_inefficient_material]?.color}>
|
||||
低效素材: {qualityConfig[parsedData.is_inefficient_material]?.label}
|
||||
</Tag>
|
||||
<Tag color={qualityConfig[parsedData.is_first_publish_material]?.color}>
|
||||
首发素材: {qualityConfig[parsedData.is_first_publish_material]?.label}
|
||||
</Tag>
|
||||
{fieldConfig.map(config => {
|
||||
const value = parsedData[config.key as keyof PreResultData];
|
||||
const isYes = value === 'YES';
|
||||
const isUnknown = value === 'UNKNOWN';
|
||||
let displayLabel, displayColor;
|
||||
if (isUnknown) {
|
||||
displayLabel = config.unknownLabel;
|
||||
displayColor = config.unknownColor;
|
||||
} else if (isYes) {
|
||||
displayLabel = config.label;
|
||||
displayColor = config.yesColor;
|
||||
} else {
|
||||
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 && (
|
||||
@@ -132,10 +162,21 @@ const PreResultDisplay: React.FC<PreResultDisplayProps> = ({ preResult }) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover content={content} title={null} trigger="hover">
|
||||
{statusTag}
|
||||
<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;
|
||||
export default PreResultDisplay;
|
||||
|
||||
@@ -148,6 +148,8 @@ const PreTest: React.FC = () => {
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [platform, setPlatform] = useState<string>('AD');
|
||||
const [areaOptions, setAreaOptions] = useState<any[]>([]);
|
||||
const [cityCodeMap, setCityCodeMap] = useState<Record<string, string>>({});
|
||||
const [provinceCityMap, setProvinceCityMap] = useState<Record<string, string[]>>({});
|
||||
|
||||
useEffect(() => {
|
||||
loadRecords();
|
||||
@@ -174,20 +176,66 @@ const PreTest: React.FC = () => {
|
||||
|
||||
const loadProvinceOptions = async () => {
|
||||
try {
|
||||
const cachedData = localStorage.getItem('preTestAreaData');
|
||||
if (cachedData) {
|
||||
const parsed = JSON.parse(cachedData);
|
||||
setAreaOptions(parsed.areaOptions);
|
||||
setCityCodeMap(parsed.cityCodeMap);
|
||||
setProvinceCityMap(parsed.provinceCityMap);
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await getArea({ level: 'ONE_LEVEL' });
|
||||
const options = (res.data || []).map((item: any) => ({
|
||||
value: item.code,
|
||||
label: item.name,
|
||||
isLeaf: false,
|
||||
}));
|
||||
setAreaOptions(options);
|
||||
const provinces = res.data || [];
|
||||
const map: Record<string, string> = {};
|
||||
const provinceCity: Record<string, string[]> = {};
|
||||
const provinceOptions = await Promise.all(
|
||||
provinces.map(async (province: any) => {
|
||||
let cities: any[] = [];
|
||||
try {
|
||||
const cityRes = await getArea({ level: 'TWO_LEVEL', parent_code: province.code });
|
||||
cities = (cityRes.data || []).map((city: any) => ({
|
||||
value: city.code,
|
||||
label: city.name,
|
||||
isLeaf: true,
|
||||
}));
|
||||
const cityCodes = cities.map((city: any) => city.value);
|
||||
provinceCity[province.code] = cityCodes;
|
||||
cities.forEach((city: any) => {
|
||||
map[city.value] = city.label;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`获取省份 ${province.name} 的城市失败`, error);
|
||||
provinceCity[province.code] = [];
|
||||
}
|
||||
return {
|
||||
value: province.code,
|
||||
label: province.name,
|
||||
isLeaf: false,
|
||||
children: cities,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const cacheData = { areaOptions: provinceOptions, cityCodeMap: map, provinceCityMap: provinceCity };
|
||||
localStorage.setItem('preTestAreaData', JSON.stringify(cacheData));
|
||||
|
||||
setAreaOptions(provinceOptions);
|
||||
setCityCodeMap(map);
|
||||
setProvinceCityMap(provinceCity);
|
||||
} catch (error) {
|
||||
message.error('获取省份信息失败');
|
||||
}
|
||||
};
|
||||
|
||||
const loadProvince = async () => {
|
||||
const cityRes = await getArea({ level: 'TWO_LEVEL' });
|
||||
console.log(cityRes);
|
||||
}
|
||||
|
||||
const loadCityOptions = async (selectedOptions: any) => {
|
||||
const targetOption = selectedOptions[selectedOptions.length - 1];
|
||||
console.log(targetOption);
|
||||
targetOption.loading = true;
|
||||
try {
|
||||
const res = await getArea({ level: 'TWO_LEVEL', parent_code: targetOption.value });
|
||||
@@ -281,13 +329,25 @@ const PreTest: React.FC = () => {
|
||||
},
|
||||
{
|
||||
title: '受众地区',
|
||||
dataIndex: 'audience_region',
|
||||
key: 'audience_region',
|
||||
dataIndex: 'audienceRegion',
|
||||
key: 'audienceRegion',
|
||||
ellipsis: true,
|
||||
width: 150,
|
||||
render: (text: string[]) => (
|
||||
<span style={{ color: '#1e293b', fontSize: 13 }}>{Array.isArray(text) ? text.join(', ') : text || '-'}</span>
|
||||
),
|
||||
width: 300,
|
||||
render: (text: string[]) => {
|
||||
if (!Array.isArray(text) || text.length === 0) {
|
||||
return <span style={{ color: '#94a3b8', fontSize: 13 }}>-</span>;
|
||||
}
|
||||
const cityNames = text.map(code => cityCodeMap[String(code)] || String(code));
|
||||
return (
|
||||
<Input.TextArea
|
||||
value={cityNames.join(', ') || '-'}
|
||||
readOnly
|
||||
autoSize={{ minRows: 1, maxRows: 4 }}
|
||||
style={{ resize: 'none', border: 'none', background: 'transparent', padding: 0 }}
|
||||
placeholder="-"
|
||||
/>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '网络类型',
|
||||
@@ -400,11 +460,22 @@ const PreTest: React.FC = () => {
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
const fillFormWithDetail = (data: any) => {
|
||||
const fillFormWithDetail = async (data: any) => {
|
||||
setCurrentRecord(data);
|
||||
setPlatform(data.platform || 'AD');
|
||||
const regionCodes = data.audience_region || [];
|
||||
const regionPaths = regionCodes.map((code: any) => [String(code)]);
|
||||
const regionCodes = data.audienceRegion || [];
|
||||
const regionPaths: string[][] = [];
|
||||
for (const code of regionCodes) {
|
||||
const cityCode = String(code);
|
||||
console.log(cityCode);
|
||||
if (cityCode.length >= 6) {
|
||||
const provinceCode = cityCode.slice(0, 2);
|
||||
console.log(provinceCode, cityCode);
|
||||
loadCityOptions(provinceCode);
|
||||
regionPaths.push([provinceCode, cityCode]);
|
||||
}
|
||||
}
|
||||
console.log(regionPaths);
|
||||
form.setFieldsValue({
|
||||
name: data.name,
|
||||
note: data.note,
|
||||
@@ -478,7 +549,17 @@ const PreTest: React.FC = () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
setSubmitLoading(true);
|
||||
const regionCodes = (values.audience_region || []).map((path: string[]) => path[path.length - 1]);
|
||||
console.log(values.audience_region);
|
||||
const regionCodes: string[] = [];
|
||||
(values.audience_region || []).forEach((path: string[]) => {
|
||||
if (path.length === 1) {
|
||||
const provinceCode = path[0];
|
||||
const cities = provinceCityMap[provinceCode] || [];
|
||||
regionCodes.push(...cities);
|
||||
} else if (path.length >= 2) {
|
||||
regionCodes.push(path[path.length - 1]);
|
||||
}
|
||||
});
|
||||
const params = {
|
||||
name: values.name,
|
||||
note: values.note,
|
||||
@@ -524,6 +605,11 @@ const PreTest: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// [['34', '340800'],['35', '350100']]
|
||||
// [['35', '350100']]
|
||||
// [['34'],['35']]
|
||||
// [['50']]
|
||||
|
||||
const handlePageChange = (page: number, size: number) => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(size);
|
||||
@@ -768,8 +854,6 @@ const PreTest: React.FC = () => {
|
||||
placeholder="请选择受众地区(可多选)"
|
||||
style={{ borderRadius: 8, minHeight: 40 }}
|
||||
options={areaOptions}
|
||||
loadData={loadCityOptions}
|
||||
changeOnSelect
|
||||
maxTagCount="responsive"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
Reference in New Issue
Block a user