From 5335834809ca679a4ea0a0f9afb8f95d6f3b5f47 Mon Sep 17 00:00:00 2001 From: oiov Date: Mon, 4 Aug 2025 11:03:43 +0800 Subject: [PATCH] feat: add export url functions --- app/(protected)/dashboard/urls/export.tsx | 201 ++++++++++++++++++++ app/(protected)/dashboard/urls/url-list.tsx | 2 + locales/en.json | 6 +- locales/zh.json | 6 +- public/sw.js.map | 2 +- 5 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 app/(protected)/dashboard/urls/export.tsx diff --git a/app/(protected)/dashboard/urls/export.tsx b/app/(protected)/dashboard/urls/export.tsx new file mode 100644 index 0000000..753f6cd --- /dev/null +++ b/app/(protected)/dashboard/urls/export.tsx @@ -0,0 +1,201 @@ +import { useState } from "react"; +import { useTranslations } from "next-intl"; + +import { ShortUrlFormData } from "@/lib/dto/short-urls"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Icons } from "@/components/shared/icons"; + +interface ExportConfig { + filename?: string; + fields?: (keyof ShortUrlFormData)[]; + filter?: (item: ShortUrlFormData) => boolean; + sort?: (a: ShortUrlFormData, b: ShortUrlFormData) => number; + format?: "pretty" | "compact"; +} + +export const exportToJsonAdvanced = ( + data: ShortUrlFormData[], + config: ExportConfig = {}, +) => { + const { + filename = "short-urls", + fields, + filter, + sort, + format = "pretty", + } = config; + + try { + let processedData = [...data]; + + // 应用过滤器 + if (filter) { + processedData = processedData.filter(filter); + } + + // 应用排序 + if (sort) { + processedData = processedData.sort(sort); + } + + // 选择特定字段 + if (fields) { + processedData = processedData.map((item) => { + const filteredItem: Partial = {}; + fields.forEach((field) => { + filteredItem[field] = item[field] as any; + }); + return filteredItem as ShortUrlFormData; + }); + } + + // 格式化 JSON + const indent = format === "pretty" ? 2 : 0; + const jsonString = JSON.stringify(processedData, null, indent); + + // 创建并下载文件 + const blob = new Blob([jsonString], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = `${filename}.json`; + + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + + return { + success: true, + exportedCount: processedData.length, + totalCount: data.length, + }; + } catch (error) { + console.error("Export failed:", error); + return { + success: false, + error: error instanceof Error ? error.message : "Unknown error", + }; + } +}; + +export const useJsonExport = () => { + const [isExporting, setIsExporting] = useState(false); + const [exportStatus, setExportStatus] = useState(""); + + const exportData = async ( + data: ShortUrlFormData[], + config?: ExportConfig, + ) => { + setIsExporting(true); + setExportStatus("正在导出..."); + + try { + const result = exportToJsonAdvanced(data, config); + + if (result.success) { + setExportStatus(`成功导出 ${result.exportedCount} 条记录`); + } else { + setExportStatus(`导出失败: ${result.error}`); + } + + // 3秒后清除状态 + setTimeout(() => { + setExportStatus(""); + }, 3000); + } finally { + setIsExporting(false); + } + }; + + return { + exportData, + isExporting, + exportStatus, + }; +}; + +export const UrlExporter: React.FC<{ + data: ShortUrlFormData[]; +}> = ({ data }) => { + const { exportData } = useJsonExport(); + + const t = useTranslations("List"); + + const handleExportAll = () => { + exportData(data, { + filename: "all-urls", + format: "pretty", + }); + }; + + const handleExportActive = () => { + exportData(data, { + filename: "active-urls", + filter: (item) => item.active === 1, + sort: (a, b) => + new Date(b.createdAt!).getTime() - new Date(a.createdAt!).getTime(), + format: "pretty", + }); + }; + + const handleExportBasicInfo = () => { + exportData(data, { + filename: "urls", + fields: ["prefix", "url", "target"], + format: "compact", + }); + }; + + return ( + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/app/(protected)/dashboard/urls/url-list.tsx b/app/(protected)/dashboard/urls/url-list.tsx index e087535..d16de74 100644 --- a/app/(protected)/dashboard/urls/url-list.tsx +++ b/app/(protected)/dashboard/urls/url-list.tsx @@ -60,6 +60,7 @@ import { PaginationWrapper } from "@/components/shared/pagination"; import QRCodeEditor from "@/components/shared/qr"; import { TimeAgoIntl } from "@/components/shared/time-ago"; +import { UrlExporter } from "./export"; import Globe from "./globe"; import LiveLog from "./live-logs"; import UserUrlMetaInfo from "./meta"; @@ -696,6 +697,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) { {/*

Total: {data?.total || 0}

*/}
+