Files
wukuang/packages/lowcode-create/playground/App.vue
T
2026-05-23 14:05:22 +08:00

1170 lines
31 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="tsx">
import type { LowCodeFormSchemaItem } from '../src/typings/FormSchema';
import { ElButton, ElCard, ElCollapseTransition, ElDivider, ElSpace, ElTag } from 'element-plus-cisdi';
import { reactive, ref } from 'vue';
import { useDetailRender } from '../src/composables/useDetailRender';
import { CustomItem } from './CustomItem';
import { CustomLayout } from './CustomLayout';
// ==================== 代码展示控制 ====================
const codeVisible = reactive<Record<string, boolean>>({});
function toggleCode(key: string) {
codeVisible[key] = !codeVisible[key];
}
// 代码图标组件
function CodeIcon() {
return (
<svg
viewBox="0 0 24 24"
width="18"
height="18"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
style={{ cursor: 'pointer' }}
>
<polyline points="16 18 22 12 16 6" />
<polyline points="8 6 2 12 8 18" />
</svg>
);
}
// ==================== 示例源码 ====================
const sourceCode = {
example1: `const [BasicDetail] = useDetailRender({
formType: 'element',
data: userData.value,
columns: [
{ prop: 'name', label: '姓名' },
{ prop: 'age', label: '年龄' },
{ prop: 'email', label: '邮箱' },
{ prop: 'phone', label: '电话' },
{ prop: 'department', label: '部门' },
{ prop: 'position', label: '职位' },
],
layout: {
props: { column: 3, border: true },
},
});`,
example2: `// 2.1 垂直布局
const [VerticalDetail] = useDetailRender({
formType: 'element',
data: userData.value,
columns: [
{ prop: 'name', label: '姓名' },
{ prop: 'department', label: '部门' },
{ prop: 'position', label: '职位' },
],
layout: {
props: { column: 3, border: true, direction: 'vertical' },
},
});
// 2.2 无边框 + 2列
const [NoBorderDetail] = useDetailRender({
formType: 'element',
data: userData.value,
columns: [
{ prop: 'name', label: '姓名' },
{ prop: 'email', label: '邮箱' },
{ prop: 'phone', label: '电话' },
{ prop: 'department', label: '部门' },
],
layout: {
props: { column: 2, border: false },
},
});
// 2.3 单列布局 + 大尺寸
const [SingleColumnDetail] = useDetailRender({
formType: 'element',
data: userData.value,
columns: [
{ prop: 'name', label: '姓名' },
{ prop: 'email', label: '邮箱' },
{ prop: 'address', label: '地址' },
],
layout: {
props: { column: 1, border: true, size: 'large' },
},
});
// 2.4 小尺寸
const [SmallDetail] = useDetailRender({
formType: 'element',
data: userData.value,
columns: [
{ prop: 'name', label: '姓名' },
{ prop: 'department', label: '部门' },
{ prop: 'position', label: '职位' },
],
layout: {
props: { column: 3, border: true, size: 'small' },
},
});`,
example3: `const [CustomRenderDetail] = useDetailRender({
formType: 'element',
data: userData.value,
columns: [
{ prop: 'name', label: '姓名' },
{ prop: 'age', label: '年龄', formatter: v => \`\${v}\` },
{
prop: 'status',
label: '状态',
render: v => (
<ElTag type={v === 'active' ? 'success' : 'danger'}>
{v === 'active' ? '在职' : '离职'}
</ElTag>
),
},
{ prop: 'salary', label: '薪资', formatter: v => \`¥\${v.toLocaleString()}\` },
{
prop: 'skills',
label: '技能',
render: v => (
<ElSpace>
{v.map(skill => (
<ElTag key={skill} size="small" type="info">{skill}</ElTag>
))}
</ElSpace>
),
},
{ prop: 'manager', label: '直属领导', emptyText: '暂无' },
],
layout: {
props: { column: 3, border: true },
},
});`,
example4: `const formSchemas: LowCodeFormSchemaItem[] = [
{
componentCode: 'input-1',
type: 'input',
fieldId: 'name',
name: '姓名',
metaProps: {
formItem: { label: '用户姓名' },
code: 'input',
},
},
// ... more schema items
];
const [SchemaDetail] = useDetailRender({
formType: 'element',
data: userData.value,
schemas: formSchemas,
layout: {
props: { column: 2, border: true, title: '从 Schema 自动生成' },
},
});`,
example5: `const [CustomComponentDetail] = useDetailRender({
formType: 'element',
data: userData.value,
columns: [
{ prop: 'name', label: '姓名' },
{ prop: 'department', label: '部门' },
{ prop: 'position', label: '职位' },
{ prop: 'email', label: '邮箱' },
],
layout: {
component: CustomLayout,
props: { title: '自定义卡片风格' },
},
item: {
component: CustomItem,
},
});`,
example6: `const dynamicData = ref({
name: '初始用户',
count: 0,
lastUpdate: new Date().toLocaleTimeString(),
});
const [DynamicDetail, dynamicApi] = useDetailRender({
formType: 'element',
data: dynamicData,
columns: [
{ prop: 'name', label: '用户名' },
{ prop: 'count', label: '更新次数' },
{ prop: 'lastUpdate', label: '最后更新' },
],
layout: {
props: { column: 3, border: true },
},
});
function updateDynamicData() {
const newData = {
name: \`用户_\${Math.random().toString(36).slice(2, 8)}\`,
count: dynamicData.value.count + 1,
lastUpdate: new Date().toLocaleTimeString(),
};
dynamicApi.setData(newData);
dynamicData.value = newData;
}`,
example7: `const userList = ref([
{ id: 1, name: '张三', age: 28, department: '技术部', status: 'active' },
{ id: 2, name: '李四', age: 32, department: '产品部', status: 'active' },
{ id: 3, name: '王五', age: 25, department: '设计部', status: 'inactive' },
]);
const [ListItemDetail] = useDetailRender({
formType: 'element',
data: {},
columns: [
{ prop: 'name', label: '姓名' },
{ prop: 'age', label: '年龄' },
{ prop: 'department', label: '部门' },
{
prop: 'status',
label: '状态',
render: v => (
<ElTag size="small" type={v === 'active' ? 'success' : 'info'}>
{v === 'active' ? '在职' : '离职'}
</ElTag>
),
},
],
layout: {
props: { column: 4, border: true, size: 'small' },
},
});
// Template 使用
<div v-for="item in userList" :key="item.id">
<ListItemDetail :data="item" />
</div>`,
example8: `const [MobileDetail] = useDetailRender({
formType: 'vant',
data: userData.value,
columns: [
{ prop: 'name', label: '姓名' },
{ prop: 'age', label: '年龄', formatter: v => \`\${v}\` },
{ prop: 'department', label: '部门' },
{ prop: 'phone', label: '电话' },
{ prop: 'email', label: '邮箱' },
],
layout: {
props: { title: '基本信息', border: true },
},
});`,
example9: `const [GroupedDetail] = useDetailRender({
formType: 'element',
data: assetData.value,
groups: [
{
title: '基本信息',
columns: [
{ prop: 'assetName', label: '资产名称' },
{ prop: 'assetType', label: '资产类别' },
// ... more columns
],
layout: { column: 4, border: true },
},
{
title: '使用管理信息',
columns: [
{ prop: 'manageDept', label: '企业管理部门' },
{ prop: 'custodian', label: '保管人' },
// ... more columns
],
layout: { column: 4, border: true },
},
// ... more groups
],
});`,
example10: `const [GroupedWithCustomRender] = useDetailRender({
formType: 'element',
data: attachmentData.value,
groups: [
{
title: '文档信息',
columns: [
{ prop: 'name', label: '文档名称' },
{ prop: 'type', label: '文档类型' },
],
layout: { column: 2, border: true },
},
{
title: '附件信息',
columns: [],
render: data => (
<table class="attachment-table">
<thead>
<tr>
<th>序号</th>
<th>附件类型</th>
<th>附件内容</th>
<th>状态</th>
</tr>
</thead>
<tbody>
{data.attachments.map((item, index) => (
<tr key={item.id}>
<td>{String(index + 1).padStart(2, '0')}</td>
<td>{item.type}</td>
<td>{item.name}</td>
<td>
<ElTag size="small" type={item.status === '已完成' ? 'success' : 'info'}>
{item.status}
</ElTag>
</td>
</tr>
))}
</tbody>
</table>
),
},
],
});`,
};
// ==================== 模拟数据 ====================
const userData = ref({
name: '张三',
age: 28,
email: 'zhangsan@example.com',
phone: '13800138000',
status: 'active',
department: '技术部',
position: '前端工程师',
joinDate: '2022-03-15',
salary: 15000,
address: '北京市朝阳区xxx街道xxx号',
skills: ['Vue', 'React', 'TypeScript'],
manager: null,
});
// ==================== 示例 1: 基础用法 ====================
const [BasicDetail] = useDetailRender({
formType: 'element',
data: userData.value,
columns: [
{ prop: 'name', label: '姓名' },
{ prop: 'age', label: '年龄' },
{ prop: 'email', label: '邮箱' },
{ prop: 'phone', label: '电话' },
{ prop: 'department', label: '部门' },
{ prop: 'position', label: '职位' },
],
layout: {
props: { column: 3, border: true },
},
});
// ==================== 示例 2: 不同布局样式 ====================
// 2.1 垂直布局
const [VerticalDetail] = useDetailRender({
formType: 'element',
data: userData.value,
columns: [
{ prop: 'name', label: '姓名' },
{ prop: 'department', label: '部门' },
{ prop: 'position', label: '职位' },
],
layout: {
props: { column: 3, border: true, direction: 'vertical' },
},
});
// 2.2 无边框 + 2列
const [NoBorderDetail] = useDetailRender({
formType: 'element',
data: userData.value,
columns: [
{ prop: 'name', label: '姓名' },
{ prop: 'email', label: '邮箱' },
{ prop: 'phone', label: '电话' },
{ prop: 'department', label: '部门' },
],
layout: {
props: { column: 2, border: false },
},
});
// 2.3 单列布局 + 大尺寸
const [SingleColumnDetail] = useDetailRender({
formType: 'element',
data: userData.value,
columns: [
{ prop: 'name', label: '姓名' },
{ prop: 'email', label: '邮箱' },
{ prop: 'address', label: '地址' },
],
layout: {
props: { column: 1, border: true, size: 'large' },
},
});
// 2.4 小尺寸
const [SmallDetail] = useDetailRender({
formType: 'element',
data: userData.value,
columns: [
{ prop: 'name', label: '姓名' },
{ prop: 'department', label: '部门' },
{ prop: 'position', label: '职位' },
],
layout: {
props: { column: 3, border: true, size: 'small' },
},
});
// ==================== 示例 3: 自定义渲染 ====================
const [CustomRenderDetail] = useDetailRender({
formType: 'element',
data: userData.value,
columns: [
{ prop: 'name', label: '姓名' },
{ prop: 'age', label: '年龄', formatter: v => `${v}` },
{
prop: 'status',
label: '状态',
render: v => (
<ElTag type={v === 'active' ? 'success' : 'danger'}>
{v === 'active' ? '在职' : '离职'}
</ElTag>
),
},
{ prop: 'salary', label: '薪资', formatter: v => `¥${v.toLocaleString()}` },
{
prop: 'skills',
label: '技能',
render: v => (
<ElSpace>
{(v as string[]).map(skill => (
<ElTag key={skill} size="small" type="info">{skill}</ElTag>
))}
</ElSpace>
),
},
{ prop: 'manager', label: '直属领导', emptyText: '暂无' },
],
layout: {
props: { column: 3, border: true },
},
});
// ==================== 示例 4: 复用 Schema ====================
const formSchemas: LowCodeFormSchemaItem[] = [
{
componentCode: 'input-1',
type: 'input',
fieldId: 'name',
name: '姓名',
metaProps: {
formItem: { label: '用户姓名' },
code: 'input',
},
},
{
componentCode: 'input-2',
type: 'input',
fieldId: 'email',
name: '邮箱',
metaProps: {
formItem: { label: '电子邮箱' },
code: 'input',
},
},
{
componentCode: 'input-3',
type: 'input',
fieldId: 'phone',
name: '电话',
metaProps: {
formItem: { label: '联系电话' },
code: 'input',
},
},
{
componentCode: 'input-4',
type: 'input',
fieldId: 'department',
name: '部门',
metaProps: {
formItem: { label: '所属部门' },
code: 'input',
},
},
{
componentCode: 'input-5',
type: 'input',
fieldId: 'position',
name: '职位',
metaProps: {
formItem: { label: '职位名称' },
code: 'input',
},
},
{
componentCode: 'hidden-1',
type: 'input',
fieldId: 'hiddenField',
controls: { hidden: true },
metaProps: { code: 'input' },
},
];
const [SchemaDetail] = useDetailRender({
formType: 'element',
data: userData.value,
schemas: formSchemas,
layout: {
props: { column: 2, border: true, title: '从 Schema 自动生成' },
},
});
// ==================== 示例 5: 自定义组件 ====================
const [CustomComponentDetail] = useDetailRender({
formType: 'element',
data: userData.value,
columns: [
{ prop: 'name', label: '姓名' },
{ prop: 'department', label: '部门' },
{ prop: 'position', label: '职位' },
{ prop: 'email', label: '邮箱' },
],
layout: {
component: CustomLayout,
props: { title: '自定义卡片风格' },
},
item: {
component: CustomItem,
},
});
// ==================== 示例 6: 动态数据更新 ====================
const dynamicData = ref({
name: '初始用户',
count: 0,
lastUpdate: new Date().toLocaleTimeString(),
});
const [DynamicDetail, dynamicApi] = useDetailRender({
formType: 'element',
data: dynamicData,
columns: [
{ prop: 'name', label: '用户名' },
{ prop: 'count', label: '更新次数' },
{ prop: 'lastUpdate', label: '最后更新' },
],
layout: {
props: { column: 3, border: true },
},
});
function updateDynamicData() {
const newData = {
name: `用户_${Math.random().toString(36).slice(2, 8)}`,
count: dynamicData.value.count + 1,
lastUpdate: new Date().toLocaleTimeString(),
};
dynamicApi.setData(newData);
dynamicData.value = newData;
}
// ==================== 示例 7: 多条数据循环 ====================
const userList = ref([
{ id: 1, name: '张三', age: 28, department: '技术部', status: 'active' },
{ id: 2, name: '李四', age: 32, department: '产品部', status: 'active' },
{ id: 3, name: '王五', age: 25, department: '设计部', status: 'inactive' },
]);
const [ListItemDetail] = useDetailRender({
formType: 'element',
data: {},
columns: [
{ prop: 'name', label: '姓名' },
{ prop: 'age', label: '年龄' },
{ prop: 'department', label: '部门' },
{
prop: 'status',
label: '状态',
render: v => (
<ElTag size="small" type={v === 'active' ? 'success' : 'info'}>
{v === 'active' ? '在职' : '离职'}
</ElTag>
),
},
],
layout: {
props: { column: 4, border: true, size: 'small' },
},
});
// ==================== 示例 8: Mobile 模式 ====================
const [MobileDetail] = useDetailRender({
formType: 'vant',
data: userData.value,
columns: [
{ prop: 'name', label: '姓名' },
{ prop: 'age', label: '年龄', formatter: v => `${v}` },
{ prop: 'department', label: '部门' },
{ prop: 'phone', label: '电话' },
{ prop: 'email', label: '邮箱' },
],
layout: {
props: { title: '基本信息', border: true },
},
});
// ==================== 示例 9: 分组模式 ====================
const assetData = ref({
// 基本信息
assetName: '生产设备A',
assetType: '机械设备',
profitCenter: '利润中心001',
assetProperty: '固定资产',
assetCode: 'ASSET-2024-001',
measureUnit: '台',
assetQty: 5,
unitLabel: '计量单位',
modelSpec: 'XYZ-1000',
enterpriseCode: 'ENT-001',
barcode: '6901234567890',
supplier: '供应商A',
manufacturer: '生产厂家B',
productionCountry: '中国',
factoryNo: 'FN-2024-001',
factoryDate: '2024-01-15',
// 使用管理信息
manageDept: '设备管理部',
custodian: '张三',
useDept: '生产一车间',
user: '李四',
useStatus: '在用',
region: '华东区',
location: '厂房A-3楼',
// 技术信息
techParam: '功率: 100kW',
paramUnit: 'kW',
designCapacity: '1000件/天',
capacityUnit: '件/天',
techStatus: '良好',
economicLife: '10年',
// 核算信息
costCenter: 'CC-001',
plateModule: '模块A',
assetCategory: '机械设备',
capitalSource: '自有资金',
capitalChannel: '银行贷款',
economicUse: '生产用',
functionRange: '主要生产',
projectCode: 'PRJ-2024-001',
subjectCategory: '固定资产',
depreciationSubject: '累计折旧',
depreciationCostCenter: 'DCC-001',
impairmentSubject: '资产减值',
depreciationExpenseSubject: '折旧费用',
depreciationCostSubject: '折旧成本',
impairmentLossSubject: '减值损失',
disposalLossSubject: '处置损失',
disposalSubject: '资产处置',
entryStatus: '已入账',
entryDate: '2024-01-20',
foreignCurrencyCode: 'USD',
});
const [GroupedDetail] = useDetailRender({
formType: 'element',
data: assetData.value,
groups: [
{
title: '基本信息',
columns: [
{ prop: 'assetName', label: '资产名称' },
{ prop: 'assetType', label: '资产类别' },
{ prop: 'profitCenter', label: '利润中心' },
{ prop: 'assetProperty', label: '资产属性' },
{ prop: 'assetCode', label: '资产标识' },
{ prop: 'measureUnit', label: '计量模式' },
{ prop: 'assetQty', label: '资产数量' },
{ prop: 'unitLabel', label: '计量单位' },
{ prop: 'modelSpec', label: '规格型号' },
{ prop: 'enterpriseCode', label: '企业自编号' },
{ prop: 'barcode', label: '资产条形号码' },
{ prop: 'supplier', label: '供应商' },
{ prop: 'manufacturer', label: '生产厂家' },
{ prop: 'productionCountry', label: '生产国别' },
{ prop: 'factoryNo', label: '出厂编号' },
{ prop: 'factoryDate', label: '出厂日期' },
],
layout: { column: 4, border: true },
},
{
title: '使用管理信息',
columns: [
{ prop: 'manageDept', label: '企业管理部门' },
{ prop: 'custodian', label: '保管人' },
{ prop: 'useDept', label: '资产使用单位' },
{ prop: 'user', label: '使用人' },
{ prop: 'useStatus', label: '使用情况' },
{ prop: 'region', label: '地理区域' },
{ prop: 'location', label: '所在地点' },
],
layout: { column: 4, border: true },
},
{
title: '技术信息',
columns: [
{ prop: 'techParam', label: '技术参数' },
{ prop: 'paramUnit', label: '参数单位' },
{ prop: 'designCapacity', label: '设计能力' },
{ prop: 'capacityUnit', label: '能力单位' },
{ prop: 'techStatus', label: '技术状态' },
{ prop: 'economicLife', label: '经济适用年限' },
],
layout: { column: 4, border: true },
},
{
title: '核算信息',
columns: [
{ prop: 'costCenter', label: '成本中心' },
{ prop: 'plateModule', label: '所属板块' },
{ prop: 'assetCategory', label: '产权归属' },
{ prop: 'capitalSource', label: '资金来源' },
{ prop: 'capitalChannel', label: '资金渠道' },
{ prop: 'economicUse', label: '经济用途' },
{ prop: 'functionRange', label: '功能范围' },
{ prop: 'projectCode', label: '项目编号' },
{ prop: 'subjectCategory', label: '科目分类' },
{ prop: 'depreciationSubject', label: '原值科目' },
{ prop: 'depreciationCostCenter', label: '折旧成本中心' },
{ prop: 'impairmentSubject', label: '减值科目' },
{ prop: 'depreciationExpenseSubject', label: '折旧费用科目' },
{ prop: 'depreciationCostSubject', label: '折旧科目' },
{ prop: 'impairmentLossSubject', label: '减值损失科目' },
{ prop: 'disposalLossSubject', label: '处置损失科目' },
{ prop: 'disposalSubject', label: '处置科目' },
{ prop: 'entryStatus', label: '入账状态' },
{ prop: 'entryDate', label: '入账日期' },
{ prop: 'foreignCurrencyCode', label: '外币编号' },
],
layout: { column: 4, border: true },
},
],
});
// ==================== 示例 10: 分组 + 自定义渲染 ====================
const attachmentData = ref({
name: '合同文档',
type: '采购合同',
attachments: [
{ id: 1, type: '电子合同', name: '宣传文案.txt', status: '已确认' },
{ id: 2, type: '印章合同', name: '设计稿件.zip', status: '待审核' },
{ id: 3, type: 'EOA签批', name: '我是默认状态的附件.pdf', status: '已完成' },
],
});
const [GroupedWithCustomRender] = useDetailRender({
formType: 'element',
data: attachmentData.value,
groups: [
{
title: '文档信息',
columns: [
{ prop: 'name', label: '文档名称' },
{ prop: 'type', label: '文档类型' },
],
layout: { column: 2, border: true },
},
{
title: '附件信息',
columns: [],
render: data => (
<table class="attachment-table">
<thead>
<tr>
<th>序号</th>
<th>附件类型</th>
<th>附件内容</th>
<th>状态</th>
</tr>
</thead>
<tbody>
{(data.attachments as any[]).map((item, index) => (
<tr key={item.id}>
<td>{String(index + 1).padStart(2, '0')}</td>
<td>{item.type}</td>
<td>{item.name}</td>
<td>
<ElTag size="small" type={item.status === '已完成' ? 'success' : 'info'}>
{item.status}
</ElTag>
</td>
</tr>
))}
</tbody>
</table>
),
},
],
});
</script>
<template>
<div class="playground">
<h1>useDetailRender Playground</h1>
<!-- 示例 1: 基础用法 -->
<ElCard class="section">
<template #header>
<div class="card-header">
<span>示例 1: 基础用法</span>
<span class="code-toggle" :class="{ active: codeVisible.example1 }" @click="toggleCode('example1')">
<CodeIcon />
</span>
</div>
</template>
<BasicDetail />
<ElCollapseTransition>
<pre v-show="codeVisible.example1" class="source-code">{{ sourceCode.example1 }}</pre>
</ElCollapseTransition>
</ElCard>
<ElDivider />
<!-- 示例 2: 不同布局样式 -->
<ElCard class="section">
<template #header>
<div class="card-header">
<span>示例 2: 不同布局样式</span>
<span class="code-toggle" :class="{ active: codeVisible.example2 }" @click="toggleCode('example2')">
<CodeIcon />
</span>
</div>
</template>
<h4>2.1 垂直布局 (direction: vertical)</h4>
<VerticalDetail />
<ElDivider />
<h4>2.2 无边框 + 2</h4>
<NoBorderDetail />
<ElDivider />
<h4>2.3 单列布局 + 大尺寸</h4>
<SingleColumnDetail />
<ElDivider />
<h4>2.4 小尺寸</h4>
<SmallDetail />
<ElCollapseTransition>
<pre v-show="codeVisible.example2" class="source-code">{{ sourceCode.example2 }}</pre>
</ElCollapseTransition>
</ElCard>
<ElDivider />
<!-- 示例 3: 自定义渲染 -->
<ElCard class="section">
<template #header>
<div class="card-header">
<span>示例 3: 自定义渲染 (render / formatter / emptyText)</span>
<span class="code-toggle" :class="{ active: codeVisible.example3 }" @click="toggleCode('example3')">
<CodeIcon />
</span>
</div>
</template>
<CustomRenderDetail />
<ElCollapseTransition>
<pre v-show="codeVisible.example3" class="source-code">{{ sourceCode.example3 }}</pre>
</ElCollapseTransition>
</ElCard>
<ElDivider />
<!-- 示例 4: 复用 Schema -->
<ElCard class="section">
<template #header>
<div class="card-header">
<span>示例 4: 复用 FormSchema 自动生成</span>
<span class="code-toggle" :class="{ active: codeVisible.example4 }" @click="toggleCode('example4')">
<CodeIcon />
</span>
</div>
</template>
<p class="tip">
从表单的 LowCodeFormSchemaItem[] 自动提取 labelhidden 的字段会被过滤
</p>
<SchemaDetail />
<ElCollapseTransition>
<pre v-show="codeVisible.example4" class="source-code">{{ sourceCode.example4 }}</pre>
</ElCollapseTransition>
</ElCard>
<ElDivider />
<!-- 示例 5: 自定义组件 -->
<ElCard class="section">
<template #header>
<div class="card-header">
<span>示例 5: 自定义 Layout Item 组件</span>
<span class="code-toggle" :class="{ active: codeVisible.example5 }" @click="toggleCode('example5')">
<CodeIcon />
</span>
</div>
</template>
<p class="tip">
通过 layout.component item.component 完全自定义渲染
</p>
<CustomComponentDetail />
<ElCollapseTransition>
<pre v-show="codeVisible.example5" class="source-code">{{ sourceCode.example5 }}</pre>
</ElCollapseTransition>
</ElCard>
<ElDivider />
<!-- 示例 6: 动态数据更新 -->
<ElCard class="section">
<template #header>
<div class="card-header">
<span>示例 6: 动态数据更新</span>
<div class="card-header-actions">
<ElButton type="primary" size="small" @click="updateDynamicData">
更新数据 (setData + refresh)
</ElButton>
<span class="code-toggle" :class="{ active: codeVisible.example6 }" @click="toggleCode('example6')">
<CodeIcon />
</span>
</div>
</div>
</template>
<DynamicDetail />
<ElCollapseTransition>
<pre v-show="codeVisible.example6" class="source-code">{{ sourceCode.example6 }}</pre>
</ElCollapseTransition>
</ElCard>
<ElDivider />
<!-- 示例 7: 多条数据循环 -->
<ElCard class="section">
<template #header>
<div class="card-header">
<span>示例 7: 多条数据循环 (v-for + :data)</span>
<span class="code-toggle" :class="{ active: codeVisible.example7 }" @click="toggleCode('example7')">
<CodeIcon />
</span>
</div>
</template>
<div class="list-container">
<div v-for="item in userList" :key="item.id" class="list-item">
<ListItemDetail :data="item" />
</div>
</div>
<ElCollapseTransition>
<pre v-show="codeVisible.example7" class="source-code">{{ sourceCode.example7 }}</pre>
</ElCollapseTransition>
</ElCard>
<ElDivider />
<!-- 示例 8: Mobile 模式 -->
<ElCard class="section">
<template #header>
<div class="card-header">
<span>示例 8: Mobile 模式 (Vant 风格)</span>
<span class="code-toggle" :class="{ active: codeVisible.example8 }" @click="toggleCode('example8')">
<CodeIcon />
</span>
</div>
</template>
<div class="mobile-preview">
<MobileDetail />
</div>
<ElCollapseTransition>
<pre v-show="codeVisible.example8" class="source-code">{{ sourceCode.example8 }}</pre>
</ElCollapseTransition>
</ElCard>
<ElDivider />
<!-- 示例 9: 分组模式 -->
<ElCard class="section">
<template #header>
<div class="card-header">
<span>示例 9: 分组模式 (groups)</span>
<span class="code-toggle" :class="{ active: codeVisible.example9 }" @click="toggleCode('example9')">
<CodeIcon />
</span>
</div>
</template>
<p class="tip">
使用 groups 配置实现分组展示每个分组可以有独立的 layout 配置
</p>
<GroupedDetail />
<ElCollapseTransition>
<pre v-show="codeVisible.example9" class="source-code">{{ sourceCode.example9 }}</pre>
</ElCollapseTransition>
</ElCard>
<ElDivider />
<!-- 示例 10: 分组 + 自定义渲染 -->
<ElCard class="section">
<template #header>
<div class="card-header">
<span>示例 10: 分组 + 自定义渲染 (render)</span>
<span class="code-toggle" :class="{ active: codeVisible.example10 }" @click="toggleCode('example10')">
<CodeIcon />
</span>
</div>
</template>
<p class="tip">
分组可以通过 render 函数完全自定义渲染适用于表格等特殊布局
</p>
<GroupedWithCustomRender />
<ElCollapseTransition>
<pre v-show="codeVisible.example10" class="source-code">{{ sourceCode.example10 }}</pre>
</ElCollapseTransition>
</ElCard>
</div>
</template>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background-color: #f5f7fa;
padding: 20px;
}
.playground {
max-width: 1200px;
margin: 0 auto;
}
h1 {
text-align: center;
margin-bottom: 24px;
color: #303133;
}
h4 {
margin: 16px 0 12px;
color: #606266;
font-weight: 500;
}
.section {
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-header-actions {
display: flex;
align-items: center;
gap: 12px;
}
.tip {
margin-bottom: 12px;
padding: 8px 12px;
background: #ecf5ff;
border-radius: 4px;
color: #409eff;
font-size: 13px;
}
.mobile-preview {
max-width: 375px;
margin: 0 auto;
background: #fff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.list-container {
display: flex;
flex-direction: column;
gap: 16px;
}
.list-item {
padding: 8px;
background: #fafafa;
border-radius: 4px;
}
.attachment-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.attachment-table th,
.attachment-table td {
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid #ebeef5;
}
.attachment-table th {
background-color: #fafafa;
font-weight: 500;
color: #606266;
}
.attachment-table tbody tr:hover {
background-color: #f5f7fa;
}
.code-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 4px;
border-radius: 4px;
color: #909399;
transition: all 0.2s;
}
.code-toggle:hover {
color: #409eff;
background-color: #ecf5ff;
}
.code-toggle.active {
color: #409eff;
background-color: #ecf5ff;
}
.source-code {
margin-top: 16px;
padding: 16px;
background-color: #1e1e1e;
border-radius: 8px;
color: #d4d4d4;
font-family: 'Fira Code', 'Monaco', 'Consolas', monospace;
font-size: 13px;
line-height: 1.6;
overflow-x: auto;
white-space: pre;
}
</style>