1170 lines
31 KiB
Vue
1170 lines
31 KiB
Vue
<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[] 自动提取 label,hidden 的字段会被过滤
|
||
</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>
|