Agent task: 开发: Phase A+E+F — CSS 全量改造 (mnote-web)
This commit is contained in:
@@ -129,6 +129,7 @@ pub fn PageLayout(
|
||||
class="mnote-workspace-source-trigger"
|
||||
data-testid="mnote-workspace-source-trigger"
|
||||
data-mnote-action="open-workspace-source-menu"
|
||||
data-state="closed"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded="false"
|
||||
title="切换我的空间或本地文件夹"
|
||||
@@ -150,6 +151,7 @@ pub fn PageLayout(
|
||||
aria-label="更多"
|
||||
data-testid="mnote-account-menu-trigger"
|
||||
data-mnote-action="open-account-menu"
|
||||
data-state="closed"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded="false"
|
||||
>
|
||||
@@ -178,7 +180,7 @@ pub fn PageLayout(
|
||||
<div class="mnote-main">
|
||||
<header class="wolai-topbar" data-testid="wolai-topbar">
|
||||
<div class="wolai-topbar-left">
|
||||
<button type="button" class="wolai-icon-button wolai-menu-button" aria-label="切换侧栏" aria-pressed="false" data-mnote-action="toggle-sidebar" data-testid="wolai-sidebar-toggle"><span class="material-symbols-outlined" data-icon="menu" aria-hidden="true"></span></button>
|
||||
<button type="button" class="wolai-icon-button wolai-menu-button" aria-label="切换侧栏" data-state="closed" aria-haspopup="true" aria-expanded="false" aria-pressed="false" data-mnote-action="toggle-sidebar" data-testid="wolai-sidebar-toggle"><span class="material-symbols-outlined" data-icon="menu" aria-hidden="true"></span></button>
|
||||
<nav class="wolai-breadcrumb" aria-label="页面路径">
|
||||
<span class="wolai-breadcrumb-root">{ws_name.clone()}</span>
|
||||
<span class="wolai-breadcrumb-separator" aria-hidden="true">"›"</span>
|
||||
@@ -187,21 +189,21 @@ pub fn PageLayout(
|
||||
</div>
|
||||
<div class="wolai-topbar-actions" aria-label="页面操作">
|
||||
<span class="wolai-public-pill" data-testid="wolai-public-state" hidden></span>
|
||||
<button type="button" class="wolai-icon-button" title="星标置顶" aria-label="星标置顶" data-mnote-action="toggle-sidebar-shortcut" data-mnote-shortcut-kind="page"><span class="material-symbols-outlined" data-icon="star" aria-hidden="true"></span></button>
|
||||
<button type="button" class="wolai-icon-button" title="星标置顶" aria-label="星标置顶" data-state="closed" aria-haspopup="true" aria-expanded="false" data-mnote-action="toggle-sidebar-shortcut" data-mnote-shortcut-kind="page"><span class="material-symbols-outlined" data-icon="star" aria-hidden="true"></span></button>
|
||||
<button type="button" class="wolai-icon-button" title="演示" aria-label="演示"><span class="material-symbols-outlined" data-icon="slideshow" aria-hidden="true"></span></button>
|
||||
<button type="button" class="wolai-icon-button" title="资料库问答" aria-label="资料库问答" data-testid="mnote-knowledge-rag-settings-toggle" data-mnote-action="open-knowledge-rag-settings"><span class="material-symbols-outlined" data-icon="travel_explore" aria-hidden="true"></span></button>
|
||||
<button type="button" class="wolai-icon-button" title="评论" aria-label="评论"><span class="material-symbols-outlined" data-icon="comment" aria-hidden="true"></span></button>
|
||||
<button type="button" class="wolai-icon-button" title="分享" aria-label="分享"><span class="material-symbols-outlined" data-icon="share" aria-hidden="true"></span></button>
|
||||
<button type="button" class="wolai-icon-button" title="成员" aria-label="成员"><span class="material-symbols-outlined" data-icon="person_add" aria-hidden="true"></span></button>
|
||||
<button type="button" class="wolai-icon-button" title="页面历史" aria-label="历史" data-mnote-action="open-page-history"><span class="material-symbols-outlined" data-icon="history" aria-hidden="true"></span></button>
|
||||
<button type="button" class="wolai-icon-button" title="页面选项和全局选项" aria-label="更多" data-testid="wolai-page-settings-trigger" data-mnote-action="open-page-settings"><span class="material-symbols-outlined" data-icon="more_horiz" aria-hidden="true"></span></button>
|
||||
<button type="button" class="wolai-icon-button" title="页面选项和全局选项" aria-label="更多" data-state="closed" aria-haspopup="menu" aria-expanded="false" data-testid="wolai-page-settings-trigger" data-mnote-action="open-page-settings"><span class="material-symbols-outlined" data-icon="more_horiz" aria-hidden="true"></span></button>
|
||||
</div>
|
||||
</header>
|
||||
<article class="mnote-content">
|
||||
{children()}
|
||||
</article>
|
||||
<div class="wolai-floating-actions" aria-label="浮动操作">
|
||||
<button type="button" data-testid="wolai-floating-ai" class="wolai-floating-button wolai-floating-button--ai" aria-label="AI 助手" title="点击 进入空间智能问答" data-mnote-action="open-page-ai"><span class="material-symbols-outlined material-symbols-filled" data-icon="auto_awesome" aria-hidden="true"></span></button>
|
||||
<button type="button" data-testid="wolai-floating-ai" class="wolai-floating-button wolai-floating-button--ai" aria-label="AI 助手" data-state="closed" aria-haspopup="dialog" aria-expanded="false" title="点击 进入空间智能问答" data-mnote-action="open-page-ai"><span class="material-symbols-outlined material-symbols-filled" data-icon="auto_awesome" aria-hidden="true"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,3 +7,4 @@ pub mod home;
|
||||
pub mod layout;
|
||||
pub mod mindmap;
|
||||
pub mod search;
|
||||
pub mod ui_debug;
|
||||
|
||||
@@ -0,0 +1,328 @@
|
||||
//! MNOTE UI Debug — 组件矩阵页面 (SSR)
|
||||
//!
|
||||
//! 调试用组件展示页面,通过 env var `MNOTE_WEB_ENABLE_DEBUG_SHELL_ROUTES=1` 启用。
|
||||
//! 包含:Button 3×3×4 矩阵、Dialog/Modal、Popover/DropdownMenu、Toast、Input/Select/Textarea。
|
||||
//! 仅在生产环境关闭的调试路由下可访问。
|
||||
|
||||
use crate::ssr::pages::layout::PageLayout;
|
||||
use leptos::prelude::*;
|
||||
|
||||
/// UI Debug 组件矩阵页面
|
||||
///
|
||||
/// 展示设计系统组件矩阵,包含主题切换。
|
||||
/// 页面使用 `.ui-debug-*` 命名空间的 CSS 样式。
|
||||
#[component]
|
||||
pub fn UiDebugComponentsPage(
|
||||
#[prop(optional)] sidebar_tree_html: Option<String>,
|
||||
#[prop(optional)] workspace_name: Option<String>,
|
||||
) -> impl IntoView {
|
||||
let sidebar_tree_html = sidebar_tree_html.unwrap_or_default();
|
||||
let workspace_name = workspace_name
|
||||
.map(|v| v.trim().to_string())
|
||||
.filter(|v| !v.is_empty())
|
||||
.unwrap_or_else(|| "开发用户 的空间".to_string());
|
||||
|
||||
// Toast JS: 简单 toast 通知系统
|
||||
let toast_js = r##"
|
||||
(function(){
|
||||
if (window.__mnoteUiDebugToastInited) return;
|
||||
window.__mnoteUiDebugToastInited = true;
|
||||
function showToast(message, kind) {
|
||||
kind = kind || 'info';
|
||||
var container = document.getElementById('ui-debug-toast-container');
|
||||
if (!container) return;
|
||||
var toast = document.createElement('div');
|
||||
toast.className = 'ui-debug-toast ui-debug-toast--' + kind;
|
||||
toast.setAttribute('role', 'status');
|
||||
toast.setAttribute('aria-live', 'polite');
|
||||
toast.textContent = message;
|
||||
container.appendChild(toast);
|
||||
requestAnimationFrame(function() { toast.classList.add('ui-debug-toast--visible'); });
|
||||
setTimeout(function() {
|
||||
toast.classList.remove('ui-debug-toast--visible');
|
||||
setTimeout(function() { if (toast.parentNode) toast.parentNode.removeChild(toast); }, 300);
|
||||
}, 2800);
|
||||
}
|
||||
window.__mnoteUiDebugToast = showToast;
|
||||
document.addEventListener('click', function(e) {
|
||||
var trigger = e.target.closest('[data-ui-debug-toast]');
|
||||
if (!trigger) return;
|
||||
var message = trigger.getAttribute('data-ui-debug-toast') || 'Toast message';
|
||||
var kind = trigger.getAttribute('data-ui-debug-toast-kind') || 'info';
|
||||
showToast(message, kind);
|
||||
});
|
||||
})();
|
||||
"##;
|
||||
|
||||
view! {
|
||||
<PageLayout current_nav="home" sidebar_tree_html={sidebar_tree_html} workspace_name={workspace_name.clone()}>
|
||||
<div class="ui-debug-root" data-ui-debug-page="components">
|
||||
// Toast container
|
||||
<div id="ui-debug-toast-container" class="ui-debug-toast-container" aria-label="通知区域"></div>
|
||||
<script inner_html={toast_js.to_string()}></script>
|
||||
|
||||
// Page header with theme toggle
|
||||
<header class="ui-debug-header">
|
||||
<h1 class="ui-debug-title">"MNote UI Debug — 组件矩阵"</h1>
|
||||
<div class="ui-debug-theme-controls">
|
||||
<button
|
||||
type="button"
|
||||
class="ui-debug-theme-btn"
|
||||
data-ui-debug-theme="light"
|
||||
title="浅色主题"
|
||||
>"☀️ 浅色"</button>
|
||||
<button
|
||||
type="button"
|
||||
class="ui-debug-theme-btn"
|
||||
data-ui-debug-theme="dark"
|
||||
title="深色主题"
|
||||
>"🌙 深色"</button>
|
||||
</div>
|
||||
// Theme JS
|
||||
<script inner_html={r##"
|
||||
(function(){
|
||||
if (window.__mnoteUiDebugThemeInited) return;
|
||||
window.__mnoteUiDebugThemeInited = true;
|
||||
var root = document.querySelector('.ui-debug-root');
|
||||
if (!root) return;
|
||||
function setTheme(theme) {
|
||||
root.setAttribute('data-ui-debug-theme', theme);
|
||||
root.querySelectorAll('[data-ui-debug-theme]').forEach(function(btn) {
|
||||
btn.classList.toggle('ui-debug-theme-btn--active', btn.getAttribute('data-ui-debug-theme') === theme);
|
||||
});
|
||||
}
|
||||
root.addEventListener('click', function(e) {
|
||||
var btn = e.target.closest('[data-ui-debug-theme]');
|
||||
if (!btn) return;
|
||||
setTheme(btn.getAttribute('data-ui-debug-theme'));
|
||||
});
|
||||
setTheme('light');
|
||||
})();
|
||||
"##.to_string()}></script>
|
||||
</header>
|
||||
|
||||
// ─── Button 3×3×4 Matrix ───
|
||||
<section class="ui-debug-section">
|
||||
<h2 class="ui-debug-section-title">"Buttons — 3 variant × 3 size × 4 state"</h2>
|
||||
<div class="ui-debug-matrix">
|
||||
{
|
||||
["primary", "secondary", "outline"]
|
||||
.iter()
|
||||
.flat_map(|variant| {
|
||||
["sm", "md", "lg"]
|
||||
.iter()
|
||||
.flat_map(move |size| {
|
||||
["default", "hover", "active", "disabled"]
|
||||
.iter()
|
||||
.map(move |state| {
|
||||
let is_disabled = *state == "disabled";
|
||||
let class = format!(
|
||||
"ui-debug-btn ui-debug-btn--{} ui-debug-btn--{}",
|
||||
variant, size
|
||||
);
|
||||
let label = format!("{}-{}-{}", variant, size, state);
|
||||
view! {
|
||||
<button
|
||||
type="button"
|
||||
class={class.clone()}
|
||||
disabled={is_disabled}
|
||||
data-ui-debug-state={*state}
|
||||
>{label.clone()}</button>
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
// ─── Dialog / Modal ───
|
||||
<section class="ui-debug-section">
|
||||
<h2 class="ui-debug-section-title">"Dialog / Modal"</h2>
|
||||
<div class="ui-debug-row">
|
||||
<button
|
||||
type="button"
|
||||
class="ui-debug-btn ui-debug-btn--primary ui-debug-btn--md"
|
||||
data-ui-debug-dialog="confirm"
|
||||
>"打开确认对话框"</button>
|
||||
<button
|
||||
type="button"
|
||||
class="ui-debug-btn ui-debug-btn--secondary ui-debug-btn--md"
|
||||
data-ui-debug-dialog="info"
|
||||
>"打开信息对话框"</button>
|
||||
<button
|
||||
type="button"
|
||||
class="ui-debug-btn ui-debug-btn--outline ui-debug-btn--md"
|
||||
data-ui-debug-dialog="danger"
|
||||
>"打开危险确认"</button>
|
||||
</div>
|
||||
// Dialog modal overlay
|
||||
<div id="ui-debug-dialog-overlay" class="ui-debug-dialog-overlay" hidden data-ui-debug-dialog-overlay="true" role="dialog" aria-modal="true" aria-label="对话框">
|
||||
<div class="ui-debug-dialog-panel">
|
||||
<header class="ui-debug-dialog-header">
|
||||
<h3 id="ui-debug-dialog-title" class="ui-debug-dialog-title"></h3>
|
||||
<button type="button" class="ui-debug-dialog-close" data-ui-debug-dialog-close="true" aria-label="关闭">"✕"</button>
|
||||
</header>
|
||||
<p id="ui-debug-dialog-body" class="ui-debug-dialog-body"></p>
|
||||
<footer class="ui-debug-dialog-footer">
|
||||
<button type="button" class="ui-debug-btn ui-debug-btn--outline ui-debug-btn--sm" data-ui-debug-dialog-close="true">"取消"</button>
|
||||
<button type="button" class="ui-debug-btn ui-debug-btn--primary ui-debug-btn--sm" id="ui-debug-dialog-confirm">"确认"</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
// Dialog JS
|
||||
<script inner_html={r##"
|
||||
(function(){
|
||||
if (window.__mnoteUiDebugDialogInited) return;
|
||||
window.__mnoteUiDebugDialogInited = true;
|
||||
var root = document.querySelector('.ui-debug-root');
|
||||
if (!root) return;
|
||||
var overlay = document.getElementById('ui-debug-dialog-overlay');
|
||||
var titleEl = document.getElementById('ui-debug-dialog-title');
|
||||
var bodyEl = document.getElementById('ui-debug-dialog-body');
|
||||
var dialogs = {
|
||||
confirm: { title: '确认操作', body: '确定要执行此操作吗?此操作不可撤销。' },
|
||||
info: { title: '提示信息', body: '这是一条重要的提示信息,请仔细阅读。' },
|
||||
danger: { title: '危险操作', body: '此操作将永久删除数据,确定要继续吗?' }
|
||||
};
|
||||
function showDialog(kind) {
|
||||
var d = dialogs[kind] || dialogs.info;
|
||||
titleEl.textContent = d.title;
|
||||
bodyEl.textContent = d.body;
|
||||
overlay.removeAttribute('hidden');
|
||||
}
|
||||
function hideDialog() { overlay.setAttribute('hidden', ''); }
|
||||
root.addEventListener('click', function(e) {
|
||||
var trigger = e.target.closest('[data-ui-debug-dialog]');
|
||||
if (trigger) { showDialog(trigger.getAttribute('data-ui-debug-dialog')); return; }
|
||||
if (e.target.closest('[data-ui-debug-dialog-close]')) { hideDialog(); return; }
|
||||
if (e.target === overlay) hideDialog();
|
||||
});
|
||||
document.getElementById('ui-debug-dialog-confirm').addEventListener('click', function() {
|
||||
if (window.__mnoteUiDebugToast) window.__mnoteUiDebugToast('操作已确认', 'success');
|
||||
hideDialog();
|
||||
});
|
||||
})();
|
||||
"##.to_string()}></script>
|
||||
</section>
|
||||
|
||||
// ─── Popover / DropdownMenu ───
|
||||
<section class="ui-debug-section">
|
||||
<h2 class="ui-debug-section-title">"Popover / DropdownMenu"</h2>
|
||||
<div class="ui-debug-row">
|
||||
<div class="ui-debug-popover-host">
|
||||
<button
|
||||
type="button"
|
||||
class="ui-debug-btn ui-debug-btn--primary ui-debug-btn--md"
|
||||
data-ui-debug-popover="actions"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded="false"
|
||||
>"操作菜单 ▾"</button>
|
||||
<div class="ui-debug-popover" hidden data-ui-debug-popover-panel="actions" role="menu">
|
||||
<button type="button" class="ui-debug-popover-item" role="menuitem" data-ui-debug-toast="菜单项 1" data-ui-debug-toast-kind="info">"菜单项 1"</button>
|
||||
<button type="button" class="ui-debug-popover-item" role="menuitem" data-ui-debug-toast="菜单项 2" data-ui-debug-toast-kind="success">"菜单项 2"</button>
|
||||
<hr class="ui-debug-popover-separator" />
|
||||
<button type="button" class="ui-debug-popover-item ui-debug-popover-item--danger" role="menuitem" data-ui-debug-toast="删除" data-ui-debug-toast-kind="error">"删除"</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui-debug-popover-host">
|
||||
<button
|
||||
type="button"
|
||||
class="ui-debug-btn ui-debug-btn--secondary ui-debug-btn--md"
|
||||
data-ui-debug-popover="settings"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded="false"
|
||||
>"设置菜单 ▾"</button>
|
||||
<div class="ui-debug-popover" hidden data-ui-debug-popover-panel="settings" role="menu">
|
||||
<label class="ui-debug-popover-item ui-debug-popover-item--check" role="menuitemcheckbox">
|
||||
<input type="checkbox" checked /> "选项 A"
|
||||
</label>
|
||||
<label class="ui-debug-popover-item ui-debug-popover-item--check" role="menuitemcheckbox">
|
||||
<input type="checkbox" /> "选项 B"
|
||||
</label>
|
||||
<label class="ui-debug-popover-item ui-debug-popover-item--check" role="menuitemcheckbox">
|
||||
<input type="checkbox" checked /> "选项 C"
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
// Popover JS
|
||||
<script inner_html={r##"
|
||||
(function(){
|
||||
if (window.__mnoteUiDebugPopoverInited) return;
|
||||
window.__mnoteUiDebugPopoverInited = true;
|
||||
var root = document.querySelector('.ui-debug-root');
|
||||
if (!root) return;
|
||||
var activePanel = null;
|
||||
function closeActive() {
|
||||
if (activePanel) { activePanel.setAttribute('hidden', ''); activePanel = null; }
|
||||
}
|
||||
root.addEventListener('click', function(e) {
|
||||
var trigger = e.target.closest('[data-ui-debug-popover]');
|
||||
if (trigger) {
|
||||
var name = trigger.getAttribute('data-ui-debug-popover');
|
||||
var panel = root.querySelector('[data-ui-debug-popover-panel="' + name + '"]');
|
||||
if (!panel) return;
|
||||
if (activePanel === panel) { closeActive(); return; }
|
||||
closeActive();
|
||||
panel.removeAttribute('hidden');
|
||||
activePanel = panel;
|
||||
return;
|
||||
}
|
||||
var item = e.target.closest('[data-ui-debug-popover-panel]');
|
||||
if (!item && activePanel) { closeActive(); }
|
||||
});
|
||||
document.addEventListener('click', function(e) {
|
||||
if (activePanel && !root.contains(e.target)) closeActive();
|
||||
});
|
||||
})();
|
||||
"##.to_string()}></script>
|
||||
</section>
|
||||
|
||||
// ─── Toast triggers ───
|
||||
<section class="ui-debug-section">
|
||||
<h2 class="ui-debug-section-title">"Toast 通知"</h2>
|
||||
<div class="ui-debug-row">
|
||||
<button type="button" class="ui-debug-btn ui-debug-btn--primary ui-debug-btn--md" data-ui-debug-toast="操作成功完成!" data-ui-debug-toast-kind="success">"成功 Toast"</button>
|
||||
<button type="button" class="ui-debug-btn ui-debug-btn--secondary ui-debug-btn--md" data-ui-debug-toast="请注意此提示信息" data-ui-debug-toast-kind="info">"信息 Toast"</button>
|
||||
<button type="button" class="ui-debug-btn ui-debug-btn--outline ui-debug-btn--md" data-ui-debug-toast="发生了一个错误" data-ui-debug-toast-kind="error">"错误 Toast"</button>
|
||||
<button type="button" class="ui-debug-btn ui-debug-btn--primary ui-debug-btn--sm" data-ui-debug-toast="警告:即将超时" data-ui-debug-toast-kind="warning">"警告 Toast"</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
// ─── Input / Select / Textarea ───
|
||||
<section class="ui-debug-section">
|
||||
<h2 class="ui-debug-section-title">"Input / Select / Textarea"</h2>
|
||||
<div class="ui-debug-form-grid">
|
||||
<div class="ui-debug-field">
|
||||
<label class="ui-debug-label" for="ui-debug-input-text">"文本输入"</label>
|
||||
<input id="ui-debug-input-text" type="text" class="ui-debug-input" placeholder="输入文本..." />
|
||||
</div>
|
||||
<div class="ui-debug-field">
|
||||
<label class="ui-debug-label" for="ui-debug-input-email">"邮箱输入"</label>
|
||||
<input id="ui-debug-input-email" type="email" class="ui-debug-input" placeholder="email@example.com" />
|
||||
</div>
|
||||
<div class="ui-debug-field">
|
||||
<label class="ui-debug-label" for="ui-debug-input-disabled">"禁用输入"</label>
|
||||
<input id="ui-debug-input-disabled" type="text" class="ui-debug-input" placeholder="已禁用" disabled />
|
||||
</div>
|
||||
<div class="ui-debug-field">
|
||||
<label class="ui-debug-label" for="ui-debug-select">"下拉选择"</label>
|
||||
<select id="ui-debug-select" class="ui-debug-select">
|
||||
<option value="">"请选择..."</option>
|
||||
<option value="a">"选项 A"</option>
|
||||
<option value="b">"选项 B"</option>
|
||||
<option value="c">"选项 C"</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ui-debug-field ui-debug-field--wide">
|
||||
<label class="ui-debug-label" for="ui-debug-textarea">"文本区域"</label>
|
||||
<textarea id="ui-debug-textarea" class="ui-debug-textarea" placeholder="输入多行文本..." rows="4"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</PageLayout>
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user