18 KiB
Base Components
Base Vue 3 components built on Element Plus.
Overview
The packages/base/ directory provides foundational Vue 3 components that wrap Element Plus components with theme integration, i18n support, and consistent prop patterns.
Architecture
packages/base/
├── index.ts # Exports all base components
├── item/ # Basic UI elements
│ ├── tag.vue
│ ├── button.vue
│ ├── select.vue
│ ├── input.vue
│ ├── datetime.vue
│ ├── light-box.vue
│ ├── buttonWithTooltip.vue
│ ├── confirmCancel.vue
│ ├── tzDatePicker.vue
│ ├── tzDateTime.vue
│ └── ws-monitor-toggle.vue
└── data/ # Data-driven components
├── search-row.vue
├── list-table.vue
├── listTableDialog.vue
├── infomation.vue
├── modify-form.vue
├── descriptions.vue
└── table-action.vue
Item Components
NoobTag
Wraps el-tag with theme integration.
File: packages/base/item/tag.vue
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
type |
string |
null |
Element Plus tag type |
color |
string |
null |
Custom color |
closable |
boolean |
false |
Show close button |
round |
boolean |
false |
Round style |
effect |
string |
'light' |
Tag effect (light/dark/plain) |
Usage:
<NoobTag type="success">Active</NoobTag>
<NoobTag color="#387dff">Custom</NoobTag>
<NoobTag closable round>Removable</NoobTag>
NoobButton
Wraps el-button with theme integration and slot content detection.
File: packages/base/item/button.vue
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
type |
string |
null |
Button type (primary/success/warning/danger/info) |
icon |
string |
null |
Icon name |
disabled |
boolean |
false |
Disabled state |
plain |
boolean |
false |
Plain style |
round |
boolean |
false |
Round style |
circle |
boolean |
false |
Circle style (icon-only) |
loading |
boolean |
false |
Loading state |
text |
boolean |
false |
Text button |
link |
boolean |
false |
Link button |
Usage:
<NoobButton type="primary" icon="Plus">Add</NoobButton>
<NoobButton type="danger" icon="Delete" circle />
<NoobButton loading @click="handleSave">Saving...</NoobButton>
Note: Automatically detects if slot content exists before rendering, handling empty slots in circle buttons.
NoobSelect
Wraps el-select with Vuex dict integration and width management.
File: packages/base/item/select.vue
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue |
any |
- |
v-model value |
placeholder |
string |
i18n: rule.pleaseSelect |
Placeholder text |
disabled |
boolean |
false |
Disabled state |
clearable |
boolean |
true |
Show clear button |
filterable |
boolean |
true |
Enable search |
full |
boolean |
false |
100% width |
width |
number |
- |
Fixed width in px |
dict |
string |
- |
Vuex dict key for options |
stateProp |
string |
- |
Vuex state property for options |
maxValue |
number |
- |
Generate 1-N options |
valueKey |
string |
'key' |
Option value key |
labelKey |
string |
'value' |
Option label key |
remote |
boolean |
false |
Enable remote search |
remoteMethod |
function |
null |
Remote search function |
Usage:
<!-- Dict-based options -->
<NoobSelect v-model="form.status" dict="userStatus" />
<!-- Array options from Vuex state -->
<NoobSelect v-model="form.type" stateProp="userTypes" valueKey="id" labelKey="name" />
<!-- Numeric range options -->
<NoobSelect v-model="form.count" :maxValue="10" />
NoobInput
Wraps el-input with width management and label prepend support.
File: packages/base/item/input.vue
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue |
any |
- |
v-model value |
label |
string |
null |
Prepend label |
placeholder |
string |
i18n: rule.pleaseEnter |
Placeholder text |
type |
string |
null |
Input type |
disabled |
boolean |
false |
Disabled state |
clearable |
boolean |
true |
Show clear button |
full |
boolean |
false |
100% width |
width |
number |
- |
Fixed width in px |
Usage:
<NoobInput v-model="form.name" label="Name" />
<NoobInput v-model="form.note" type="textarea" :rows="3" />
<NoobInput v-model="form.search" full placeholder="Search..." />
ConfirmCancel
Confirmation button pair with i18n text.
File: packages/base/item/confirmCancel.vue
Events:
| Event | Payload | Description |
|---|---|---|
confirm |
void |
Confirm button clicked |
cancel |
void |
Cancel button clicked |
Usage:
<ConfirmCancel @confirm="handleConfirm" @cancel="handleCancel" />
ButtonWithTooltip
Wraps NoobButton with tooltip disabled state handling.
File: packages/base/item/buttonWithTooltip.vue
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
tooltip |
string |
- |
Tooltip content |
disabled |
boolean |
- |
Button disabled state |
Usage:
<ButtonWithTooltip tooltip="Requires admin access" :disabled="!isAdmin">
Delete
</ButtonWithTooltip>
WsMonitorToggle
WebSocket subscription monitor with automatic retry logic.
File: packages/base/item/ws-monitor-toggle.vue
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
topics |
string[] |
required | Topics to monitor |
statusEndpoint |
string |
'/public/ws/topics' |
Status check endpoint |
interval |
number |
3000 |
Retry interval (ms) |
maxRetries |
number |
10 |
Max connection attempts |
connect |
object/function |
- |
Subscribe command or function |
disconnect |
object/function |
- |
Unsubscribe command or function |
disabled |
boolean |
false |
Disable toggle |
labelConnected |
string |
'ws.autorefresh.enabled' |
Connected label i18n key |
labelConnecting |
string |
'ws.autorefresh.enabling' |
Connecting label i18n key |
labelDisconnected |
string |
'ws.autorefresh.disabled' |
Disconnected label i18n key |
labelError |
string |
'ws.autorefresh.error' |
Error label i18n key |
Events:
| Event | Payload | Description |
|---|---|---|
status-change |
boolean |
Connection status changed |
error |
Error |
Error occurred |
States: disconnected | connecting | connected | error
Usage:
<WsMonitorToggle
:topics="['user:notifications', 'system:alerts']"
:connect="{ action: 'subscribe', topics: topics }"
:disconnect="{ action: 'unsubscribe', topics: topics }"
@status-change="onStatusChange"
/>
Data Components
SearchRow
Search form row with standard action buttons.
File: packages/base/data/search-row.vue
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
title |
string |
null |
Row title |
fresh |
boolean |
true |
Show refresh button |
add |
boolean |
true |
Show add button |
del |
boolean |
true |
Show delete button |
query |
boolean |
true |
Show query button |
Slots:
| Slot | Description |
|---|---|
left |
Extra buttons on left side |
default |
Search form fields |
Events:
| Event | Description |
|---|---|
query |
Query button clicked |
add |
Add button clicked |
delete |
Delete button clicked |
Usage:
<SearchRow title="User Management" @query="fetchData" @add="openAddDialog">
<template #left>
<NoobButton @click="exportData">Export</NoobButton>
</template>
<NoobInput v-model="searchForm.name" label="Name" />
<NoobSelect v-model="searchForm.status" dict="userStatus" />
</SearchRow>
ListTable
Data table with pagination and formatting support.
File: packages/base/data/list-table.vue
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
data |
any[]/PageResponse |
[] |
Table data or paginated response |
props |
TableColumn[] |
[] |
Column definitions |
page |
boolean |
false |
Enable pagination |
height |
number/string |
- |
Table height |
maxHeight |
number/string |
- |
Table max height |
example |
object |
{} |
Pagination state ({ page, size }) |
rowKey |
string |
- |
Row key for tree/lazy |
selectKey |
string |
- |
Key for auto-selection |
treeProps |
object |
- |
Tree table configuration |
lazy |
boolean |
false |
Lazy loading |
border |
boolean |
false |
Show border |
TableColumn Interface:
interface TableColumn {
code: string; // Property name
name?: string; // Column label (used if i18n not set)
i18n?: string; // i18n key for label
type?: string; // 'selection' | 'index' | 'expand'
width?: number; // Column width
fixed?: string; // 'left' | 'right'
align?: string; // 'left' | 'center' | 'right'
slot?: boolean; // Enable slot rendering
dict?: string; // Dict key for value mapping
timestamp?: boolean; // Format as timestamp
filesize?: boolean; // Format as file size
}
Events:
| Event | Payload | Description |
|---|---|---|
query |
void |
Refresh data |
selection-change |
selection[] |
Selection changed |
row-click |
row |
Row clicked |
cell-click |
row, column, cell, event |
Cell clicked |
Usage:
<ListTable
:data="tableData"
:props="columns"
page
:example="pagination"
row-key="id"
@query="fetchData"
@selection-change="onSelect"
>
<template #status="{ row }">
<NoobTag :type="row.active ? 'success' : 'info'">
{{ row.active ? 'Active' : 'Inactive' }}
</NoobTag>
</template>
</ListTable>
<script setup>
const columns = [
{ code: 'id', name: 'ID', width: 80 },
{ code: 'name', name: 'Name' },
{ code: 'status', slot: true },
{ code: 'createdAt', timestamp: true },
{ code: 'size', filesize: true }
];
</script>
ListTableDialog
List table with multi-select in a dialog.
File: packages/base/data/listTableDialog.vue
Props (from ListTableProps):
| Prop | Type | Default | Description |
|---|---|---|---|
props |
TableColumn[] |
- |
Column definitions |
rowKey |
string |
- |
Row key for selection |
useDicts |
string[] |
- |
Dict keys to load |
initialPage |
number |
1 |
Initial page |
initialPageSize |
number |
- |
Initial page size |
initExample |
object |
- |
Initial query params |
Events:
| Event | Payload | Description |
|---|---|---|
query |
(handler, example) |
Query handler (async) |
confirm |
(handler, rows) |
Confirm handler (async) |
close |
(handler) |
Close handler (async) |
Usage:
<ListTableDialog
:props="columns"
row-key="id"
:useDicts="['userStatus']"
@query="handleQuery"
@confirm="handleConfirm"
@close="closeDialog"
>
<template #name="{ row }">
<strong>{{ row.name }}</strong>
</template>
</ListTableDialog>
ModifyForm
Dynamic form for create/edit operations.
File: packages/base/data/modify-form.vue
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
width |
number |
80 |
Label width |
param |
object |
{} |
Form data |
rules |
object |
{} |
Validation rules |
class |
string |
'' |
Form class |
type |
string |
null |
Form type |
modify |
boolean |
false |
Edit mode (hides noModify fields) |
items |
FormItem[] |
[] |
Form field definitions |
confirm |
string/boolean |
undefined |
Confirm button config |
cancel |
string/boolean |
undefined |
Cancel button config |
FormItem Interface:
interface FormItem {
code: string; // Field key
name?: string; // Label (used if i18n not set)
i18n?: string; // i18n key for label
dict?: string; // Dict for select
maxValue?: number; // Max value for number select
date?: boolean; // Render as date picker
formater?: string; // Date format string
slot?: boolean; // Enable slot rendering
type?: string; // Input type (textarea, etc.)
rows?: number; // Textarea rows
disabled?: boolean; // Disabled state
noModify?: boolean; // Hidden in edit mode
readonly?: boolean; // Readonly state
}
Events:
| Event | Description |
|---|---|
confirm |
Form validated and submitted |
cancel |
Cancel clicked |
Exposed Methods:
| Method | Description |
|---|---|
clearValidate |
Clear validation state |
Usage:
<ModifyForm
ref="formRef"
:param="formData"
:items="formFields"
:rules="formRules"
:modify="isEdit"
@confirm="handleSubmit"
@cancel="handleCancel"
/>
<script setup>
const formFields = [
{ code: 'name', name: 'Username', type: 'input' },
{ code: 'status', dict: 'userStatus' },
{ code: 'birthDate', date: true, formater: 'YYYY-MM-DD' },
{ code: 'remark', type: 'textarea', rows: 3 },
{ code: 'slot_field', slot: true }
];
</script>
Descriptions
Key-value display component based on el-descriptions.
File: packages/base/data/descriptions.vue
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
data |
object/array/string |
0 |
Display data |
title |
string |
null |
Descriptions title |
border |
boolean |
true |
Show border |
Usage:
<!-- Object key-value -->
<Descriptions :data="{ name: 'John', age: 30 }" title="User Info" />
<!-- Array of {key, value} -->
<Descriptions :data="[{ key: 'Email', value: 'john@example.com' }]" />
<!-- Single value -->
<Descriptions :data="totalCount" title="Statistics" />
TableAction
Row action buttons (edit/delete/add) with tooltips.
File: packages/base/data/table-action.vue
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
add |
boolean |
false |
Show add button |
modify |
boolean |
true |
Show edit button |
del |
boolean |
true |
Show delete button |
Slots:
| Slot | Description |
|---|---|
left |
Extra content before actions |
default |
Extra buttons after actions |
Events:
| Event | Description |
|---|---|
modify |
Edit button clicked |
del |
Delete button clicked |
add |
Add button clicked |
Usage:
<TableAction @modify="handleEdit" @del="handleDelete" @add="handleAdd">
<template #left>
<NoobButton size="small">Custom</NoobButton>
</template>
</TableAction>
Infomation
Card component for displaying single metrics/stats.
File: packages/base/data/infomation.vue
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
num |
number |
0 |
Numeric value |
icon |
string |
'' |
Element Plus icon name |
title |
string |
'' |
Card title |
center |
boolean |
false |
Center alignment |
Usage:
<Infomation :num="152" icon="User" title="Total Users" />
<Infomation :num="89.5" icon="TrendCharts" title="Conversion Rate" center />
Common Patterns
Theme Integration
All components use Vuex store for theme values:
const { state } = useStore();
// state.style - colors, backgrounds
// state.size - sizes, paddings, fonts
Example in SCSS:
color: v-bind('state.style.color');
background-color: v-bind('state.style.primaryBg');
i18n Integration
Components use vue3-i18n for translations:
const { t } = useI18n();
// t('key') for translation
Common i18n keys:
base.add- Add buttonbase.delete- Delete buttonbase.select- Select/Search buttonbase.confirm- Confirm buttonbase.cancel- Cancel buttonrule.pleaseEnter- Enter placeholderrule.pleaseSelect- Select placeholder
v-model Pattern
Components use Vue 3 defineProps with defineEmits:
const prop = defineProps({
modelValue: null,
});
const emit = defineEmits(["update:modelValue"]);
// Sync internal value to prop
watch(myValue, (n) => emit('update:modelValue', n));
Width Management
Item components support multiple width modes:
// Fixed width from prop
width.value = prop.width + 'px';
// Or from Vuex store
width.value = state.size.searchWidth;
// Full width
&.full { width: 100%; }
Props Definition Pattern
// With defaults
const prop = defineProps({
modelValue: null,
disabled: {
type: Boolean,
default: false,
},
});
// TypeScript interface (preferred for new components)
interface Props {
title: string;
items: TableColumn[];
}
withDefaults(defineProps<Props>(), {
items: () => [],
});
Anti-Patterns
-
Do not access Vuex store directly in templates - Use computed properties
<!-- Bad --> <div>{{ state.style.color }}</div> <!-- Good --> <div :style="{ color: themeColor }">text</div> -
Do not use
anyfor typed props - Define proper interfaces// Bad prop: any; // Good interface TableColumn { code: string; name?: string; } -
Do not mix Element Plus imports - Use the component registry
// Avoid import { ElButton } from 'element-plus'; // Prefer (through noob-mengyxu) import { NoobButton } from 'noob-mengyxu'; -
Avoid prop drilling - Use provide/inject or Vuex for deep component trees
-
Do not forget to emit
update:modelValue- For v-model to work properly