5.4 KiB
Component Guidelines
How components are built in this project.
Overview
This project uses Vue 3 with Element Plus components. Components follow a consistent pattern for props, slots, and composition.
Element Plus el-table-v2 Usage
IMPORTANT: el-table-v2 has a different API than el-table. Key differences:
Auto-resizing with ElAutoResizer
el-table-v2 does NOT have an autosize prop. Use ElAutoResizer wrapper instead:
<el-auto-resizer>
<template #default="{ height, width }">
<el-table-v2
:columns="columns"
:data="data"
:width="width"
:height="height"
:row-height="50"
:header-height="50"
/>
</template>
</el-auto-resizer>
Note: Parent container of ElAutoResizer must have a fixed height (e.g., height: 100% or explicit pixel value).
el-table-v2 Required Props
el-table-v2 requires both width and height as mandatory props - they cannot be omitted.
Slot-to-CellRenderer Conversion
el-table-v2 uses cellRenderer functions instead of Vue slots. To render parent slots:
import { useSlots, renderSlot } from 'vue';
const slots = useSlots();
// In column definition:
col.cellRenderer = ({ cellData, rowData }) => {
if (slots[slotName]) {
return renderSlot(slots, slotName, { row: rowData });
}
return h('span', {}, formattedValue);
};
Column Flex Distribution
For auto-distributed column widths (no explicit width), keep a minimum basis and
use flex factors greater than 1 so columns can absorb remaining row space after
scrollbar width and fixed columns are accounted for:
const col = {
key: 'code',
title: 'Name',
dataKey: 'code',
width: 120, // minimum width required
flexGrow: 1.2, // expands to fill available space
flexShrink: 1.2, // shrinks proportionally when space is tight
align: 'center',
};
Import Path Conventions
Correct Import Patterns
// ✅ CORRECT: Use relative paths for internal modules
import { clearAndAssign, deepCopy } from "../util/objectUtil";
import { ElAutoResizer, ElTableV2 } from "element-plus";
// ❌ WRONG: noob-mengyxu/utils does not exist
import { clearAndAssign } from "noob-mengyxu/utils";
Rule: For utility functions within the project, always use relative paths (../util/, ./). The noob-mengyxu namespace is only for exporting packages.
Component Structure
<template>
<div class="component-name">
<!-- Markup -->
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
// Props definition
interface Props {
data?: any;
columns?: TableColumn[];
}
const prop = withDefaults(defineProps<Props>(), {
data: () => [],
columns: () => [],
});
const emit = defineEmits(['query', 'change']);
</script>
<style lang="scss" scoped>
/* Scoped styles */
</style>
Props Conventions
- Use
withDefaults(defineProps<Props>())for optional props with defaults - Always provide default values for array/object props
- Use TypeScript interfaces for complex prop types
Common Mistakes
1. Timestamp Formatting Assumptions
The formatStamp function expects Unix timestamps in seconds (not milliseconds):
const formatStamp = (value: any) => {
const date = new Date(value * 1000); // expects seconds
// ...
};
When generating test data, use Math.floor(Date.now() / 1000) or Date.now() / 1000.
2. Element Plus Version Mismatch
Element Plus el-table (v1) and el-table-v2 (virtualized) have completely different APIs:
- v1: uses
propfor columns, slots for cell rendering - v2: uses
columnsprop,cellRendererfunctions, separateElAutoResizer
3. Forgetting Required Props
When using new Element Plus components, verify required props - they will cause runtime errors if omitted.
4. Vue 3 Template Ref Auto-Unwrap Gotchas
Vue 3 <script setup> auto-unwraps refs in templates, but with important limitations:
Works - Top-level refs:
const count = ref(0);
// Template: {{ count }} ✓ (auto-unwrapped)
Broken - Refs nested in objects:
const virtualizer = useVirtualRows(...);
// Template: {{ virtualizer.visibleRows }} ✗ (NOT auto-unwrapped)
Fixed - Destructure refs for template use:
const virtualizer = useVirtualRows(...);
const { visibleRows, totalHeight } = virtualizer;
// Template: {{ visibleRows }} ✓ (auto-unwrapped top-level ref)
TypeScript also fails when refs are nested in objects - TypeScript sees virtualizer.visibleRows as type ComputedRef | true | VirtualRow[] instead of VirtualRow[].
Rule: Always destructure refs from composable return objects when they will be used directly in templates.
5. Virtual Scrolling Overscan with Small Datasets
Virtual scrolling with overscan may render ALL rows even when scrolled if:
- Total row heights < viewport height + (overscan × average row height)
- Dataset is small (e.g., 10 rows with 60px each = 600px total)
With overscan=5 and 10 rows (600px total) and viewport 300px:
- Overscan renders rows -5 to +5 from viewport edge
- This covers all 10 rows regardless of scroll position
This is NOT a bug - it's expected behavior. Virtual scrolling only culls rows when the dataset exceeds viewport + overscan capacity.
Accessibility
- Use semantic HTML elements
- Ensure keyboard navigation works
- Add ARIA labels where necessary