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:
xfy 2026-07-05 14:49:24 +08:00
parent c83b834c8b
commit 1dc02561a3
2 changed files with 55 additions and 27 deletions

View File

@ -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()}
}
}

View File

@ -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(),