diff --git a/frontend/e2e/dashboard.spec.ts b/frontend/e2e/dashboard.spec.ts index 64e7484..da15bf9 100644 --- a/frontend/e2e/dashboard.spec.ts +++ b/frontend/e2e/dashboard.spec.ts @@ -56,6 +56,27 @@ test('manages a task and reads a real run log', async ({ page, request }) => { path: 'test-results/dashboard-task-editor.png', fullPage: true, }) + const addArgumentButton = newTaskDialog.getByRole('button', { + name: '+ 添加参数', + }) + await addArgumentButton.focus() + await page.keyboard.press('Enter') + await newTaskDialog + .getByPlaceholder('参数名 (如 --mode 或 -v)') + .fill('--mode') + await newTaskDialog + .getByPlaceholder('参数值 (可选,无值可留空)') + .fill('full') + await newTaskDialog + .getByText('调用预览 (Runtime Preview)') + .scrollIntoViewIfNeeded() + await expect( + newTaskDialog.getByText('--mode full', { exact: false }), + ).toBeVisible() + await page.screenshot({ + path: 'test-results/dashboard-task-editor-runtime.png', + fullPage: true, + }) await page.keyboard.press('Escape') await expect(newTaskDialog).toBeHidden() @@ -75,7 +96,7 @@ test('manages a task and reads a real run log', async ({ page, request }) => { name: '编辑任务 [e2e_echo]', }) await expect(editor).toBeVisible() - const nameInput = editor.getByLabel('任务名称') + const nameInput = editor.getByLabel('名称') await nameInput.fill('E2E updated') await editor.getByRole('button', { name: '保存修改' }).click() await expect(page.getByText('E2E updated')).toBeVisible() diff --git a/frontend/src/App.css b/frontend/src/App.css index 2de5b02..97944d8 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -648,6 +648,12 @@ code { max-height: 75vh; } +.task-editor .modal-body { + min-height: 0; + flex: 1; + max-height: none; +} + .form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); @@ -659,6 +665,10 @@ code { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 0.7fr); } +.form-grid-single { + grid-template-columns: 1fr; +} + .cron-header-row { display: flex; align-items: center; @@ -804,7 +814,7 @@ code { font-size: 10px; } -.action-panel { +.dynamic-fields { margin: 22px 0 0; border: 1px solid #eeeff2; border-radius: 12px; @@ -812,15 +822,6 @@ code { background: #fafafa; } -.action-panel legend { - padding: 0 8px; - color: #374151; - font-family: var(--font-sans); - font-size: 13px; - font-weight: 600; - letter-spacing: 0; -} - .collection-header { display: flex; align-items: flex-start; @@ -853,7 +854,7 @@ code { } .argument-row { - grid-template-columns: 1fr auto; + grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr) auto; } .key-value-row { @@ -866,11 +867,46 @@ code { font-size: 12px; } -.form-actions { +.cli-preview { + margin-top: 18px; + border-radius: 8px; + padding: 14px 18px; + background: #18191c; +} + +.cli-preview-header { + display: flex; + justify-content: space-between; + margin-bottom: 8px; + border-bottom: 1px solid #282a30; + padding-bottom: 6px; + color: #71767f; + font-size: 11px; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.cli-preview-body { + font-family: var(--font-mono); + font-size: 12px; + line-height: 1.6; +} + +.cli-line-env { + margin-bottom: 4px; + color: #9da3af; +} + +.cli-line-cmd { + color: #34d399; + font-weight: 500; + overflow-wrap: anywhere; +} + +.modal-footer { display: flex; justify-content: flex-end; gap: 8px; - margin: 24px -28px -24px; border-top: 1px solid var(--border); padding: 16px 28px; background: #fafafb; @@ -1205,8 +1241,7 @@ code { padding: 18px; } - .form-actions { - margin: 24px -18px -18px; + .modal-footer { padding: 16px 18px; } @@ -1215,7 +1250,8 @@ code { grid-template-columns: 1fr; } - .key-value-row { + .key-value-row, + .argument-row { grid-template-columns: 1fr; } diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index cc10ca5..62b301f 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -126,9 +126,16 @@ describe('TaskDashboard', () => { renderApp() await user.click(screen.getByRole('button', { name: '+ 新建任务' })) - await user.clear(screen.getByLabelText('任务标识')) - await user.type(screen.getByLabelText('任务标识'), 'new_task') - await user.type(screen.getByLabelText('任务名称'), 'New task') + await user.click(screen.getByRole('button', { name: '+ 添加参数' })) + expect( + screen.getByPlaceholderText('参数名 (如 --mode 或 -v)'), + ).toBeInTheDocument() + expect( + screen.getByPlaceholderText('参数值 (可选,无值可留空)'), + ).toBeInTheDocument() + await user.clear(screen.getByLabelText(/任务标识/)) + await user.type(screen.getByLabelText(/任务标识/), 'new_task') + await user.type(screen.getByLabelText('名称'), 'New task') await user.type(screen.getByLabelText('工作目录'), 'D:\\Projects\\new') await user.click( diff --git a/frontend/src/components/TaskEditorModal.tsx b/frontend/src/components/TaskEditorModal.tsx index ee2f062..4aeb5d9 100644 --- a/frontend/src/components/TaskEditorModal.tsx +++ b/frontend/src/components/TaskEditorModal.tsx @@ -13,6 +13,7 @@ import { mapServerFieldToFormField, taskFormSchema, taskToFormValues, + type KeyValueFormValue, type TaskFormValues, } from '../lib/taskForm' import { useFocusTrap } from '../lib/useFocusTrap' @@ -53,6 +54,7 @@ export function TaskEditorModal({ const actionType = watch('actionType') const cronValue = watch('cron') const timezoneValue = watch('timezone') + const scriptPreview = watch('script') const debouncedCron = useDebouncedValue(cronValue, 300) const debouncedTimezone = useDebouncedValue(timezoneValue, 300) const cronPreview = useQuery({ @@ -169,7 +171,12 @@ export function TaskEditorModal({ -