Files
wukuang/docs/widget-steps/02-vue-component.html
T

309 lines
12 KiB
HTML
Raw Normal View History

2026-05-23 14:05:22 +08:00
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>步骤详解 - 创建 Vue 渲染组件</title>
<style>
:root {
--bg-primary: #0a0e1a;
--bg-secondary: #111827;
--bg-card: #1a2035;
--bg-card-hover: #1f2a45;
--border-color: #2d3a5c;
--text-primary: #e2e8f0;
--text-secondary: #94a3b8;
--text-muted: #64748b;
--accent-green: #10b981;
--accent-green-light: #34d399;
--accent-cyan: #06b6d4;
--accent-purple: #8b5cf6;
--shadow-lg: 0 10px 40px rgba(0, 0, 0, 0.4);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.7;
padding: 2rem;
}
.container { max-width: 1000px; margin: 0 auto; }
.back-link {
display: inline-flex;
align-items: center;
gap: 0.5rem;
color: var(--accent-green-light);
text-decoration: none;
margin-bottom: 2rem;
padding: 0.5rem 1rem;
background: var(--bg-card);
border-radius: 8px;
border: 1px solid var(--border-color);
transition: all 0.2s ease;
}
.back-link:hover { background: var(--bg-card-hover); transform: translateX(-4px); }
.header {
background: var(--bg-card);
border-radius: 16px;
padding: 2rem;
margin-bottom: 2rem;
border: 1px solid var(--border-color);
box-shadow: var(--shadow-lg);
border-left: 4px solid var(--accent-green);
}
.step-badge {
display: inline-block;
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
padding: 0.3rem 1rem;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
margin-bottom: 1rem;
}
.header h1 { font-size: 2rem; margin-bottom: 0.5rem; }
.header .desc { color: var(--text-secondary); font-size: 1rem; }
.section {
background: var(--bg-card);
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 1.5rem;
border: 1px solid var(--border-color);
}
.section h2 {
font-size: 1.3rem;
margin-bottom: 1rem;
color: var(--accent-green-light);
display: flex;
align-items: center;
gap: 0.5rem;
}
.section p { color: var(--text-secondary); margin-bottom: 1rem; }
.code-block {
background: var(--bg-secondary);
padding: 1.25rem;
border-radius: 8px;
font-family: 'Fira Code', 'Cascadia Code', monospace;
font-size: 0.85rem;
color: var(--accent-cyan);
overflow-x: auto;
line-height: 1.6;
margin-bottom: 1rem;
}
.code-block .comment { color: var(--text-muted); }
.code-block .tag { color: var(--accent-green-light); }
.code-block .attr { color: var(--accent-purple); }
.code-block .string { color: #fbbf24; }
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.info-card {
background: var(--bg-secondary);
padding: 1rem;
border-radius: 8px;
border: 1px solid var(--border-color);
}
.info-card h4 { font-size: 0.9rem; margin-bottom: 0.5rem; color: var(--accent-green-light); }
.info-card p { font-size: 0.85rem; color: var(--text-secondary); margin: 0; }
.file-path {
font-family: 'Fira Code', monospace;
font-size: 0.8rem;
color: var(--accent-cyan);
background: rgba(6, 182, 212, 0.1);
padding: 0.25rem 0.5rem;
border-radius: 4px;
}
ul { list-style: none; padding: 0; }
ul li { padding: 0.4rem 0; color: var(--text-secondary); display: flex; align-items: flex-start; gap: 0.5rem; }
ul li::before { content: "▸"; color: var(--accent-green); flex-shrink: 0; }
.warning {
background: rgba(245, 158, 11, 0.1);
border: 1px solid rgba(245, 158, 11, 0.3);
border-radius: 8px;
padding: 1rem;
margin-top: 1rem;
}
.warning h4 { color: #fbbf24; margin-bottom: 0.5rem; }
.warning p { color: var(--text-secondary); margin: 0; font-size: 0.9rem; }
.tip {
background: rgba(6, 182, 212, 0.1);
border: 1px solid rgba(6, 182, 212, 0.3);
border-radius: 8px;
padding: 1rem;
margin-top: 1rem;
}
.tip h4 { color: var(--accent-cyan); margin-bottom: 0.5rem; }
.tip p { color: var(--text-secondary); margin: 0; font-size: 0.9rem; }
</style>
</head>
<body>
<div class="container">
<a href="../../物料组件添加流程.html" class="back-link">← 返回主文档</a>
<div class="header">
<div class="step-badge">步骤 2</div>
<h1>创建 Vue 渲染组件</h1>
<p class="desc">定义物料组件的实际 UI 结构和渲染逻辑</p>
</div>
<div class="section">
<h2>📋 功能描述</h2>
<p>创建 Vue 单文件组件 (.vue),定义物料组件在画布和运行时的实际渲染内容。这是用户在页面设计器中拖拽后看到的视觉效果,也是最终用户在使用低代码页面时看到的 UI 元素。</p>
</div>
<div class="section">
<h2>🧩 模块描述</h2>
<p>该组件位于 form-designer 的 widget 目录下,主要职责:</p>
<ul>
<li><strong>画布渲染</strong> - 在页面设计器画布中显示组件预览</li>
<li><strong>运行时渲染</strong> - 在最终用户访问页面时渲染实际 UI</li>
<li><strong>属性绑定</strong> - 接收来自 Helper buildWidget 生成的 schema props</li>
<li><strong>事件处理</strong> - 处理用户交互和组件事件</li>
</ul>
</div>
<div class="section">
<h2>📁 文件信息</h2>
<div class="info-grid">
<div class="info-card">
<h4>文件路径</h4>
<p><span class="file-path">packages/form-designer/src/config/widget/&lt;组件名&gt;.vue</span></p>
</div>
<div class="info-card">
<h4>操作类型</h4>
<p><strong style="color: #34d399;">新建</strong> - 创建全新的 Vue 组件文件</p>
</div>
<div class="info-card">
<h4>组件模式</h4>
<p>简单组件直接建 .vue 文件,复杂组件可建目录</p>
</div>
<div class="info-card">
<h4>所属层级</h4>
<p>页面设计器层 (packages/form-designer)</p>
</div>
</div>
</div>
<div class="section">
<h2>💻 代码示例 - 简单组件</h2>
<p>适用于 CommonBtn、CommonTxt 等展示型组件:</p>
<div class="code-block">
<span class="comment">&lt;!-- packages/form-designer/src/config/widget/MyNewComponent.vue --&gt;</span>
&lt;<span class="tag">template</span>&gt;
&lt;<span class="tag">div</span> <span class="attr">class</span>=<span class="string">"my-new-component"</span>&gt;
{{ displayContent }}
&lt;/<span class="tag">div</span>&gt;
&lt;/<span class="tag">template</span>&gt;
&lt;<span class="tag">script</span> <span class="attr">setup</span> <span class="attr">lang</span>=<span class="string">"ts"</span>&gt;
import { computed } from 'vue'
const props = defineProps&lt;{
content?: string
fontSize?: string
color?: string
}&gt;()
const displayContent = computed(() =&gt; {
return props.content || '默认文本'
})
&lt;/<span class="tag">script</span>&gt;
&lt;<span class="tag">style</span> <span class="attr">scoped</span>&gt;
.my-new-component {
padding: 8px 16px;
font-size: v-bind(fontSize);
color: v-bind(color);
}
&lt;/<span class="tag">style</span>&gt;
</div>
</div>
<div class="section">
<h2>💻 代码示例 - 复杂组件(目录模式)</h2>
<p>适用于 Anchor、CommonTree 等包含子组件和工具函数的复杂组件:</p>
<div class="code-block">
<span class="comment">// 目录结构:</span>
<span class="comment">// packages/form-designer/src/config/widget/anchor/</span>
<span class="comment">// ├── index.vue # 主组件入口</span>
<span class="comment">// ├── AnchorIcon.vue # 子组件</span>
<span class="comment">// ├── Constant.ts # 常量/默认数据</span>
<span class="comment">// ├── hooks.ts # 组合式函数</span>
<span class="comment">// └── utils.ts # 工具函数</span>
<span class="comment">// packages/form-designer/src/config/widget/anchor/index.vue</span>
&lt;<span class="tag">template</span>&gt;
&lt;<span class="tag">div</span> <span class="attr">class</span>=<span class="string">"anchor-container"</span>&gt;
&lt;<span class="tag">AnchorIcon</span> /&gt;
&lt;<span class="tag">ul</span>&gt;
&lt;<span class="tag">li</span> <span class="attr">v-for</span>=<span class="string">"item in anchors"</span> <span class="attr">:key</span>=<span class="string">"item.id"</span>&gt;
{{ item.label }}
&lt;/<span class="tag">li</span>&gt;
&lt;/<span class="tag">ul</span>&gt;
&lt;/<span class="tag">div</span>&gt;
&lt;/<span class="tag">template</span>&gt;
&lt;<span class="tag">script</span> <span class="attr">setup</span> <span class="attr">lang</span>=<span class="string">"ts"</span>&gt;
import { useAnchorHooks } from './hooks'
import { DEFAULT_ANCHORS } from './Constant'
const props = defineProps&lt;{
anchors?: Array&lt;{ id: string; label: string }&gt;
}&gt;()
const { anchors } = useAnchorHooks(props.anchors ?? DEFAULT_ANCHORS)
&lt;/<span class="tag">script</span>&gt;
</div>
</div>
<div class="section">
<h2>📝 Props 来源</h2>
<p>组件接收的 props 来自 Helper 的 <code>buildWidget()</code> 方法返回的 schema</p>
<div class="code-block">
<span class="comment">// Helper 中的 buildWidget 返回:</span>
{
type: WidgetType.MyNewComponent,
props: {
content: <span class="string">'默认文本'</span>,
fontSize: <span class="string">'14px'</span>,
color: <span class="string">'#333'</span>
}
}
<span class="comment">// 这些 props 会自动传递给 Vue 组件</span>
</div>
</div>
<div class="section">
<h2>⚠️ 注意事项</h2>
<div class="warning">
<h4>样式隔离</h4>
<p>建议使用 <code>&lt;style scoped&gt;</code> 或 CSS Modules,避免全局样式污染。如确需全局样式,使用 <code>:global()</code> 明确声明。</p>
</div>
<div class="tip" style="margin-top: 1rem;">
<h4>组件设计建议</h4>
<p>保持组件简单专注,只负责自身渲染逻辑。复杂业务逻辑应抽取到 composables 或 utils 中。</p>
</div>
<ul style="margin-top: 1rem;">
<li>组件名必须与 WidgetType 枚举值保持一致</li>
<li>确保组件在没有 props 的情况下也能正常渲染(使用默认值)</li>
<li>复杂组件建议拆分到独立目录,便于维护</li>
</ul>
</div>
<div class="section" style="border-left: 4px solid var(--accent-green);">
<h2>➡️ 下一步</h2>
<p>完成 Vue 组件创建后,继续执行:</p>
<ul>
<li><strong>步骤 3</strong>:创建 Helper 配置文件(定义物料面板、属性面板、拖拽行为等)</li>
<li><strong>步骤 4</strong>:在 RegisterWidget.ts 中注册组件实例</li>
</ul>
</div>
</div>
</body>
</html>