Vue 项目中最常用的 10 个组件封装,全是你在真实业务中天天用到的,不带虚的,直接上代码 + 暴躁讲解!
🧠 一、为什么你要封装组件?
因为你不是写一次性页面的程序员!
你写的项目要维护、要复用、要团队协作,别每次都要从头写一遍 el-button 或 input。
封装的目的就是:
✅ 提高复用率✅ 统一风格和逻辑✅ 降低耦合度✅ 装逼加分
📦 二、最常用 Vue 组件清单(真实业务高频使用)
序号组件名称使用场景1BaseButton.vue所有按钮统一风格2BaseInput.vue表单输入框封装3BaseSelect.vue下拉选择器4BaseTable.vue数据表格展示5BaseModal.vue弹窗通用组件6BaseForm.vue表单统一布局与校验7BasePagination.vue分页组件8BaseBreadcrumb.vue面包屑导航9BaseCard.vue内容区块容器10BaseSkeleton.vue页面骨架屏
🔥 三、每个组件都来一波暴躁版封装(Vue3 + Composition API)
1. ✅ BaseButton.vue(统一按钮样式 & loading)
defineProps({
text: String,
type: { type: String, default: 'default' }, // default / primary / danger 等
loading: Boolean,
disabled: Boolean
});
.base-button {
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
.base-button--primary {
background-color: #409EFF;
color: white;
}
.base-button--danger {
background-color: #f56c6c;
color: white;
}
💬 老子一句话:按钮你要是每次都写 v-loading,你是想累死吗?
2. ✅ BaseInput.vue(表单输入统一处理)
:type="type"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
:placeholder="placeholder"
/>
defineProps({
modelValue: [String, Number],
placeholder: String,
label: String,
type: { type: String, default: 'text' }
});
defineEmits(['update:modelValue']);
.base-input input {
width: 100%;
padding: 8px;
margin-top: 4px;
}
💬 老子一句话:输入框你不封装,你是想让表单变成屎山吗?
3. ✅ BaseSelect.vue(下拉选择器统一)
{{ item.label }}
defineProps({
modelValue: [String, Number],
options: {
type: Array,
required: true,
default: () => []
}
});
defineEmits(['update:modelValue']);
💬 老子一句话:选个值你都写不好,你是想气死产品经理吗?
4. ✅ BaseTable.vue(数据表格统一结构)
| {{ col.label }} |
|---|
|
{{ col.render(row) }} {{ row[col.prop] }} |
defineProps({
data: { type: Array, required: true },
columns: { type: Array, required: true }
});
.base-table {
width: 100%;
border-collapse: collapse;
}
.base-table th,
.base-table td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
💬 老子一句话:表格你要是每次都重写
5. ✅ BaseModal.vue(弹窗统一)
import { defineProps, defineEmits } from 'vue';
import BaseButton from './BaseButton.vue';
const props = defineProps({
visible: Boolean,
title: String
});
const emit = defineEmits(['update:visible', 'submit']);
function close() {
emit('update:visible', false);
}
function handleMaskClick() {
if (maskClosable) close();
}
💬 老子一句话:弹窗你不封装,你是想让页面炸了?
6. ✅ BaseForm.vue(表单统一结构)
defineEmits(['submit', 'cancel']);
💬 老子一句话:表单你不统一结构,你是想让你的代码像厕所一样乱?
7. ✅ BasePagination.vue(分页组件)
第 {{ currentPage }} 页 / 共 {{ totalPages }} 页
import { defineProps, defineEmits } from 'vue';
const props = defineProps({
currentPage: { type: Number, required: true },
totalPages: { type: Number, required: true }
});
const emit = defineEmits(['update:currentPage']);
function prev() {
if (props.currentPage > 1) {
emit('update:currentPage', props.currentPage - 1);
}
}
function next() {
if (props.currentPage < props.totalPages) {
emit('update:currentPage', props.currentPage + 1);
}
}
💬 老子一句话:分页你不封装,你是想把产品经理干哭吗?
8. ✅ BaseBreadcrumb.vue(面包屑导航)
defineProps({
items: { type: Array, required: true }
});
💬 老子一句话:没有面包屑的系统,就像没有地图的迷宫。
9. ✅ BaseCard.vue(内容卡片)
{{ title }}
defineProps({
title: String
});
💬 老子一句话:内容区域不封装成 Card,你是想让 UI 看着难受吗?
10. ✅ BaseSkeleton.vue(骨架屏)
defineProps({
rows: { type: Number, default: 5 }
});
.base-skeleton {
padding: 16px;
}
.skeleton-row {
height: 20px;
background: #eee;
margin: 10px 0;
animation: pulse 1.5s infinite ease-in-out;
}
@keyframes pulse {
0% { opacity: 0.4; }
50% { opacity: 1; }
100% { opacity: 0.4; }
}
💬 老子一句话:数据没回来前你不给用户看点东西,用户以为你网站挂了。
🧰 四、如何组织这些组件?(老子教你目录结构)
src/
├── components/
│ ├── BaseButton.vue
│ ├── BaseInput.vue
│ ├── BaseSelect.vue
│ ├── BaseTable.vue
│ ├── BaseModal.vue
│ ├── BaseForm.vue
│ ├── BasePagination.vue
│ ├── BaseBreadcrumb.vue
│ ├── BaseCard.vue
│ └── BaseSkeleton.vue
└── App.vue
💬 老子一句话:组件不归类,你是想让新人翻文件翻到吐血吗?
🎁 五、老子送你一个自动注册全局组件的方法(省得你一个一个 import)
// utils/registerComponents.js
import BaseButton from '@/components/BaseButton.vue';
import BaseInput from '@/components/BaseInput.vue';
import BaseSelect from '@/components/BaseSelect.vue';
import BaseTable from '@/components/BaseTable.vue';
import BaseModal from '@/components/BaseModal.vue';
import BaseForm from '@/components/BaseForm.vue';
import BasePagination from '@/components/BasePagination.vue';
import BaseBreadcrumb from '@/components/BaseBreadcrumb.vue';
import BaseCard from '@/components/BaseCard.vue';
import BaseSkeleton from '@/components/BaseSkeleton.vue';
export default {
install: (app) => {
app.component('BaseButton', BaseButton);
app.component('BaseInput', BaseInput);
app.component('BaseSelect', BaseSelect);
app.component('BaseTable', BaseTable);
app.component('BaseModal', BaseModal);
app.component('BaseForm', BaseForm);
app.component('BasePagination', BasePagination);
app.component('BaseBreadcrumb', BaseBreadcrumb);
app.component('BaseCard', BaseCard);
app.component('BaseSkeleton', BaseSkeleton);
}
};
然后在 main.js 中注册:
import { createApp } from 'vue';
import App from './App.vue';
import BaseComponents from './utils/registerComponents';
createApp(App).use(BaseComponents).mount('#app');
💬 老子一句话:全局注册都不搞,你是想写一辈子 import 吗?
🧠 六、老子总结一句话:
这 10 个组件你要是不会封装,你就别写前端了。
再给你写一个完整的 BaseTable + BasePagination 联动示例,让你在 Vue3 项目中直接复制粘贴就能用!
🧱 功能目标:
使用 BaseTable.vue 展示数据使用 BasePagination.vue 控制分页两个组件通过父组件控制联动支持动态请求数据、翻页加载
📦 目录结构(建议这样组织)
src/
├── components/
│ ├── BaseTable.vue
│ └── BasePagination.vue
└── views/
└── UserListView.vue
✅ 组件一:BaseTable.vue(表格展示)
| {{ col.label }} |
|---|
|
{{ col.render(row) }} {{ row[col.prop] }} |
defineProps({
data: { type: Array, required: true },
columns: { type: Array, required: true }
});
.base-table {
width: 100%;
border-collapse: collapse;
}
.base-table th,
.base-table td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
✅ 组件二:BasePagination.vue(分页器)
第 {{ currentPage }} 页 / 共 {{ totalPages }} 页
import { defineProps, defineEmits } from 'vue';
const props = defineProps({
currentPage: { type: Number, required: true },
totalPages: { type: Number, required: true }
});
const emit = defineEmits(['update:currentPage']);
function prev() {
if (props.currentPage > 1) {
emit('update:currentPage', props.currentPage - 1);
}
}
function next() {
if (props.currentPage < props.totalPages) {
emit('update:currentPage', props.currentPage + 1);
}
}
.base-pagination {
margin-top: 16px;
display: flex;
align-items: center;
gap: 10px;
}
🎯 示例页面:UserListView.vue(使用两个组件联动)
用户列表
:current-page="currentPage" :total-pages="totalPages" @update:currentPage="handlePageChange" />
import { ref, computed, onMounted } from 'vue';
import BaseTable from '@/components/BaseTable.vue';
import BasePagination from '@/components/BasePagination.vue';
// 当前页码
const currentPage = ref(1);
// 每页显示数量
const pageSize = 10;
// 假设这是从接口获取的总数据量
const totalUsers = 55;
// 计算总页数
const totalPages = computed(() => Math.ceil(totalUsers / pageSize));
// 用户数据
const users = ref([]);
// 列配置
const columns = [
{ label: 'ID', prop: 'id' },
{ label: '用户名', prop: 'name' },
{ label: '邮箱', prop: 'email' },
{
label: '操作',
render: (row) => (
)
}
];
// 模拟请求数据
function fetchData(page = 1) {
// 实际开发中替换为 axios 请求
setTimeout(() => {
const start = (page - 1) * pageSize;
const end = start + pageSize;
const mockData = [];
for (let i = start; i < end && i < totalUsers; i++) {
mockData.push({
id: i + 1,
name: `用户${i + 1}`,
email: `user${i + 1}@example.com`
});
}
users.value = mockData;
}, 500);
}
// 页面加载时请求第一页数据
onMounted(() => {
fetchData(currentPage.value);
});
// 处理页码变化
function handlePageChange(newPage) {
currentPage.value = newPage;
fetchData(newPage);
}
h2 {
margin-bottom: 16px;
}
💥 老子一句话总结:
你要是还不会这么联动,你是真不想学前端了!
这个组合:
✅ 结构清晰,复用性强✅ 数据隔离良好,逻辑简单✅ 可扩展性高(支持自定义列渲染)✅ 可替换为真实 API 接口(如 axios.get('/api/users?page=1'))
🧠 老子送你一句灵魂拷问:
你说你只会 copy 官方文档?那你什么时候能写出自己的组件库?
🎁 老子再送你一个优化点:
支持 v-model 控制当前页码(更现代写法)
然后在 BasePagination.vue 中改成:
const props = defineProps({
currentPage: { type: Number, required: true },
totalPages: { type: Number, required: true }
});
const emit = defineEmits(['update:currentPage']);
function prev() {
if (props.currentPage > 1) {
emit('update:currentPage', props.currentPage - 1);
}
}
function next() {
if (props.currentPage < props.totalPages) {
emit('update:currentPage', props.currentPage + 1);
}
}
🔥 老子最后总结一句话:
BaseTable + BasePagination 是 Vue 后台系统的黄金搭档,不会这两个你就别说自己会写前端了。
要不要我继续给你写一个带搜索过滤、排序、导出功能的增强版 AdvancedTable.vue?
这里是暴躁前端老曹,下篇文章我们再见!
😎💥