From 35745b95fe7c1f87372cb56f78f5126dc834f968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Sat, 14 Feb 2026 12:18:12 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=90=9B=20=E4=BF=AE=E5=A4=8D=E6=8B=96?= =?UTF-8?q?=E6=8B=BD=E7=BB=84=E4=BB=B6=E5=AF=BC=E8=87=B4=E8=A7=A6=E5=8F=91?= =?UTF-8?q?focusin=20/=20focusout=E5=8D=A1=E9=A1=BF=20#1224?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/components/layout/MainLayout.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/components/layout/MainLayout.tsx b/src/pages/components/layout/MainLayout.tsx index 0e8892304..2f82938d0 100644 --- a/src/pages/components/layout/MainLayout.tsx +++ b/src/pages/components/layout/MainLayout.tsx @@ -225,6 +225,8 @@ const MainLayout: React.FC<{ const { getRootProps, getInputProps, isDragActive } = useDropzone({ accept: { "application/javascript": [".js"] }, onDrop, + noClick: true, + noKeyboard: true, }); const languageList: { key: string; title: string }[] = []; @@ -450,7 +452,7 @@ const MainLayout: React.FC<{ style={{ background: "var(--color-fill-2)", }} - {...getRootProps({ onClick: (e) => e.stopPropagation() })} + {...getRootProps({ onBlur: undefined, onFocus: undefined })} >
Date: Sat, 14 Feb 2026 12:26:32 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E4=BD=BF=E7=94=A8noKeyboard=EF=BC=8C?= =?UTF-8?q?=E4=B8=8D=E9=9C=80=E8=A6=81=E8=87=AA=E5=B7=B1=E6=8C=87=E5=AE=9A?= =?UTF-8?q?undefined?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/components/layout/MainLayout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/components/layout/MainLayout.tsx b/src/pages/components/layout/MainLayout.tsx index 2f82938d0..ae4d43f60 100644 --- a/src/pages/components/layout/MainLayout.tsx +++ b/src/pages/components/layout/MainLayout.tsx @@ -452,7 +452,7 @@ const MainLayout: React.FC<{ style={{ background: "var(--color-fill-2)", }} - {...getRootProps({ onBlur: undefined, onFocus: undefined })} + {...getRootProps({})} >
Date: Sat, 14 Feb 2026 14:34:10 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=E6=8A=BD=E7=A6=BB=E9=81=AE=E7=BD=A9?= =?UTF-8?q?=E7=BB=84=E4=BB=B6,=20=E4=BD=BF=E7=94=A8=20useMemo=20=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E8=AF=AD=E8=A8=80=E5=88=97=E8=A1=A8=EF=BC=8C=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E5=87=BD=E6=95=B0=E7=A7=BB=E5=87=BA=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E5=A4=96=EF=BC=8C=E6=8E=A7=E5=88=B6CSS=E8=A1=8C=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/components/layout/MainLayout.tsx | 121 +++++++++++---------- src/pages/components/layout/index.css | 7 +- 2 files changed, 72 insertions(+), 56 deletions(-) diff --git a/src/pages/components/layout/MainLayout.tsx b/src/pages/components/layout/MainLayout.tsx index ae4d43f60..04b1d12f3 100644 --- a/src/pages/components/layout/MainLayout.tsx +++ b/src/pages/components/layout/MainLayout.tsx @@ -23,7 +23,7 @@ import { IconSunFill, } from "@arco-design/web-react/icon"; import type { ReactNode } from "react"; -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { useAppContext } from "@App/pages/store/AppContext"; import { RiFileCodeLine, RiImportLine, RiPlayListAddLine, RiTerminalBoxLine, RiTimerLine } from "react-icons/ri"; @@ -37,6 +37,8 @@ import { prepareScriptByCode } from "@App/pkg/utils/script"; import { saveHandle } from "@App/pkg/utils/filehandle-db"; import { makeBlobURL } from "@App/pkg/utils/utils"; +// --- 工具函数移出组件外,避免每次 Render 重新定义 --- + const formatUrl = async (url: string) => { try { const newUrl = new URL(url.replace(/\/$/, "")); @@ -69,6 +71,21 @@ const formatUrl = async (url: string) => { } }; +// 提供一个简单的字串封装(非加密用) +const simpleDigestMessage = async (message: string) => { + const encoder = new TextEncoder(); + const data = encoder.encode(message); + return crypto.subtle.digest("SHA-1", data as BufferSource).then((hashBuffer) => { + const hashArray = new Uint8Array(hashBuffer); + let hex = ""; + for (let i = 0; i < hashArray.length; i++) { + const byte = hashArray[i]; + hex += `${byte < 16 ? "0" : ""}${byte.toString(16)}`; + } + return hex; + }); +}; + type TImportStat = { success: number; fail: number; @@ -102,6 +119,31 @@ const importByUrls = async (urls: string[]): Promise => return stat; }; +// --- 子组件:提取拖拽遮罩以优化性能 --- +const DropzoneOverlay: React.FC<{ active: boolean; text: string }> = React.memo(({ active, text }) => { + if (!active) return null; + return ( +
+ {text} +
+ ); +}); +DropzoneOverlay.displayName = "DropzoneOverlay"; + const MainLayout: React.FC<{ children: ReactNode; className: string; @@ -146,21 +188,6 @@ const MainLayout: React.FC<{ if (stat) showImportResult(stat); }; - // 提供一个简单的字串封装(非加密用) - function simpleDigestMessage(message: string) { - const encoder = new TextEncoder(); - const data = encoder.encode(message); - return crypto.subtle.digest("SHA-1", data as BufferSource).then((hashBuffer) => { - const hashArray = new Uint8Array(hashBuffer); - let hex = ""; - for (let i = 0; i < hashArray.length; i++) { - const byte = hashArray[i]; - hex += `${byte < 16 ? "0" : ""}${byte.toString(16)}`; - } - return hex; - }); - } - const onDrop = (acceptedFiles: FileWithPath[]) => { // 本地的文件在当前页面处理,打开安装页面,将FileSystemFileHandle传递过去 // 实现本地文件的监听 @@ -178,7 +205,7 @@ const MainLayout: React.FC<{ } else if (aFile instanceof File) { // 清理 import-local files 避免同文件不再触发onChange (document.getElementById("import-local") as HTMLInputElement).value = ""; - const blob = new Blob([aFile], { type: "application/javascript" }); + const blob = new Blob([aFile], { type: "text/javascript" }); const url = makeBlobURL({ blob, persistence: false }) as string; // 生成一个临时的URL const result = await scriptClient.importByUrl(url); if (result.success) { @@ -223,26 +250,28 @@ const MainLayout: React.FC<{ }; const { getRootProps, getInputProps, isDragActive } = useDropzone({ - accept: { "application/javascript": [".js"] }, + accept: { "text/javascript": [".js"] }, onDrop, noClick: true, noKeyboard: true, }); - const languageList: { key: string; title: string }[] = []; - for (const key of Object.keys(i18n.store.data)) { - if (key === "ach-UG") { - continue; - } - languageList.push({ - key, - title: i18n.store.data[key].title as string, - }); - } - languageList.push({ - key: "help", - title: t("help_translate"), - }); + // 当dragzone使用时,在加入.dragzone-active,控制CSS行为 + // 只改CSS,不要改动React元件的任何状态,否则会触发重绘计算 + useEffect(() => { + document.documentElement.classList.toggle("dragzone-active", isDragActive); + }, [isDragActive]); + + // 使用 useMemo 缓存语言列表,避免每次重绘都执行循环,然后生成新的参考 + const languageList = useMemo(() => { + const list = Object.keys(i18n.store.data) + .filter((key) => key !== "ach-UG") + .map((key) => ({ + key, + title: i18n.store.data[key].title as string, + })); + return [...list, { key: "help", title: t("help_translate") }]; + }, [t]); useEffect(() => { // 当没有匹配语言时显示语言按钮 @@ -255,7 +284,7 @@ const MainLayout: React.FC<{ const handleImport = async () => { const urls = importRef.current!.dom.value.split("\n").filter((v) => v); - importByUrlsLocal(urls); // 異步卻不用等候? + importByUrlsLocal(urls); // 异步却不用等候? setImportVisible(false); // 不等待 importByUrlsLocal? }; @@ -334,7 +363,7 @@ const MainLayout: React.FC<{ types: [ { description: "JavaScript", - accept: { "application/javascript": [".js"] }, + accept: { "text/javascript": [".js"] }, }, ], }) @@ -449,30 +478,12 @@ const MainLayout: React.FC<{ -
- {t("drag_script_here_to_upload")} -
+ {/* 性能关键:抽离遮罩组件,只有 active 变化时此小组件重绘 */} + {children}
diff --git a/src/pages/components/layout/index.css b/src/pages/components/layout/index.css index 7f2f9b9e7..fd53b2633 100644 --- a/src/pages/components/layout/index.css +++ b/src/pages/components/layout/index.css @@ -18,4 +18,9 @@ :is(.arco-dropdown-menu-pop-header, .arco-dropdown-menu-item, .arco-dropdown-menu-item a) > svg { margin-right: .5em; -} \ No newline at end of file +} + +/* 避免拖拽时 tooltip 等元件弹出 */ +.dragzone-active .arco-layout-content { + pointer-events: none; +} From a71974943c2f3092fcdbe42dc42f391e203847ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Sat, 14 Feb 2026 18:53:11 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E6=94=BE=E5=88=B0body?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/components/layout/MainLayout.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/components/layout/MainLayout.tsx b/src/pages/components/layout/MainLayout.tsx index 04b1d12f3..6dbbdef41 100644 --- a/src/pages/components/layout/MainLayout.tsx +++ b/src/pages/components/layout/MainLayout.tsx @@ -256,10 +256,10 @@ const MainLayout: React.FC<{ noKeyboard: true, }); - // 当dragzone使用时,在加入.dragzone-active,控制CSS行为 + // 当dragzone使用时,在加入.dragzone-active,控制CSS行为 // 只改CSS,不要改动React元件的任何状态,否则会触发重绘计算 useEffect(() => { - document.documentElement.classList.toggle("dragzone-active", isDragActive); + document.body.classList.toggle("dragzone-active", isDragActive); }, [isDragActive]); // 使用 useMemo 缓存语言列表,避免每次重绘都执行循环,然后生成新的参考