feat(render): 布局模板画廊 —— live 缩略预览 + 点选完整预览
- LayoutGallery 升级:每模板卡片 = 真实渲染缩略图(scale 0.4),点选看完整预览 - demoPageFrom:由 preview.demoBlocks 构造可渲染 demo 页(预览即真实) - playground 新增 dashboard 布局 + stat-grid widget;app-shell 验证 DefaultLayout 兜底 - 三个模板(admin/dashboard/app-shell)全部 live 渲染验证通过
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* dashboard 布局的真实渲染骨架 —— 数据看板
|
||||
* 槽位:stats(顶部指标条)/ content(主内容)
|
||||
*/
|
||||
import React from 'react'
|
||||
import type { LayoutRenderProps } from 'vibe-registry'
|
||||
|
||||
export function DashboardLayout({ slots, blocks, page }: LayoutRenderProps): React.ReactElement {
|
||||
const stats = (blocks['stats'] as React.ReactNode[] | undefined) ?? []
|
||||
const content = (blocks['content'] as React.ReactNode[] | undefined) ?? []
|
||||
|
||||
return (
|
||||
<div style={{ minHeight: '100vh', background: '#f3f4f6', fontFamily: 'system-ui, sans-serif' }}>
|
||||
<div style={{ background: '#111827', color: '#fff', padding: '14px 24px', display: 'flex', justifyContent: 'space-between' }}>
|
||||
<strong>{page.title ?? 'Dashboard'}</strong>
|
||||
<span style={{ fontSize: 12, opacity: 0.7 }}>layout: {slots.join(' + ')}</span>
|
||||
</div>
|
||||
<div style={{ padding: 20 }}>
|
||||
<div style={{ marginBottom: 16 }}>{stats}</div>
|
||||
<div>{content}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
/**
|
||||
* playground 入口:画廊 + 真实页面渲染(类 Vite 首页体验)
|
||||
* 演示:import 即注册 → setup 业务 → PageView 渲染
|
||||
* playground 入口:模板画廊(预览即真实)+ 业务实装示例
|
||||
*/
|
||||
import React from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
@@ -28,21 +27,26 @@ function App(): React.ReactElement {
|
||||
<div>
|
||||
<section style={{ borderBottom: '1px solid #e5e7eb', padding: '12px 24px', background: '#111827', color: '#fff' }}>
|
||||
<strong>vibe-registry playground</strong>
|
||||
<span style={{ marginLeft: 12, color: '#9ca3af', fontSize: 12 }}>import 即注册 · 页面只管业务 · 预览即真实</span>
|
||||
<span style={{ marginLeft: 12, color: '#9ca3af', fontSize: 12 }}>布局模板 · 引入即注册 · 页面只管业务 · 预览即真实</span>
|
||||
</section>
|
||||
|
||||
{/* ① 模板画廊:每卡真实渲染,点选看完整预览 */}
|
||||
<section style={{ padding: 24, background: '#f9fafb', borderBottom: '1px solid #e5e7eb' }}>
|
||||
<LayoutGallery registry={registry} />
|
||||
</section>
|
||||
|
||||
{/* ② 业务实装:同一布局,页面只管业务(订单列表) */}
|
||||
<section style={{ padding: 24 }}>
|
||||
<h2 style={{ marginTop: 0 }}>真实渲染:订单列表页(layout: admin)</h2>
|
||||
<h2 style={{ marginTop: 0 }}>业务实装示例:订单列表页(复用 admin 布局)</h2>
|
||||
<p style={{ color: '#6b7280', fontSize: 13, marginTop: 0 }}>
|
||||
页面只声明 layout:'admin' + blocks;数据来自 setup —— 与画廊里的 demo 用同一渲染内核
|
||||
</p>
|
||||
{data ? (
|
||||
<PageView page={orderListPage} registry={registry} data={data} />
|
||||
) : (
|
||||
<div style={{ color: '#6b7280' }}>setup 加载中…</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section style={{ padding: 24, borderTop: '1px solid #e5e7eb', background: '#f9fafb' }}>
|
||||
<LayoutGallery registry={registry} />
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,21 +4,103 @@
|
||||
*/
|
||||
import { registerLayout, registerWidget } from 'vibe-registry'
|
||||
import { AdminLayout } from './layouts/admin.tsx'
|
||||
import { PageHeader, DataTable, NavMenu } from './widgets.tsx'
|
||||
import { DashboardLayout } from './layouts/dashboard.tsx'
|
||||
import { PageHeader, DataTable, NavMenu, StatGrid } from './widgets.tsx'
|
||||
|
||||
// ── 布局注册(带真实渲染骨架 + demo 预览数据)──────────────────
|
||||
|
||||
// ── 布局注册(带真实渲染骨架)──────────────────────────────
|
||||
registerLayout({
|
||||
kind: 'layout',
|
||||
id: 'admin',
|
||||
slots: ['sidebar', 'header', 'content', 'footer'],
|
||||
description: '后台管理骨架',
|
||||
description: '后台管理骨架(侧栏+顶栏+内容)',
|
||||
render: AdminLayout,
|
||||
preview: {
|
||||
title: 'Admin · 后台管理',
|
||||
title: 'Admin 后台管理',
|
||||
demoBlocks: [
|
||||
{ slot: 'sidebar', widget: 'nav-menu', props: { items: [{ label: '订单', active: true }, { label: '商品' }] } },
|
||||
{ slot: 'header', widget: 'page-header', props: { title: '订单管理', subtitle: '全部订单' } },
|
||||
{ slot: 'content', widget: 'data-table' },
|
||||
{ slot: 'sidebar', widget: 'nav-menu', props: { items: [{ label: '订单', active: true }, { label: '商品' }, { label: '客户' }] } },
|
||||
{ slot: 'header', widget: 'page-header', props: { title: '订单管理', subtitle: '近 30 天全部订单' } },
|
||||
{
|
||||
slot: 'content',
|
||||
widget: 'data-table',
|
||||
props: {
|
||||
columns: [
|
||||
{ key: 'id', title: '订单号' },
|
||||
{ key: 'customer', title: '客户' },
|
||||
{ key: 'amount', title: '金额' },
|
||||
{ key: 'status', title: '状态' },
|
||||
],
|
||||
rows: [
|
||||
{ id: 'A1001', customer: '张三', amount: '¥1,299', status: '已支付' },
|
||||
{ id: 'A1002', customer: '李四', amount: '¥88', status: '待发货' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
registerLayout({
|
||||
kind: 'layout',
|
||||
id: 'dashboard',
|
||||
slots: ['stats', 'content'],
|
||||
description: '数据看板(顶部指标 + 主内容)',
|
||||
render: DashboardLayout,
|
||||
preview: {
|
||||
title: 'Dashboard 数据看板',
|
||||
demoBlocks: [
|
||||
{
|
||||
slot: 'stats',
|
||||
widget: 'stat-grid',
|
||||
props: {
|
||||
stats: [
|
||||
{ label: '今日订单', value: 128, delta: '+12%' },
|
||||
{ label: '销售额', value: '¥45,200', delta: '+8%' },
|
||||
{ label: '失败率', value: '1.2%', delta: '-0.3%' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
slot: 'content',
|
||||
widget: 'data-table',
|
||||
props: {
|
||||
columns: [
|
||||
{ key: 'name', title: '指标' },
|
||||
{ key: 'value', title: '数值' },
|
||||
],
|
||||
rows: [
|
||||
{ name: '订单转化率', value: '3.4%' },
|
||||
{ name: '客单价', value: '¥353' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
registerLayout({
|
||||
kind: 'layout',
|
||||
id: 'app-shell',
|
||||
slots: ['header', 'content'],
|
||||
description: '通用外壳(顶栏+内容),无自定义 render → 走缺省兜底',
|
||||
preview: {
|
||||
title: 'App Shell 通用外壳',
|
||||
demoBlocks: [
|
||||
{ slot: 'header', widget: 'page-header', props: { title: '欢迎使用', subtitle: '无 render 的布局由 DefaultLayout 兜底' } },
|
||||
{
|
||||
slot: 'content',
|
||||
widget: 'data-table',
|
||||
props: {
|
||||
columns: [
|
||||
{ key: 'k', title: '项' },
|
||||
{ key: 'v', title: '值' },
|
||||
],
|
||||
rows: [
|
||||
{ k: '布局', v: 'app-shell' },
|
||||
{ k: '渲染', v: 'DefaultLayout 兜底' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
@@ -42,3 +124,9 @@ registerWidget({
|
||||
component: DataTable,
|
||||
description: '通用数据表格(列定义来自声明 props)',
|
||||
})
|
||||
registerWidget({
|
||||
kind: 'widget',
|
||||
id: 'stat-grid',
|
||||
component: StatGrid,
|
||||
description: '指标格栅(仪表盘统计)',
|
||||
})
|
||||
|
||||
@@ -70,3 +70,33 @@ export function NavMenu({ items = [] }: { items?: Array<{ label: string; active?
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/** 统计卡片(仪表盘用) */
|
||||
export function StatCard({
|
||||
label,
|
||||
value,
|
||||
delta,
|
||||
}: {
|
||||
label?: string
|
||||
value?: unknown
|
||||
delta?: string
|
||||
}): React.ReactElement {
|
||||
return React.createElement(
|
||||
'div',
|
||||
{ style: { border: '1px solid #e5e7eb', borderRadius: 8, padding: 16, background: '#fff' } },
|
||||
React.createElement('div', { style: { fontSize: 12, color: '#6b7280' } }, label ?? ''),
|
||||
React.createElement('div', { style: { fontSize: 24, fontWeight: 700, margin: '4px 0' } }, String(value ?? '-')),
|
||||
delta ? React.createElement('div', { style: { fontSize: 12, color: delta.startsWith('-') ? '#dc2626' : '#16a34a' } }, delta) : null,
|
||||
)
|
||||
}
|
||||
|
||||
/** 指标格子容器(dashboard 布局的内容区 demo 用) */
|
||||
export function StatGrid({ stats = [] }: { stats?: Array<{ label?: string; value?: unknown; delta?: string }> }): React.ReactElement {
|
||||
return React.createElement(
|
||||
'div',
|
||||
{ style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', gap: 12 } },
|
||||
...(stats as Array<{ label?: string; value?: unknown; delta?: string }>).map((s, i) =>
|
||||
React.createElement(StatCard, { key: i, label: s.label, value: s.value, delta: s.delta }),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user