diff --git a/src/components/code_runner/runner.rs b/src/components/code_runner/runner.rs index df967a4..58fb7eb 100644 --- a/src/components/code_runner/runner.rs +++ b/src/components/code_runner/runner.rs @@ -9,6 +9,7 @@ use dioxus::prelude::*; use crate::api::code_runner::execute::{get_exec_result, start_exec}; use crate::api::code_runner::{ExecRequest, ExecStatus}; +use crate::components::ui::SPINNER_SVG; use crate::infra::runner_config::ResourceLimits; use crate::utils::time::sleep_ms; @@ -210,18 +211,24 @@ pub fn CodeRunner( }; rsx! { - div { class: "code-runner-container border border-base-300 rounded-xl overflow-hidden my-4 bg-base-100", - div { class: "flex justify-between items-center bg-base-200 px-3 py-1 text-sm border-b border-base-300", - span { class: "font-mono font-bold text-base-content/80", "{language}" } + div { class: "rounded-2xl overflow-hidden border border-[var(--color-paper-border)] bg-[var(--color-paper-entry)]", + // 顶栏:语言标签 + 运行按钮 + div { class: "flex justify-between items-center px-4 py-2.5 border-b border-[var(--color-paper-border)] bg-[var(--color-paper-theme)]", + div { class: "flex items-center gap-2", + span { class: "w-2 h-2 rounded-full bg-[var(--color-paper-accent)]" } + span { class: "font-mono text-sm font-semibold text-[var(--color-paper-primary)]", "{language}" } + } button { - class: "btn btn-xs btn-primary rounded-lg", + class: "inline-flex items-center gap-1.5 px-4 py-1.5 text-sm font-medium rounded-full text-[var(--color-paper-theme)] bg-[var(--color-paper-accent)] hover:brightness-110 active:scale-[0.98] transition disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer", disabled: running(), onclick: run_code, if running() { - span { class: "loading loading-spinner loading-xs mr-1" } + span { class: "inline-block w-3.5 h-3.5 text-[var(--color-paper-theme)]", + dangerous_inner_html: SPINNER_SVG, + } "{stage()}" } else { - "Run" + "运行" } } } @@ -229,19 +236,24 @@ pub fn CodeRunner( // 服务端渲染时仅展示空容器,避免在 SSR 阶段拉起 JS 编辑器。 div { id: "{container_id}", - class: "code-runner-editor min-h-[100px] font-mono text-sm", + class: "code-runner-editor font-mono text-sm", + style: "min-height: 160px", } + // 输出区 if !output().is_empty() { - div { class: "output-area bg-neutral text-neutral-content p-3 rounded-lg m-2 font-mono text-xs whitespace-pre-wrap break-all", - div { class: "text-gray-400 border-b border-gray-600 pb-1 mb-1 flex justify-between items-center", - span { "Console Output" } - span { class: "text-gray-500", "{exit_info()}" } + div { class: "border-t border-[var(--color-paper-border)]", + div { class: "flex justify-between items-center px-4 py-2 text-xs text-[var(--color-paper-tertiary)] border-b border-[var(--color-paper-border)] bg-[var(--color-paper-code-block)]", + span { class: "font-medium uppercase tracking-wide", "输出" } + span { "{exit_info()}" } + } + pre { class: "px-4 py-3 m-0 text-xs font-mono text-[var(--color-paper-secondary)] bg-[var(--color-paper-code-block)] overflow-x-auto whitespace-pre-wrap break-words", + {output()} } - {output()} } } + // 错误提示 if !error_msg().is_empty() { - div { class: "px-3 pb-2 text-xs text-error", + div { class: "px-4 py-2.5 border-t border-[var(--color-paper-border)] text-xs text-red-500 dark:text-red-400 bg-red-50 dark:bg-red-900/10", {error_msg()} } } diff --git a/src/pages/admin/runner.rs b/src/pages/admin/runner.rs index a7f8e4b..40eb339 100644 --- a/src/pages/admin/runner.rs +++ b/src/pages/admin/runner.rs @@ -51,22 +51,31 @@ pub fn Runner() -> Element { let (overrides, override_error) = (parsed.read().0.clone(), parsed.read().1.clone()); rsx! { - div { class: "flex flex-col gap-6 max-w-4xl mx-auto w-full", - div { class: "flex flex-col gap-2", - h1 { class: "text-3xl font-extrabold tracking-tight", "代码试运行沙箱" } - p { class: "text-base text-base-content/60", - "在此快速试运行代码,验证文章中可运行代码块的预期输出。资源钳制与读者侧一致,速率限制对 admin 放行。" + div { class: "w-full max-w-5xl mx-auto space-y-8", + // 页头:与 dashboard / posts / system 对齐(h1 text-4xl + 底部分割线) + div { class: "flex flex-col md:flex-row md:items-end justify-between gap-6 pb-8 border-b border-[var(--color-paper-border)]/50", + div { + h1 { class: "text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]", "代码试运行沙箱" } + p { class: "text-base text-[var(--color-paper-secondary)] mt-2", + "在此快速试运行代码,验证文章中可运行代码块的预期输出。资源钳制与读者侧一致,速率限制对 admin 放行。" + } } } - div { class: "{ADMIN_CARD_CLASS} flex flex-col gap-3", - div { class: "flex flex-wrap items-center gap-3", - label { class: "text-sm font-semibold text-base-content/70", "语言" } - div { class: "join", + // 配置卡片:语言切换 + 资源覆盖 + div { class: "{ADMIN_CARD_CLASS} p-8 flex flex-col gap-6", + // 语言切换 + div { class: "flex flex-col gap-2", + label { class: "text-sm font-medium text-[var(--color-paper-secondary)]", "语言" } + div { class: "flex gap-2", for l in SUPPORTED_LANGS { button { key: "{l}", - class: (if lang() == *l { "btn btn-sm join-item btn-primary" } else { "btn btn-sm join-item btn-ghost" }).to_string(), + class: (if lang() == *l { + "px-4 py-1.5 text-sm font-medium rounded-full text-[var(--color-paper-theme)] bg-[var(--color-paper-accent)] hover:brightness-110 transition shadow-sm cursor-pointer" + } else { + "px-4 py-1.5 text-sm font-medium rounded-full text-[var(--color-paper-secondary)] bg-[var(--color-paper-theme)] hover:bg-[var(--color-paper-border)] hover:text-[var(--color-paper-primary)] transition cursor-pointer" + }).to_string(), onclick: { let ll = (*l).to_string(); move |_| { @@ -81,23 +90,30 @@ pub fn Runner() -> Element { } } } - div { class: "flex flex-col gap-1", - label { class: "text-sm font-semibold text-base-content/70", + + // 资源覆盖(JSON) + div { class: "flex flex-col gap-2", + label { class: "text-sm font-medium text-[var(--color-paper-secondary)]", "资源覆盖 (JSON, 可选)" } input { - class: "input input-bordered input-sm font-mono w-full", + class: "w-full px-3 py-2 text-sm border border-paper-border rounded-lg bg-[var(--color-paper-theme)] text-[var(--color-paper-primary)] font-mono focus:outline-none focus:border-[var(--color-paper-accent)] transition-colors", r#type: "text", placeholder: "如 {{\"timeout_secs\":10,\"memory_mb\":512}}", value: "{overrides_json()}", oninput: move |e| overrides_json.set(e.value()), } if !override_error.is_empty() { - span { class: "text-xs text-error", "{override_error}" } + p { class: "text-xs text-red-500 dark:text-red-400", "{override_error}" } + } else { + p { class: "text-xs text-[var(--color-paper-tertiary)]", + "覆盖 cpu_cores / memory_mb / timeout_secs / output_bytes / allow_network;最终仍受 CODE_RUNNER_MAX_* 钳制" + } } } } + // 运行器 CodeRunner { source: source(), language: lang(),