style(code-runner): 重做 admin/runner 视觉,对齐项目 token 体系
页面与 CodeRunner 组件此前用的是 daisyUI 类(base-100/btn-primary/ input-bordered/bg-neutral 等),但项目根本没装 daisyUI——这些类在 编译产物里 0 匹配,导致页面渲染成裸结构,是「半成品」观感的根因。 全部换成项目真实的 --color-paper-* 语义 token,视觉与 dashboard / posts / system 等后台页面统一: 页面壳(runner.rs): - h1 升至 text-4xl,与其它 admin 页一致;加底部分割线 - 外层对齐 max-w-5xl space-y-8 - 语言切换改为胶囊按钮组(选中态 accent 绿实心) - overrides 输入框套 system.rs 的表单范式,加 focus 态 - 错误/提示用 red-500 dark: red-400,解析成功时显示字段说明 CodeRunner 组件(runner.rs): - 容器改 rounded-2xl + paper-border + paper-entry - 顶栏加状态点(accent)+ 胶囊运行按钮(active 缩放反馈) - 运行中用 SPINNER_SVG 替代 daisyUI loading-spinner - 输出区改 paper-code-block 底 + 语义色文字,随主题切 - 错误区改 red-50 dark: red-900/10 柔光块
This commit is contained in:
parent
c83b834c8b
commit
1dc02561a3
@ -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()}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user