docs: 验证渲染系统可以嵌入文档

This commit is contained in:
unanmed 2026-10-29 20:26:02 +08:00
parent e05cc55b95
commit 3cc0fa9bf6
6 changed files with 340 additions and 1 deletions

View File

@ -0,0 +1,50 @@
<template>
<container :width="480" :height="320">
<g-rect
:x="40"
:y="40"
:width="160"
:height="90"
:fillStyle="color"
strokeStyle="#9dc0ff"
:lineWidth="2"
@click="onClick"
/>
<g-circle
:x="320"
:y="110"
:radius="48"
fillStyle="#e0a42f"
strokeStyle="#ffe3a3"
:lineWidth="2"
@click="onClick"
/>
<text
:x="40"
:y="200"
:text="'点击次数:' + count"
fillStyle="#e8f0ff"
/>
<text
:x="40"
:y="240"
:text="'点击颜色:' + color"
fillStyle="#8fb6ff"
/>
</container>
</template>
<script setup lang="ts">
import { ref } from 'vue';
/** 点击次数 */
const count = ref<number>(0);
/** 矩形当前的填充色,点击后轮换 */
const color = ref<string>('#2f6fed');
/** 点击图形时更新计数与颜色 */
function onClick(): void {
count.value++;
color.value = color.value === '#2f6fed' ? '#2fbf71' : '#2f6fed';
}
</script>

View File

@ -0,0 +1,120 @@
<template>
<container :width="480" :height="320">
<text :x="40" :y="24" text="打开 UI" fillStyle="#e8f0ff" />
<g-rect
:x="40"
:y="40"
:width="150"
:height="56"
fillStyle="#2f6fed"
strokeStyle="#9dc0ff"
:lineWidth="2"
@click="openUi"
/>
<text :x="240" :y="24" text="关闭 UI" fillStyle="#e8f0ff" />
<g-rect
:x="240"
:y="40"
:width="150"
:height="56"
fillStyle="#3a2f5f"
strokeStyle="#b09dff"
:lineWidth="2"
@click="closeUi"
/>
<text
:x="40"
:y="140"
:text="'栈长度:' + stackSize"
fillStyle="#8fb6ff"
/>
<text
:x="40"
:y="180"
:text="active ? '已打开' : '未打开'"
fillStyle="#8fb6ff"
/>
<UIRenderer />
</container>
</template>
<script setup lang="ts">
import { computed, defineComponent, h, onUnmounted } from 'vue';
import {
UIController,
GameUI,
IUIDefaultPropsBase,
IUIInstance
} from '@motajs/system';
const controller = new UIController<IUIDefaultPropsBase>();
controller.setPropsBaseCreator((instance, ctrl) => ({
instance,
controller: ctrl
}));
/** 当前打开的 UI 实例,打开与关闭必须复用同一个变量 */
let instance: IUIInstance | null = null;
/** 最小 UI 组件,用渲染函数构造,不依赖运行时模板编译器 */
const DemoPanel = defineComponent({
name: 'PocDemoPanel',
setup() {
return () =>
h('container', { x: 240, y: 140, width: 220, height: 150 }, [
h('g-rect', {
x: 0,
y: 0,
width: 220,
height: 150,
fillStyle: '#1d2b45',
strokeStyle: '#7aa2ff',
lineWidth: 2
}),
h('text', {
x: 16,
y: 40,
text: '这是一个最小 UI',
fillStyle: '#e8f0ff'
}),
h('text', {
x: 16,
y: 80,
text: '由 UIController 打开',
fillStyle: '#8fb6ff'
})
]);
}
});
const ui = new GameUI('poc-minimal-ui', DemoPanel);
/** 把 controller.render() 挂进同一棵渲染树 */
const UIRenderer = defineComponent({
name: 'PocUIRenderer',
setup() {
return () => controller.render();
}
});
const active = computed<boolean>(() => controller.active.value);
const stackSize = computed<number>(() => controller.stack.length);
/** 打开最小 UI,已打开时不重复入栈 */
function openUi(): void {
if (instance) return;
instance = controller.open(ui, {});
}
/** 关闭最小 UI,复用打开时保存的实例 */
function closeUi(): void {
if (instance) {
controller.close(instance);
instance = null;
}
}
onUnmounted(() => {
controller.closeAll();
});
</script>

View File

@ -0,0 +1,84 @@
<template>
<div :style="{ background: props.background, overflow: 'hidden' }">
<canvas ref="canvasRef"></canvas>
</div>
</template>
<script setup lang="ts">
import { Component, onMounted, onUnmounted, ref } from 'vue';
/** demo 模块的导出形态,只要求默认导出组件 */
interface DemoModule {
default: Component;
}
/** 动态导入一个 demo 组件的加载函数 */
type DemoLoader = () => Promise<DemoModule>;
/** 包装组件的外部参数 */
interface RenderDemoProps {
/** 动态导入 demo 组件的加载函数 */
loader: DemoLoader;
/** 逻辑宽度 */
width?: number;
/** 逻辑高度 */
height?: number;
/** 缩放比例 */
scale?: number;
/** 画布容器背景色 */
background?: string;
}
/** 可清理的 app 引用,仅声明清理所需的 unmount */
interface AppHandle {
unmount(): void;
}
const props = withDefaults(defineProps<RenderDemoProps>(), {
width: 480,
height: 320,
scale: 1,
background: '#101418'
});
const canvasRef = ref<HTMLCanvasElement | null>(null);
/** 组件是否已经卸载,用于拦截动态导入完成后的竞态 */
let disposed = false;
/** 渲染器的清理入口,在渲染器创建后、挂载 app 之前赋值 */
let dispose: (() => void) | null = null;
onMounted(async () => {
const { MotaRenderer } = await import('@motajs/render');
const { createRendererFor } = await import('@motajs/render-vue');
const demo = await props.loader();
if (disposed || !canvasRef.value) return;
const canvas = canvasRef.value;
const renderer = new MotaRenderer({
canvas,
width: props.width,
height: props.height
});
let appRef: AppHandle | null = null;
dispose = () => {
if (appRef) {
appRef.unmount();
}
renderer.destroy();
};
if (props.scale !== 1) {
renderer.setScale(props.scale);
}
const app = createRendererFor(renderer).createApp(demo.default);
appRef = app;
app.mount(renderer);
// 一个包装实例只服务一个 demo,loader 变更不触发重挂载
});
onUnmounted(() => {
disposed = true;
dispose?.();
dispose = null;
});
</script>

View File

@ -1,7 +1,30 @@
import { defineConfig } from 'vitepress';
import { MermaidMarkdown, MermaidPlugin } from 'vitepress-plugin-mermaid';
import vuejsx from '@vitejs/plugin-vue-jsx';
import { join } from 'path';
/** 文档站内的自定义渲染标签,与主站 vite.config.ts 保持一致 */
const custom = [
'container',
'custom',
'text',
'image',
'shader',
'comment',
'custom-container',
'map-render',
'animate',
'damage',
'graphics',
'icon',
'winskin'
];
/** 判断一个标签是否为自定义渲染标签 */
function isCustomElement(tag: string): boolean {
return custom.includes(tag) || tag.startsWith('g-');
}
// https://vitepress.dev/reference/site-config
export default defineConfig({
title: 'HTML5 魔塔样板 V2.B',
@ -298,9 +321,29 @@ export default defineConfig({
label: '中文'
}
},
vue: {
template: {
compilerOptions: {
isCustomElement
}
}
},
vite: {
// @ts-expect-error 类型错误
plugins: [MermaidPlugin()],
plugins: [MermaidPlugin(), vuejsx({ isCustomElement })],
resolve: {
alias: [
{
find: /^@motajs\/(.+)$/,
replacement: `${process.cwd()}/packages/$1/src`
},
{
find: /^@user\/(.+)$/,
replacement: `${process.cwd()}/packages-user/$1/src`
}
],
dedupe: ['vue']
},
optimizeDeps: {
include: ['mermaid']
},

26
docs/poc/render-demo.md Normal file
View File

@ -0,0 +1,26 @@
# 渲染器内嵌演示(临时技术验证)
本页是「文档内嵌可运行示例」的**临时技术验证页**,不进入导航;POC 通过后再由用户决定是否保留。
## 覆盖范围
- 渲染基础:容器、矩形、圆、文字,以及点击图形后计数与颜色的变化。
- UISystem:`UIController` 打开 / 关闭一个最小 UI,页面上的栈长度与打开状态随之变化。
两个示例都只在浏览器中挂载;构建期预渲染会跳过它们,因此不会触碰 `document`。
<ClientOnly>
<RenderDemo :loader="basicLoader" :width="480" :height="320" />
<RenderDemo :loader="uiLoader" :width="480" :height="320" />
<template #fallback>
渲染示例需要浏览器环境。
</template>
</ClientOnly>
<script setup>
import RenderDemo from '../.vitepress/components/renderDemo.vue';
const basicLoader = () =>
import('../.vitepress/components/demos/basicRender.vue');
const uiLoader = () => import('../.vitepress/components/demos/uiSystem.vue');
</script>

16
docs/tsconfig.json Normal file
View File

@ -0,0 +1,16 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"baseUrl": "..",
"ignoreDeprecations": "6.0",
"paths": {
"@motajs/*": ["./packages/*/src"],
"@user/*": ["./packages-user/*/src"]
}
},
"include": [
"./.vitepress/**/*.ts",
"./.vitepress/**/*.vue",
"../src/types/vite-env.d.ts"
]
}