添加城市地区缓存
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>
|
||||||
<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">
|
<link rel="stylesheet" crossorigin href="/assets/index-BhPFzWLH.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -2,27 +2,29 @@ import React, { useState } from 'react';
|
|||||||
import { Popover, Tag, List, Descriptions, Typography } from 'antd';
|
import { Popover, Tag, List, Descriptions, Typography } from 'antd';
|
||||||
|
|
||||||
interface PreResultData {
|
interface PreResultData {
|
||||||
video_id: string; //视频id
|
video_id: string;
|
||||||
advertiser_id: number; //广告主id
|
advertiser_id: number;
|
||||||
material_id: string; //素材id
|
material_id: string;
|
||||||
is_ad_high_quality_material: string; //是否优质素材
|
is_ad_high_quality_material: string;
|
||||||
is_ecp_high_quality_material: string; //是否千川优质素材
|
is_ecp_high_quality_material: string;
|
||||||
is_inefficient_material: string; //是否低效素材
|
is_inefficient_material: string;
|
||||||
is_first_publish_material: string; //是否首发素材
|
is_first_publish_material: string;
|
||||||
not_ad_high_quality_reason: string[] | null; //AD非优质原因
|
is_local_high_quality_material: string;
|
||||||
not_ecp_high_quality_reason: string[] | null; //千川非优质原因
|
not_ad_high_quality_reason: string[] | null;
|
||||||
is_local_high_quality_material: string; //是否本地推优质素材
|
not_ecp_high_quality_reason: string[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PreResultDisplayProps {
|
interface PreResultDisplayProps {
|
||||||
preResult: string;
|
preResult: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const qualityConfig: Record<string, { color: string; label: string }> = {
|
const fieldConfig = [
|
||||||
YES: { color: 'green', label: '是' },
|
{ key: 'is_ad_high_quality_material', label: 'AD优质', noLabel: 'AD非优质素材', unknownLabel: 'AD未知', yesColor: 'green', noColor: 'red', unknownColor: 'default' },
|
||||||
NO: { color: 'red', label: '否' },
|
{ key: 'is_ecp_high_quality_material', label: '千川优质', noLabel: '千川非优质素材', unknownLabel: '千川未知', yesColor: 'green', noColor: 'red', unknownColor: 'default' },
|
||||||
UNKNOWN: { color: 'default', label: '未知' },
|
{ 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 PreResultDisplay: React.FC<PreResultDisplayProps> = ({ preResult }) => {
|
||||||
const [parsedData, setParsedData] = useState<PreResultData | null>(null);
|
const [parsedData, setParsedData] = useState<PreResultData | null>(null);
|
||||||
@@ -48,51 +50,79 @@ const PreResultDisplay: React.FC<PreResultDisplayProps> = ({ preResult }) => {
|
|||||||
return <span style={{ color: '#64748b' }}>-</span>;
|
return <span style={{ color: '#64748b' }}>-</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const allQualityFields = [
|
const getIndicatorDisplay = (key: string) => {
|
||||||
parsedData.is_ad_high_quality_material,
|
const config = fieldConfig.find(c => c.key === key);
|
||||||
parsedData.is_ecp_high_quality_material,
|
if (!config) return null;
|
||||||
parsedData.is_local_high_quality_material,
|
const value = parsedData[key as keyof PreResultData];
|
||||||
];
|
const isYes = value === 'YES';
|
||||||
|
const isUnknown = value === 'UNKNOWN';
|
||||||
const hasNoQuality = allQualityFields.some((val) => val === 'NO');
|
let displayLabel, displayColor;
|
||||||
const allYes = allQualityFields.every((val) => val === 'YES');
|
if (isUnknown) {
|
||||||
|
displayLabel = config.unknownLabel;
|
||||||
let statusTag;
|
displayColor = config.unknownColor;
|
||||||
if (hasNoQuality) {
|
} else if (isYes) {
|
||||||
statusTag = <Tag color="red">非优质</Tag>;
|
displayLabel = config.label;
|
||||||
} else if (allYes) {
|
displayColor = config.yesColor;
|
||||||
statusTag = <Tag color="green">优质</Tag>;
|
} else {
|
||||||
} else {
|
displayLabel = config.noLabel;
|
||||||
statusTag = <Tag color="default">待评估</Tag>;
|
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 = (
|
const content = (
|
||||||
<div style={{ maxWidth: 500 }}>
|
<div style={{ maxWidth: 500, padding: '8px 0' }}>
|
||||||
<Typography.Text strong style={{ fontSize: 14, display: 'block', marginBottom: 12 }}>前测结果详情</Typography.Text>
|
<Typography.Text strong style={{ fontSize: 14, display: 'block', marginBottom: 12, color: '#1e293b' }}>前测结果详情</Typography.Text>
|
||||||
|
|
||||||
<Descriptions column={1} size="small" style={{ marginBottom: 12 }}>
|
<Descriptions column={1} size="small" style={{ marginBottom: 12 }}>
|
||||||
<Descriptions.Item label="视频ID">{parsedData.video_id}</Descriptions.Item>
|
<Descriptions.Item label="视频ID" labelStyle={{ fontWeight: 500, color: '#64748b' }} contentStyle={{ color: '#1e293b' }}>{parsedData.video_id}</Descriptions.Item>
|
||||||
<Descriptions.Item label="广告主ID">{parsedData.advertiser_id}</Descriptions.Item>
|
<Descriptions.Item label="广告主ID" labelStyle={{ fontWeight: 500, color: '#64748b' }} contentStyle={{ color: '#1e293b' }}>{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.material_id}</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
|
|
||||||
<Typography.Text strong style={{ fontSize: 12, color: '#64748b', display: 'block', marginBottom: 8 }}>质量评估</Typography.Text>
|
<Typography.Text strong style={{ fontSize: 12, color: '#64748b', display: 'block', marginBottom: 8 }}>质量评估</Typography.Text>
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginBottom: 12 }}>
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginBottom: 12 }}>
|
||||||
<Tag color={qualityConfig[parsedData.is_ad_high_quality_material]?.color}>
|
{fieldConfig.map(config => {
|
||||||
AD优质素材: {qualityConfig[parsedData.is_ad_high_quality_material]?.label}
|
const value = parsedData[config.key as keyof PreResultData];
|
||||||
</Tag>
|
const isYes = value === 'YES';
|
||||||
<Tag color={qualityConfig[parsedData.is_ecp_high_quality_material]?.color}>
|
const isUnknown = value === 'UNKNOWN';
|
||||||
千川优质素材: {qualityConfig[parsedData.is_ecp_high_quality_material]?.label}
|
let displayLabel, displayColor;
|
||||||
</Tag>
|
if (isUnknown) {
|
||||||
<Tag color={qualityConfig[parsedData.is_local_high_quality_material]?.color}>
|
displayLabel = config.unknownLabel;
|
||||||
本地推优质素材: {qualityConfig[parsedData.is_local_high_quality_material]?.label}
|
displayColor = config.unknownColor;
|
||||||
</Tag>
|
} else if (isYes) {
|
||||||
<Tag color={qualityConfig[parsedData.is_inefficient_material]?.color}>
|
displayLabel = config.label;
|
||||||
低效素材: {qualityConfig[parsedData.is_inefficient_material]?.label}
|
displayColor = config.yesColor;
|
||||||
</Tag>
|
} else {
|
||||||
<Tag color={qualityConfig[parsedData.is_first_publish_material]?.color}>
|
displayLabel = config.noLabel;
|
||||||
首发素材: {qualityConfig[parsedData.is_first_publish_material]?.label}
|
displayColor = config.noColor;
|
||||||
</Tag>
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
key={config.key}
|
||||||
|
color={displayColor}
|
||||||
|
style={{ fontSize: 12, borderRadius: 4 }}
|
||||||
|
>
|
||||||
|
{displayLabel}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{parsedData.not_ad_high_quality_reason && parsedData.not_ad_high_quality_reason.length > 0 && (
|
{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 (
|
return (
|
||||||
<Popover content={content} title={null} trigger="hover">
|
<Popover
|
||||||
{statusTag}
|
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>
|
</Popover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PreResultDisplay;
|
export default PreResultDisplay;
|
||||||
|
|||||||
@@ -148,6 +148,8 @@ const PreTest: React.FC = () => {
|
|||||||
const [pageSize, setPageSize] = useState(10);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
const [platform, setPlatform] = useState<string>('AD');
|
const [platform, setPlatform] = useState<string>('AD');
|
||||||
const [areaOptions, setAreaOptions] = useState<any[]>([]);
|
const [areaOptions, setAreaOptions] = useState<any[]>([]);
|
||||||
|
const [cityCodeMap, setCityCodeMap] = useState<Record<string, string>>({});
|
||||||
|
const [provinceCityMap, setProvinceCityMap] = useState<Record<string, string[]>>({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadRecords();
|
loadRecords();
|
||||||
@@ -174,20 +176,66 @@ const PreTest: React.FC = () => {
|
|||||||
|
|
||||||
const loadProvinceOptions = async () => {
|
const loadProvinceOptions = async () => {
|
||||||
try {
|
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 res = await getArea({ level: 'ONE_LEVEL' });
|
||||||
const options = (res.data || []).map((item: any) => ({
|
const provinces = res.data || [];
|
||||||
value: item.code,
|
const map: Record<string, string> = {};
|
||||||
label: item.name,
|
const provinceCity: Record<string, string[]> = {};
|
||||||
isLeaf: false,
|
const provinceOptions = await Promise.all(
|
||||||
}));
|
provinces.map(async (province: any) => {
|
||||||
setAreaOptions(options);
|
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) {
|
} catch (error) {
|
||||||
message.error('获取省份信息失败');
|
message.error('获取省份信息失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadProvince = async () => {
|
||||||
|
const cityRes = await getArea({ level: 'TWO_LEVEL' });
|
||||||
|
console.log(cityRes);
|
||||||
|
}
|
||||||
|
|
||||||
const loadCityOptions = async (selectedOptions: any) => {
|
const loadCityOptions = async (selectedOptions: any) => {
|
||||||
const targetOption = selectedOptions[selectedOptions.length - 1];
|
const targetOption = selectedOptions[selectedOptions.length - 1];
|
||||||
|
console.log(targetOption);
|
||||||
targetOption.loading = true;
|
targetOption.loading = true;
|
||||||
try {
|
try {
|
||||||
const res = await getArea({ level: 'TWO_LEVEL', parent_code: targetOption.value });
|
const res = await getArea({ level: 'TWO_LEVEL', parent_code: targetOption.value });
|
||||||
@@ -281,13 +329,25 @@ const PreTest: React.FC = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '受众地区',
|
title: '受众地区',
|
||||||
dataIndex: 'audience_region',
|
dataIndex: 'audienceRegion',
|
||||||
key: 'audience_region',
|
key: 'audienceRegion',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
width: 150,
|
width: 300,
|
||||||
render: (text: string[]) => (
|
render: (text: string[]) => {
|
||||||
<span style={{ color: '#1e293b', fontSize: 13 }}>{Array.isArray(text) ? text.join(', ') : text || '-'}</span>
|
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: '网络类型',
|
title: '网络类型',
|
||||||
@@ -400,11 +460,22 @@ const PreTest: React.FC = () => {
|
|||||||
setModalOpen(true);
|
setModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fillFormWithDetail = (data: any) => {
|
const fillFormWithDetail = async (data: any) => {
|
||||||
setCurrentRecord(data);
|
setCurrentRecord(data);
|
||||||
setPlatform(data.platform || 'AD');
|
setPlatform(data.platform || 'AD');
|
||||||
const regionCodes = data.audience_region || [];
|
const regionCodes = data.audienceRegion || [];
|
||||||
const regionPaths = regionCodes.map((code: any) => [String(code)]);
|
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({
|
form.setFieldsValue({
|
||||||
name: data.name,
|
name: data.name,
|
||||||
note: data.note,
|
note: data.note,
|
||||||
@@ -478,7 +549,17 @@ const PreTest: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
const values = await form.validateFields();
|
const values = await form.validateFields();
|
||||||
setSubmitLoading(true);
|
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 = {
|
const params = {
|
||||||
name: values.name,
|
name: values.name,
|
||||||
note: values.note,
|
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) => {
|
const handlePageChange = (page: number, size: number) => {
|
||||||
setCurrentPage(page);
|
setCurrentPage(page);
|
||||||
setPageSize(size);
|
setPageSize(size);
|
||||||
@@ -768,8 +854,6 @@ const PreTest: React.FC = () => {
|
|||||||
placeholder="请选择受众地区(可多选)"
|
placeholder="请选择受众地区(可多选)"
|
||||||
style={{ borderRadius: 8, minHeight: 40 }}
|
style={{ borderRadius: 8, minHeight: 40 }}
|
||||||
options={areaOptions}
|
options={areaOptions}
|
||||||
loadData={loadCityOptions}
|
|
||||||
changeOnSelect
|
|
||||||
maxTagCount="responsive"
|
maxTagCount="responsive"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
Reference in New Issue
Block a user