click subpath
This commit is contained in:
@@ -292,6 +292,8 @@ function AnalyticsContent() {
|
||||
const [selectedProjectIds, setSelectedProjectIds] = useState<string[]>([]);
|
||||
// 添加标签名称状态 - 用于在UI中显示和API请求
|
||||
const [selectedTagNames, setSelectedTagNames] = useState<string[]>([]);
|
||||
// 添加子路径筛选状态
|
||||
const [selectedSubpath, setSelectedSubpath] = useState<string>('');
|
||||
// 添加分页状态
|
||||
const [currentPage, setCurrentPage] = useState<number>(1);
|
||||
const [pageSize, setPageSize] = useState<number>(10);
|
||||
@@ -377,6 +379,11 @@ function AnalyticsContent() {
|
||||
params.append('tagName', tagName);
|
||||
});
|
||||
}
|
||||
|
||||
// 添加子路径筛选参数
|
||||
if (selectedSubpath) {
|
||||
params.append('subpath', selectedSubpath);
|
||||
}
|
||||
|
||||
// 记录构建的 URL,以确保参数正确包含
|
||||
const summaryUrl = `${baseUrl}/summary?${params.toString()}`;
|
||||
@@ -446,7 +453,7 @@ function AnalyticsContent() {
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, [dateRange, selectedTeamIds, selectedProjectIds, selectedTagNames, currentPage, pageSize, selectedShortUrl, shouldFetchData]);
|
||||
}, [dateRange, selectedTeamIds, selectedProjectIds, selectedTagNames, selectedSubpath, currentPage, pageSize, selectedShortUrl, shouldFetchData]);
|
||||
|
||||
// Function to clear the shorturl filter
|
||||
const handleClearShortUrlFilter = () => {
|
||||
@@ -467,6 +474,19 @@ function AnalyticsContent() {
|
||||
window.location.href = newUrl.toString();
|
||||
};
|
||||
|
||||
// 清除子路径筛选
|
||||
const handleClearSubpathFilter = () => {
|
||||
setSelectedSubpath('');
|
||||
};
|
||||
|
||||
// 处理子路径点击
|
||||
const handlePathClick = (path: string) => {
|
||||
console.log('Path clicked:', path);
|
||||
setSelectedSubpath(path);
|
||||
// 重置到第一页
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
@@ -553,6 +573,23 @@ function AnalyticsContent() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 如果有选定的 subpath,显示提示 */}
|
||||
{selectedSubpath && (
|
||||
<div className="bg-blue-100 text-blue-800 px-3 py-2 rounded-md text-sm flex items-center justify-between">
|
||||
<div>
|
||||
<span>Filtered by Channel:</span>
|
||||
<span className="ml-2 font-medium">{selectedSubpath}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleClearSubpathFilter}
|
||||
className="ml-3 text-blue-700 hover:text-blue-900 p-1 rounded-full hover:bg-blue-200"
|
||||
aria-label="Clear subpath filter"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 只在没有选中 shorturl 时显示筛选选择器 */}
|
||||
{!selectedShortUrl && (
|
||||
<>
|
||||
@@ -670,6 +707,7 @@ function AnalyticsContent() {
|
||||
projectIds={selectedProjectIds}
|
||||
tagIds={selectedTagNames}
|
||||
linkId={selectedShortUrl?.externalId}
|
||||
subpath={selectedSubpath}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -681,6 +719,7 @@ function AnalyticsContent() {
|
||||
startTime={format(dateRange.from, "yyyy-MM-dd'T'HH:mm:ss'Z'")}
|
||||
endTime={format(dateRange.to, "yyyy-MM-dd'T'HH:mm:ss'Z'")}
|
||||
linkId={selectedShortUrl.externalId}
|
||||
onPathClick={handlePathClick}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -18,7 +18,9 @@ export async function GET(request: NextRequest) {
|
||||
// 添加团队、项目和标签筛选
|
||||
teamIds: teamIds.length > 0 ? teamIds : undefined,
|
||||
projectIds: projectIds.length > 0 ? projectIds : undefined,
|
||||
tagIds: tagIds.length > 0 ? tagIds : undefined
|
||||
tagIds: tagIds.length > 0 ? tagIds : undefined,
|
||||
// 添加子路径筛选
|
||||
subpath: searchParams.get('subpath') || undefined
|
||||
});
|
||||
|
||||
const response: ApiResponse<typeof data> = {
|
||||
|
||||
@@ -22,7 +22,9 @@ export async function GET(request: NextRequest) {
|
||||
// 添加团队、项目和标签筛选
|
||||
teamIds: teamIds.length > 0 ? teamIds : undefined,
|
||||
projectIds: projectIds.length > 0 ? projectIds : undefined,
|
||||
tagIds: tagIds.length > 0 ? tagIds : undefined
|
||||
tagIds: tagIds.length > 0 ? tagIds : undefined,
|
||||
// 添加子路径筛选
|
||||
subpath: searchParams.get('subpath') || undefined
|
||||
});
|
||||
|
||||
const response: ApiResponse<typeof data> = {
|
||||
|
||||
@@ -13,7 +13,9 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
// Add debug log to check if linkId is being received
|
||||
const linkId = searchParams.get('linkId');
|
||||
const subpath = searchParams.get('subpath');
|
||||
console.log('Summary API received linkId:', linkId);
|
||||
console.log('Summary API received subpath:', subpath);
|
||||
console.log('Summary API full parameters:', Object.fromEntries(searchParams.entries()));
|
||||
console.log('Summary API URL:', request.url);
|
||||
|
||||
@@ -23,7 +25,8 @@ export async function GET(request: NextRequest) {
|
||||
linkId: searchParams.get('linkId') || undefined,
|
||||
teamIds: teamIds.length > 0 ? teamIds : undefined,
|
||||
projectIds: projectIds.length > 0 ? projectIds : undefined,
|
||||
tagIds: tagIds.length > 0 ? tagIds : undefined
|
||||
tagIds: tagIds.length > 0 ? tagIds : undefined,
|
||||
subpath: searchParams.get('subpath') || undefined
|
||||
});
|
||||
|
||||
const response: ApiResponse<typeof summary> = {
|
||||
|
||||
@@ -28,7 +28,9 @@ export async function GET(request: NextRequest) {
|
||||
// 添加团队、项目和标签筛选
|
||||
teamIds: teamIds.length > 0 ? teamIds : undefined,
|
||||
projectIds: projectIds.length > 0 ? projectIds : undefined,
|
||||
tagIds: tagIds.length > 0 ? tagIds : undefined
|
||||
tagIds: tagIds.length > 0 ? tagIds : undefined,
|
||||
// 添加子路径筛选
|
||||
subpath: searchParams.get('subpath') || undefined
|
||||
});
|
||||
|
||||
const response: ApiResponse<typeof data> = {
|
||||
|
||||
@@ -24,6 +24,7 @@ export async function GET(request: NextRequest) {
|
||||
const startTime = searchParams.get('startTime');
|
||||
const endTime = searchParams.get('endTime');
|
||||
const linkId = searchParams.get('linkId');
|
||||
const subpath = searchParams.get('subpath');
|
||||
|
||||
// 获取团队、项目和标签筛选参数
|
||||
const teamIds = searchParams.getAll('teamId');
|
||||
@@ -39,6 +40,7 @@ export async function GET(request: NextRequest) {
|
||||
startTime,
|
||||
endTime,
|
||||
linkId,
|
||||
subpath,
|
||||
teamIds,
|
||||
projectIds,
|
||||
tagIds,
|
||||
@@ -63,6 +65,11 @@ export async function GET(request: NextRequest) {
|
||||
conditions.push(`link_id = '${linkId}'`);
|
||||
}
|
||||
|
||||
// 添加子路径筛选
|
||||
if (subpath) {
|
||||
conditions.push(`positionCaseInsensitive(url, '/${subpath}') > 0`);
|
||||
}
|
||||
|
||||
// 添加团队筛选
|
||||
if (teamIds && teamIds.length > 0) {
|
||||
// 如果只有一个团队ID
|
||||
|
||||
@@ -4,6 +4,7 @@ interface PathAnalyticsProps {
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
linkId?: string;
|
||||
onPathClick?: (path: string) => void;
|
||||
}
|
||||
|
||||
interface PathData {
|
||||
@@ -12,7 +13,7 @@ interface PathData {
|
||||
percentage: number;
|
||||
}
|
||||
|
||||
const PathAnalytics: React.FC<PathAnalyticsProps> = ({ startTime, endTime, linkId }) => {
|
||||
const PathAnalytics: React.FC<PathAnalyticsProps> = ({ startTime, endTime, linkId, onPathClick }) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [pathData, setPathData] = useState<PathData[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -83,6 +84,13 @@ const PathAnalytics: React.FC<PathAnalyticsProps> = ({ startTime, endTime, linkI
|
||||
fetchPathData();
|
||||
}, [startTime, endTime, linkId]);
|
||||
|
||||
const handlePathClick = (path: string, e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
if (onPathClick) {
|
||||
onPathClick(path);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <div className="py-8 flex justify-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500" />
|
||||
@@ -118,7 +126,15 @@ const PathAnalytics: React.FC<PathAnalyticsProps> = ({ startTime, endTime, linkI
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{pathData.map((item, index) => (
|
||||
<tr key={index} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{item.path}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
||||
<a
|
||||
href="#"
|
||||
className="hover:text-blue-600 hover:underline cursor-pointer"
|
||||
onClick={(e) => handlePathClick(item.path, e)}
|
||||
>
|
||||
{item.path}
|
||||
</a>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 text-right">{item.count}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-right">
|
||||
<div className="flex items-center justify-end">
|
||||
|
||||
@@ -18,9 +18,10 @@ interface UtmAnalyticsProps {
|
||||
teamIds?: string[];
|
||||
projectIds?: string[];
|
||||
tagIds?: string[];
|
||||
subpath?: string;
|
||||
}
|
||||
|
||||
export default function UtmAnalytics({ startTime, endTime, linkId, teamIds, projectIds, tagIds }: UtmAnalyticsProps) {
|
||||
export default function UtmAnalytics({ startTime, endTime, linkId, teamIds, projectIds, tagIds, subpath }: UtmAnalyticsProps) {
|
||||
const [activeTab, setActiveTab] = useState<string>('source');
|
||||
const [utmData, setUtmData] = useState<UtmData[]>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
@@ -38,6 +39,7 @@ export default function UtmAnalytics({ startTime, endTime, linkId, teamIds, proj
|
||||
if (startTime) params.append('startTime', startTime);
|
||||
if (endTime) params.append('endTime', endTime);
|
||||
if (linkId) params.append('linkId', linkId);
|
||||
if (subpath) params.append('subpath', subpath);
|
||||
params.append('utmType', activeTab);
|
||||
|
||||
// 添加团队ID参数
|
||||
@@ -78,7 +80,7 @@ export default function UtmAnalytics({ startTime, endTime, linkId, teamIds, proj
|
||||
};
|
||||
|
||||
fetchUtmData();
|
||||
}, [activeTab, startTime, endTime, linkId, teamIds, projectIds, tagIds]);
|
||||
}, [activeTab, startTime, endTime, linkId, teamIds, projectIds, tagIds, subpath]);
|
||||
|
||||
// 安全地格式化数字
|
||||
const formatNumber = (value: number | undefined | null): string => {
|
||||
|
||||
Reference in New Issue
Block a user