From c290f5a6d90e06a7132c06d08ecd2c9f48ec2545 Mon Sep 17 00:00:00 2001 From: jiangrui Date: Tue, 25 Feb 2025 12:53:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=A2=9E=E5=8A=A0=E8=BD=AC=E5=AD=98?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E5=88=97=E8=A1=A8=E5=B1=95=E7=A4=BA=E4=B8=8E?= =?UTF-8?q?=E9=80=89=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/auto-imports.d.ts | 2 +- frontend/components.d.ts | 2 + frontend/src/components/Home/FolderSelect.vue | 7 ++ .../src/components/Home/ResourceSelect.vue | 82 +++++++++++++++++ frontend/src/stores/resource.ts | 87 +++++++++++++------ frontend/src/types/index.ts | 3 +- frontend/src/utils/index.ts | 9 ++ frontend/src/utils/request.ts | 2 +- frontend/src/views/ResourceList.vue | 75 +++++++++++++--- package.json | 2 +- 10 files changed, 226 insertions(+), 45 deletions(-) create mode 100644 frontend/src/components/Home/ResourceSelect.vue create mode 100644 frontend/src/utils/index.ts diff --git a/frontend/auto-imports.d.ts b/frontend/auto-imports.d.ts index c1cd8b3..78813d8 100644 --- a/frontend/auto-imports.d.ts +++ b/frontend/auto-imports.d.ts @@ -5,5 +5,5 @@ // Generated by unplugin-auto-import export {} declare global { - const ElMessage: (typeof import("element-plus/es"))["ElMessage"] + const ElMessage: typeof import('element-plus/es')['ElMessage'] } diff --git a/frontend/components.d.ts b/frontend/components.d.ts index e9b1961..3adb78d 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -8,6 +8,7 @@ export {} declare module 'vue' { export interface GlobalComponents { AsideMenu: typeof import('./src/components/AsideMenu.vue')['default'] + copy: typeof import('./src/components/Home/FolderSelect copy.vue')['default'] DoubanMovie: typeof import('./src/components/Home/DoubanMovie.vue')['default'] ElAside: typeof import('element-plus/es')['ElAside'] ElBacktop: typeof import('element-plus/es')['ElBacktop'] @@ -39,6 +40,7 @@ declare module 'vue' { ElTree: typeof import('element-plus/es')['ElTree'] FolderSelect: typeof import('./src/components/Home/FolderSelect.vue')['default'] ResourceCard: typeof import('./src/components/Home/ResourceCard.vue')['default'] + ResourceSelect: typeof import('./src/components/Home/ResourceSelect.vue')['default'] ResourceTable: typeof import('./src/components/Home/ResourceTable.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] diff --git a/frontend/src/components/Home/FolderSelect.vue b/frontend/src/components/Home/FolderSelect.vue index 6e205f9..c204274 100644 --- a/frontend/src/components/Home/FolderSelect.vue +++ b/frontend/src/components/Home/FolderSelect.vue @@ -31,6 +31,9 @@ import { quarkApi } from "@/api/quark"; import type { TreeInstance } from "element-plus"; import type { Folder } from "@/types"; import { type RequestResult } from "@/types/response"; +import { useResourceStore } from "@/stores/resource"; + +const resourceStore = useResourceStore(); import { ElMessage } from "element-plus"; const props = defineProps({ @@ -59,10 +62,12 @@ const cloudTypeApiMap = { quark: quarkApi, }; +// eslint-disable-next-line @typescript-eslint/no-explicit-any const loadNode = async (node: any, resolve: (list: Folder[]) => void) => { const api = cloudTypeApiMap[props.cloudType as keyof typeof cloudTypeApiMap]; try { let res: RequestResult = { code: 0, data: [] as Folder[], message: "" }; + resourceStore.setLoadTree(true); if (node.level === 0) { if (api.getFolderList) { // 使用类型保护检查方法是否存在 @@ -79,10 +84,12 @@ const loadNode = async (node: any, resolve: (list: Folder[]) => void) => { } else { throw new Error(res.message); } + resourceStore.setLoadTree(false); } catch (error) { ElMessage.error(error instanceof Error ? `${error.message}` : "获取目录失败"); // 关闭模态框 emit("close"); + resourceStore.setLoadTree(false); resolve([]); } }; diff --git a/frontend/src/components/Home/ResourceSelect.vue b/frontend/src/components/Home/ResourceSelect.vue new file mode 100644 index 0000000..e85e41b --- /dev/null +++ b/frontend/src/components/Home/ResourceSelect.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/frontend/src/stores/resource.ts b/frontend/src/stores/resource.ts index 3145142..735516a 100644 --- a/frontend/src/stores/resource.ts +++ b/frontend/src/stores/resource.ts @@ -7,6 +7,7 @@ import type { ShareInfoResponse, Save115FileParams, SaveQuarkFileParams, + ShareInfo, ResourceItem, } from "@/types"; import { ElMessage } from "element-plus"; @@ -101,13 +102,18 @@ export const useResourceStore = defineStore("resource", { }, resources: lastResource.list, lastUpdateTime: lastResource.lastUpdateTime || "", - selectedResources: [] as Resource[], + shareInfo: {} as ShareInfoResponse, + resourceSelect: [] as ShareInfo[], loading: false, lastKeyWord: "", backupPlan: false, + loadTree: false, }), actions: { + setLoadTree(loadTree: boolean) { + this.loadTree = loadTree; + }, // 搜索资源 async searchResources(keyword?: string, isLoadMore = false, channelId?: string): Promise { this.loading = true; @@ -153,6 +159,11 @@ export const useResourceStore = defineStore("resource", { } }, + // 设置选择资源 + async setSelectedResource(resourceSelect: ShareInfo[]) { + this.resourceSelect = resourceSelect; + }, + // 转存资源 async saveResource(resource: ResourceItem, folderId: string): Promise { const savePromises: Promise[] = []; @@ -178,25 +189,12 @@ export const useResourceStore = defineStore("resource", { const match = link.match(drive.regex); if (!match) throw new Error("链接解析失败"); - const parsedCode = drive.parseShareCode(match); + const shareInfo = { + ...this.shareInfo, + list: this.resourceSelect, + }; if (this.is115Drive(drive)) { - let shareInfo = await drive.api.getShareInfo( - parsedCode as { shareCode: string; receiveCode: string } - ); - if (shareInfo) { - if (Array.isArray(shareInfo)) { - shareInfo = { - list: shareInfo, - ...parsedCode, - }; - } else { - shareInfo = { - ...shareInfo, - ...parsedCode, - }; - } - } const params = drive.getSaveParams(shareInfo, folderId); const result = await drive.api.saveFile(params); @@ -206,16 +204,6 @@ export const useResourceStore = defineStore("resource", { ElMessage.error(result.message); } } else { - let shareInfo = this.is115Drive(drive) - ? await drive.api.getShareInfo(parsedCode as { shareCode: string; receiveCode: string }) - : await drive.api.getShareInfo(parsedCode as { pwdId: string }); - if (shareInfo) { - if (Array.isArray(shareInfo)) { - shareInfo = { - list: shareInfo, - }; - } - } const params = drive.getSaveParams(shareInfo, folderId); const result = await drive.api.saveFile(params); @@ -283,6 +271,49 @@ export const useResourceStore = defineStore("resource", { } }, + // 获取资源列表并选择 + async getResourceListAndSelect(resource: ResourceItem): Promise { + const { cloudType } = resource; + const drive = CLOUD_DRIVES.find((x) => x.type === cloudType); + if (!drive) { + return; + } + const link = resource.cloudLinks.find((link) => drive.regex.test(link)); + if (!link) return; + + const match = link.match(drive.regex); + if (!match) throw new Error("链接解析失败"); + + const parsedCode = drive.parseShareCode(match); + let shareInfo = {} as ShareInfoResponse; + this.setLoadTree(true); + if (this.is115Drive(drive)) { + shareInfo = await drive.api.getShareInfo( + parsedCode as { shareCode: string; receiveCode: string } + ); + } else { + shareInfo = this.is115Drive(drive) + ? await drive.api.getShareInfo(parsedCode as { shareCode: string; receiveCode: string }) + : await drive.api.getShareInfo(parsedCode as { pwdId: string }); + } + if (shareInfo) { + if (Array.isArray(shareInfo)) { + shareInfo = { + list: shareInfo, + ...parsedCode, + }; + } else { + shareInfo = { + ...shareInfo, + ...parsedCode, + }; + } + } + this.shareInfo = shareInfo; + this.setSelectedResource(this.shareInfo.list); + this.setLoadTree(false); + }, + // 统一错误处理 handleError(message: string, error: unknown): void { console.error(message, error); diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 65b3012..e56bdd4 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -26,7 +26,7 @@ export interface Resource { export interface ShareInfo { fileId: string; fileName: string; - fileSize: number; + fileSize?: number; fileIdToken?: string; } @@ -36,6 +36,7 @@ export interface ShareInfoResponse { stoken?: string; shareCode?: string; receiveCode?: string; + fileSize?: number; } export interface Folder { diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts new file mode 100644 index 0000000..09cb076 --- /dev/null +++ b/frontend/src/utils/index.ts @@ -0,0 +1,9 @@ +export const formattedFileSize = (size: number): string => { + if (size < 1024 * 1024) { + return `${(size / 1024).toFixed(2)}KB`; + } + if (size < 1024 * 1024 * 1024) { + return `${(size / 1024 / 1024).toFixed(2)}MB`; + } + return `${(size / 1024 / 1024 / 1024).toFixed(2)}GB`; +}; diff --git a/frontend/src/utils/request.ts b/frontend/src/utils/request.ts index 313a9ad..cbd4307 100644 --- a/frontend/src/utils/request.ts +++ b/frontend/src/utils/request.ts @@ -4,7 +4,7 @@ import { RequestResult } from "../types/response"; const axiosInstance = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL as string, - timeout: 9000, + timeout: 16000, withCredentials: true, headers: { "Content-Type": "application/json", diff --git a/frontend/src/views/ResourceList.vue b/frontend/src/views/ResourceList.vue index f380370..059692d 100644 --- a/frontend/src/views/ResourceList.vue +++ b/frontend/src/views/ResourceList.vue @@ -32,7 +32,11 @@ @save="handleSave" > - + - +
+ + +
+
@@ -66,25 +85,43 @@ import { ref } from "vue"; import { useResourceStore } from "@/stores/resource"; import { useUserSettingStore } from "@/stores/userSetting"; import FolderSelect from "@/components/Home/FolderSelect.vue"; +import ResourceSelect from "@/components/Home/ResourceSelect.vue"; import ResourceTable from "@/components/Home/ResourceTable.vue"; +import { formattedFileSize } from "@/utils/index"; import type { ResourceItem, TagColor } from "@/types"; + import ResourceCard from "@/components/Home/ResourceCard.vue"; import { useRouter } from "vue-router"; +import { ElMessage } from "element-plus"; const router = useRouter(); const resourceStore = useResourceStore(); const userStore = useUserSettingStore(); -const folderDialogVisible = ref(false); +const saveDialogVisible = ref(false); const currentResource = ref(null); const currentFolderId = ref(null); +const saveDialogStep = ref<1 | 2>(1); const refreshResources = async () => { resourceStore.searchResources("", false); }; +const saveDialogMap = { + 1: { + title: "选择资源", + buttonText: "下一步", + }, + 2: { + title: "选择保存目录", + buttonText: "保存", + }, +}; + const handleSave = (resource: ResourceItem) => { currentResource.value = resource; - folderDialogVisible.value = true; + saveDialogVisible.value = true; + saveDialogStep.value = 1; + resourceStore.getResourceListAndSelect(currentResource.value); }; const handleFolderSelect = async (folderId: string) => { @@ -92,9 +129,21 @@ const handleFolderSelect = async (folderId: string) => { currentFolderId.value = folderId; }; +const handleConfirmClick = async () => { + if (saveDialogStep.value === 1) { + if (resourceStore.resourceSelect.length === 0) { + ElMessage.warning("请选择要保存的资源"); + return; + } + saveDialogStep.value = 2; + } else { + handleSaveBtnClick(); + } +}; + const handleSaveBtnClick = async () => { if (!currentResource.value || !currentFolderId.value) return; - folderDialogVisible.value = false; + saveDialogVisible.value = false; await resourceStore.saveResource(currentResource.value, currentFolderId.value); }; const setDisplayStyle = (style: string) => { diff --git a/package.json b/package.json index 79fb7bd..ac15c83 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloud-saver", - "version": "0.1.0", + "version": "0.1.1", "private": true, "workspaces": [ "frontend",