From d3f0f57f6de48646a135ac1f6c5c9e7724693861 Mon Sep 17 00:00:00 2001 From: CNWei Date: Mon, 2 Nov 2026 23:08:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20=E8=87=AA=E5=8A=A8=E5=9B=9E?= =?UTF-8?q?=E5=A1=AB=E5=B9=B6=E6=94=AF=E6=8C=81=E4=BF=AE=E6=94=B9=E8=99=9A?= =?UTF-8?q?=E6=8B=9F=E7=8E=AF=E5=A2=83=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 虚拟环境目录与解释器移到工作目录正下方,虚拟环境目录优先且更宽 - 探测到虚拟环境时自动填入(工作目录内用相对路径 .venv),仍可手动修改 - 手动改过的值不再被探测结果覆盖;换工作目录只清理上一轮自动填入的值并重新探测 - 防抖期间忽略过期探测结果,避免旧环境被回填 --- frontend/src/App.css | 9 ++ frontend/src/components/TaskEditorModal.tsx | 113 ++++++++++++++++---- 2 files changed, 100 insertions(+), 22 deletions(-) diff --git a/frontend/src/App.css b/frontend/src/App.css index 87f1da4..7e55f13 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -211,6 +211,11 @@ gap: 16px; } +/* 虚拟环境目录通常是一条完整路径,给它更多宽度;解释器名很短 */ +.venv-grid { + grid-template-columns: minmax(0, 1.4fr) minmax(0, 0.9fr); +} + .form-select { width: 100%; } @@ -889,6 +894,10 @@ grid-template-columns: 1fr; } + .venv-grid { + grid-template-columns: 1fr; + } + .cron-run-grid { grid-template-columns: 1fr; } diff --git a/frontend/src/components/TaskEditorModal.tsx b/frontend/src/components/TaskEditorModal.tsx index 67a8d88..900ec86 100644 --- a/frontend/src/components/TaskEditorModal.tsx +++ b/frontend/src/components/TaskEditorModal.tsx @@ -15,7 +15,7 @@ import { Textarea, TextButton, } from '@stash/ui' -import { useEffect, useState, type ReactNode } from 'react' +import { useEffect, useRef, useState, type ReactNode } from 'react' import { Controller, type FieldPath, @@ -87,9 +87,13 @@ export function TaskEditorModal({ const [submitError, setSubmitError] = useState(null) const [cronPreviewCount, setCronPreviewCount] = useState(5) const [pickerOpen, setPickerOpen] = useState(false) + // 记录「自动填入」的虚拟环境目录:用户手动改过就不再覆盖; + // 工作目录变化时只有仍等于该值的字段会被清掉,交给新目录重新探测。 + const autoVenvRef = useRef('') const { register, control, + getValues, handleSubmit, reset, watch, @@ -181,8 +185,49 @@ export function TaskEditorModal({ setCronPreviewCount(5) setSubmitError(null) setPickerOpen(false) + autoVenvRef.current = '' }, [open, reset, task]) + // 换工作目录时清掉上一轮自动填入的值(手动填写的值不动)。 + useEffect(() => { + if (!open) { + return + } + if (autoVenvRef.current && getValues('script.venv').trim() === autoVenvRef.current) { + setValue('script.venv', '', { shouldDirty: false }) + } + autoVenvRef.current = '' + }, [debouncedWorkingDir, getValues, open, setValue]) + + // 探测到虚拟环境就自动填进「虚拟环境目录」,但用户自己改过就保留用户的值。 + useEffect(() => { + if (!open) { + return + } + const probe = venvProbe.data + if (!probe || probe.status !== 'linked' || !probe.venv_path) { + return + } + const current = getValues('script.venv').trim() + // 防抖中的输入可能还没落到查询键上:只有「当前输入」与「本次探测用的输入」 + // 一致时才回填,否则工作目录刚变化时可能把上一轮探测结果又填回去。 + if (debouncedVenv.trim() !== current) { + return + } + if (current && current !== autoVenvRef.current) { + return + } + if (current !== '' && current === autoVenvRef.current) { + return + } + const next = venvFieldValue(debouncedWorkingDir, probe.venv_path) + autoVenvRef.current = next + setValue('script.venv', next, { + shouldDirty: true, + shouldValidate: true, + }) + }, [debouncedVenv, debouncedWorkingDir, getValues, open, setValue, venvProbe.data]) + // 打开后把焦点给第一个可编辑字段:新建时是任务标识,编辑时 ID 已锁定,跳名称。 // 原生 dialog 默认聚焦关闭按钮,不符合编辑表单的节奏。 useEffect(() => { @@ -442,14 +487,20 @@ export function TaskEditorModal({ - - -
+
+ + + - - -
+ +