UI 组件库
Vue 生态系统拥有丰富的 UI 组件库,为开发者提供了各种风格和功能的组件选择。本文介绍主流的 Vue 3 UI 组件库及其使用方法。
主流 UI 组件库对比
组件库 | 特点 | 适用场景 | 包大小 | TypeScript |
---|---|---|---|---|
Element Plus | 企业级、功能丰富 | 后台管理系统 | 较大 | ✅ |
Ant Design Vue | 企业级、设计语言完整 | 企业应用 | 较大 | ✅ |
Vuetify | Material Design | 现代 Web 应用 | 较大 | ✅ |
Naive UI | 轻量、现代 | 各种应用 | 中等 | ✅ |
Quasar | 全平台、功能强大 | 跨平台应用 | 大 | ✅ |
Vant | 移动端专用 | 移动端应用 | 小 | ✅ |
PrimeVue | 主题丰富 | 企业应用 | 中等 | ✅ |
Arco Design | 字节跳动出品 | 企业应用 | 中等 | ✅ |
Element Plus
安装和配置
bash
# 安装
npm install element-plus
# 图标库(可选)
npm install @element-plus/icons-vue
完整引入
javascript
// main.js
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
按需引入(推荐)
bash
# 安装自动导入插件
npm install -D unplugin-vue-components unplugin-auto-import
javascript
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
})
基本使用
vue
<template>
<div class="demo-container">
<!-- 按钮 -->
<div class="demo-section">
<h3>按钮</h3>
<el-button>默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
</div>
<!-- 表单 -->
<div class="demo-section">
<h3>表单</h3>
<el-form :model="form" :rules="rules" ref="formRef" label-width="80px">
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
v-model="form.password"
type="password"
placeholder="请输入密码"
show-password
></el-input>
</el-form-item>
<el-form-item label="性别" prop="gender">
<el-radio-group v-model="form.gender">
<el-radio label="male">男</el-radio>
<el-radio label="female">女</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="爱好" prop="hobbies">
<el-checkbox-group v-model="form.hobbies">
<el-checkbox label="reading">阅读</el-checkbox>
<el-checkbox label="music">音乐</el-checkbox>
<el-checkbox label="sports">运动</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 表格 -->
<div class="demo-section">
<h3>表格</h3>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="name" label="姓名" width="120"></el-table-column>
<el-table-column prop="age" label="年龄" width="80"></el-table-column>
<el-table-column prop="email" label="邮箱"></el-table-column>
<el-table-column label="操作" width="150">
<template #default="{ row, $index }">
<el-button size="small" @click="editUser(row)">编辑</el-button>
<el-button size="small" type="danger" @click="deleteUser($index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 对话框 -->
<el-dialog v-model="dialogVisible" title="编辑用户" width="30%">
<el-form :model="editForm" label-width="80px">
<el-form-item label="姓名">
<el-input v-model="editForm.name"></el-input>
</el-form-item>
<el-form-item label="年龄">
<el-input-number v-model="editForm.age" :min="1" :max="120"></el-input-number>
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model="editForm.email"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveUser">保存</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
// 表单数据
const form = reactive({
username: '',
password: '',
gender: '',
hobbies: []
})
// 表单验证规则
const rules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 15, message: '长度在 3 到 15 个字符', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码长度不能少于 6 位', trigger: 'blur' }
],
gender: [
{ required: true, message: '请选择性别', trigger: 'change' }
]
}
const formRef = ref()
// 表格数据
const tableData = ref([
{ name: '张三', age: 25, email: 'zhangsan@example.com' },
{ name: '李四', age: 30, email: 'lisi@example.com' },
{ name: '王五', age: 28, email: 'wangwu@example.com' }
])
// 对话框
const dialogVisible = ref(false)
const editForm = reactive({
name: '',
age: 0,
email: ''
})
let editIndex = -1
// 表单提交
function submitForm() {
formRef.value.validate((valid) => {
if (valid) {
ElMessage.success('提交成功!')
console.log('表单数据:', form)
} else {
ElMessage.error('请检查表单数据')
}
})
}
// 重置表单
function resetForm() {
formRef.value.resetFields()
}
// 编辑用户
function editUser(row) {
editIndex = tableData.value.indexOf(row)
Object.assign(editForm, row)
dialogVisible.value = true
}
// 删除用户
function deleteUser(index) {
ElMessageBox.confirm('确定要删除这个用户吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
tableData.value.splice(index, 1)
ElMessage.success('删除成功')
}).catch(() => {
ElMessage.info('已取消删除')
})
}
// 保存用户
function saveUser() {
if (editIndex >= 0) {
Object.assign(tableData.value[editIndex], editForm)
ElMessage.success('保存成功')
dialogVisible.value = false
}
}
</script>
<style scoped>
.demo-container {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.demo-section {
margin-bottom: 40px;
padding: 20px;
border: 1px solid #ebeef5;
border-radius: 4px;
}
.demo-section h3 {
margin-top: 0;
color: #303133;
}
.el-button {
margin-right: 10px;
margin-bottom: 10px;
}
</style>
主题定制
scss
// styles/element-variables.scss
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
$colors: (
'primary': (
'base': #409eff,
),
'success': (
'base': #67c23a,
),
'warning': (
'base': #e6a23c,
),
'danger': (
'base': #f56c6c,
),
'error': (
'base': #f56c6c,
),
'info': (
'base': #909399,
),
)
);
javascript
// vite.config.js
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
additionalData: `@use "@/styles/element-variables.scss" as *;`,
},
},
},
})
Ant Design Vue
安装和配置
bash
npm install ant-design-vue
javascript
// main.js
import { createApp } from 'vue'
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/antd.css'
import App from './App.vue'
const app = createApp(App)
app.use(Antd)
app.mount('#app')
基本使用
vue
<template>
<div class="antd-demo">
<!-- 布局 -->
<a-layout style="min-height: 100vh">
<a-layout-sider v-model:collapsed="collapsed" collapsible>
<div class="logo" />
<a-menu theme="dark" mode="inline" :default-selected-keys="['1']">
<a-menu-item key="1">
<user-outlined />
<span>用户管理</span>
</a-menu-item>
<a-menu-item key="2">
<video-camera-outlined />
<span>内容管理</span>
</a-menu-item>
<a-menu-item key="3">
<upload-outlined />
<span>文件管理</span>
</a-menu-item>
</a-menu>
</a-layout-sider>
<a-layout>
<a-layout-header style="background: #fff; padding: 0">
<menu-unfold-outlined
v-if="collapsed"
class="trigger"
@click="() => (collapsed = !collapsed)"
/>
<menu-fold-outlined
v-else
class="trigger"
@click="() => (collapsed = !collapsed)"
/>
</a-layout-header>
<a-layout-content style="margin: 16px">
<div style="padding: 24px; background: #fff; min-height: 360px">
<!-- 表格 -->
<a-table
:columns="columns"
:data-source="dataSource"
:pagination="pagination"
:loading="loading"
@change="handleTableChange"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-space>
<a-button type="primary" size="small" @click="edit(record)">
编辑
</a-button>
<a-popconfirm
title="确定要删除吗?"
@confirm="deleteRecord(record.key)"
>
<a-button type="primary" danger size="small">
删除
</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
</div>
</a-layout-content>
</a-layout>
</a-layout>
<!-- 编辑模态框 -->
<a-modal
v-model:visible="modalVisible"
title="编辑用户"
@ok="handleOk"
@cancel="handleCancel"
>
<a-form
:model="formData"
:rules="rules"
ref="formRef"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 18 }"
>
<a-form-item label="姓名" name="name">
<a-input v-model:value="formData.name" />
</a-form-item>
<a-form-item label="年龄" name="age">
<a-input-number v-model:value="formData.age" :min="1" :max="120" />
</a-form-item>
<a-form-item label="邮箱" name="email">
<a-input v-model:value="formData.email" />
</a-form-item>
<a-form-item label="状态" name="status">
<a-select v-model:value="formData.status">
<a-select-option value="active">活跃</a-select-option>
<a-select-option value="inactive">非活跃</a-select-option>
</a-select>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import {
UserOutlined,
VideoCameraOutlined,
UploadOutlined,
MenuUnfoldOutlined,
MenuFoldOutlined,
} from '@ant-design/icons-vue'
import { message } from 'ant-design-vue'
// 侧边栏折叠状态
const collapsed = ref(false)
// 表格配置
const columns = [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
},
{
title: '年龄',
dataIndex: 'age',
key: 'age',
},
{
title: '邮箱',
dataIndex: 'email',
key: 'email',
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
},
{
title: '操作',
key: 'action',
},
]
// 表格数据
const dataSource = ref([])
const loading = ref(false)
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total, range) => `${range[0]}-${range[1]} 共 ${total} 条`,
})
// 模态框
const modalVisible = ref(false)
const formRef = ref()
const formData = reactive({
key: '',
name: '',
age: 0,
email: '',
status: 'active'
})
// 表单验证规则
const rules = {
name: [{ required: true, message: '请输入姓名' }],
age: [{ required: true, message: '请输入年龄' }],
email: [
{ required: true, message: '请输入邮箱' },
{ type: 'email', message: '请输入正确的邮箱格式' }
],
status: [{ required: true, message: '请选择状态' }]
}
// 加载数据
function loadData() {
loading.value = true
// 模拟 API 调用
setTimeout(() => {
dataSource.value = [
{
key: '1',
name: '张三',
age: 25,
email: 'zhangsan@example.com',
status: 'active'
},
{
key: '2',
name: '李四',
age: 30,
email: 'lisi@example.com',
status: 'inactive'
},
{
key: '3',
name: '王五',
age: 28,
email: 'wangwu@example.com',
status: 'active'
}
]
pagination.total = dataSource.value.length
loading.value = false
}, 1000)
}
// 表格变化处理
function handleTableChange(pag, filters, sorter) {
pagination.current = pag.current
pagination.pageSize = pag.pageSize
loadData()
}
// 编辑记录
function edit(record) {
Object.assign(formData, record)
modalVisible.value = true
}
// 删除记录
function deleteRecord(key) {
const index = dataSource.value.findIndex(item => item.key === key)
if (index > -1) {
dataSource.value.splice(index, 1)
message.success('删除成功')
}
}
// 模态框确认
function handleOk() {
formRef.value.validate().then(() => {
const index = dataSource.value.findIndex(item => item.key === formData.key)
if (index > -1) {
Object.assign(dataSource.value[index], formData)
}
modalVisible.value = false
message.success('保存成功')
}).catch(() => {
message.error('请检查表单数据')
})
}
// 模态框取消
function handleCancel() {
modalVisible.value = false
formRef.value.resetFields()
}
onMounted(() => {
loadData()
})
</script>
<style scoped>
.antd-demo {
height: 100vh;
}
.logo {
height: 32px;
margin: 16px;
background: rgba(255, 255, 255, 0.3);
}
.trigger {
font-size: 18px;
line-height: 64px;
padding: 0 24px;
cursor: pointer;
transition: color 0.3s;
}
.trigger:hover {
color: #1890ff;
}
</style>
Naive UI
安装和配置
bash
npm install naive-ui
javascript
// main.js
import { createApp } from 'vue'
import naive from 'naive-ui'
import App from './App.vue'
const app = createApp(App)
app.use(naive)
app.mount('#app')
基本使用
vue
<template>
<n-config-provider :theme="theme">
<div class="naive-demo">
<n-space vertical size="large">
<!-- 主题切换 -->
<n-card title="主题设置">
<n-space>
<n-button @click="toggleTheme">
{{ isDark ? '切换到亮色' : '切换到暗色' }}
</n-button>
</n-space>
</n-card>
<!-- 表单 -->
<n-card title="表单示例">
<n-form
ref="formRef"
:model="model"
:rules="rules"
label-placement="left"
label-width="auto"
>
<n-form-item label="用户名" path="username">
<n-input v-model:value="model.username" placeholder="请输入用户名" />
</n-form-item>
<n-form-item label="密码" path="password">
<n-input
v-model:value="model.password"
type="password"
placeholder="请输入密码"
show-password-on="mousedown"
/>
</n-form-item>
<n-form-item label="年龄" path="age">
<n-input-number v-model:value="model.age" :min="1" :max="120" />
</n-form-item>
<n-form-item label="性别" path="gender">
<n-radio-group v-model:value="model.gender">
<n-radio value="male">男</n-radio>
<n-radio value="female">女</n-radio>
</n-radio-group>
</n-form-item>
<n-form-item>
<n-space>
<n-button type="primary" @click="handleSubmit">
提交
</n-button>
<n-button @click="handleReset">
重置
</n-button>
</n-space>
</n-form-item>
</n-form>
</n-card>
<!-- 数据表格 -->
<n-card title="数据表格">
<n-data-table
:columns="columns"
:data="data"
:pagination="paginationReactive"
:loading="loading"
/>
</n-card>
</n-space>
</div>
</n-config-provider>
</template>
<script setup>
import { ref, reactive, h } from 'vue'
import {
NButton,
NSpace,
NPopconfirm,
useMessage,
useDialog,
darkTheme
} from 'naive-ui'
// 主题
const isDark = ref(false)
const theme = ref(null)
function toggleTheme() {
isDark.value = !isDark.value
theme.value = isDark.value ? darkTheme : null
}
// 消息和对话框
const message = useMessage()
const dialog = useDialog()
// 表单
const formRef = ref()
const model = reactive({
username: '',
password: '',
age: null,
gender: null
})
const rules = {
username: {
required: true,
message: '请输入用户名',
trigger: ['input', 'blur']
},
password: {
required: true,
message: '请输入密码',
trigger: ['input', 'blur']
},
age: {
required: true,
type: 'number',
message: '请输入年龄',
trigger: ['input', 'blur']
},
gender: {
required: true,
message: '请选择性别',
trigger: 'change'
}
}
function handleSubmit() {
formRef.value?.validate((errors) => {
if (!errors) {
message.success('提交成功')
console.log('表单数据:', model)
} else {
message.error('请检查表单数据')
}
})
}
function handleReset() {
formRef.value?.restoreValidation()
Object.assign(model, {
username: '',
password: '',
age: null,
gender: null
})
}
// 表格
const loading = ref(false)
const data = ref([
{
key: 1,
name: '张三',
age: 25,
email: 'zhangsan@example.com',
status: '活跃'
},
{
key: 2,
name: '李四',
age: 30,
email: 'lisi@example.com',
status: '非活跃'
},
{
key: 3,
name: '王五',
age: 28,
email: 'wangwu@example.com',
status: '活跃'
}
])
const columns = [
{
title: '姓名',
key: 'name'
},
{
title: '年龄',
key: 'age'
},
{
title: '邮箱',
key: 'email'
},
{
title: '状态',
key: 'status'
},
{
title: '操作',
key: 'actions',
render(row) {
return h(NSpace, null, {
default: () => [
h(
NButton,
{
size: 'small',
type: 'primary',
onClick: () => editRow(row)
},
{ default: () => '编辑' }
),
h(
NPopconfirm,
{
onPositiveClick: () => deleteRow(row.key)
},
{
trigger: () => h(
NButton,
{
size: 'small',
type: 'error'
},
{ default: () => '删除' }
),
default: () => '确定要删除吗?'
}
)
]
})
}
}
]
const paginationReactive = reactive({
page: 1,
pageSize: 5,
showSizePicker: true,
pageSizes: [3, 5, 7],
onChange: (page) => {
paginationReactive.page = page
},
onUpdatePageSize: (pageSize) => {
paginationReactive.pageSize = pageSize
paginationReactive.page = 1
}
})
function editRow(row) {
dialog.info({
title: '编辑',
content: `编辑用户: ${row.name}`,
positiveText: '确定',
onPositiveClick: () => {
message.success('编辑成功')
}
})
}
function deleteRow(key) {
const index = data.value.findIndex(item => item.key === key)
if (index > -1) {
data.value.splice(index, 1)
message.success('删除成功')
}
}
</script>
<style scoped>
.naive-demo {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
</style>
Vant (移动端)
安装和配置
bash
npm install vant
javascript
// main.js
import { createApp } from 'vue'
import Vant from 'vant'
import 'vant/lib/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(Vant)
app.mount('#app')
移动端应用示例
vue
<template>
<div class="mobile-app">
<!-- 导航栏 -->
<van-nav-bar
title="移动端应用"
left-text="返回"
right-text="按钮"
left-arrow
@click-left="onClickLeft"
@click-right="onClickRight"
/>
<!-- 标签页 -->
<van-tabs v-model:active="activeTab" @change="onTabChange">
<!-- 首页 -->
<van-tab title="首页">
<div class="tab-content">
<!-- 轮播图 -->
<van-swipe :autoplay="3000" indicator-color="white">
<van-swipe-item v-for="(image, index) in banners" :key="index">
<img :src="image" alt="Banner" class="banner-image">
</van-swipe-item>
</van-swipe>
<!-- 网格导航 -->
<van-grid :column-num="4" :gutter="10">
<van-grid-item
v-for="item in gridItems"
:key="item.id"
:icon="item.icon"
:text="item.text"
@click="onGridClick(item)"
/>
</van-grid>
<!-- 商品列表 -->
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<van-card
v-for="item in products"
:key="item.id"
:num="item.num"
:price="item.price"
:title="item.title"
:thumb="item.thumb"
@click="onProductClick(item)"
>
<template #footer>
<van-button size="mini" type="primary">
加入购物车
</van-button>
</template>
</van-card>
</van-list>
</div>
</van-tab>
<!-- 分类 -->
<van-tab title="分类">
<div class="tab-content">
<van-sidebar v-model="activeSidebar">
<van-sidebar-item
v-for="category in categories"
:key="category.id"
:title="category.name"
/>
</van-sidebar>
</div>
</van-tab>
<!-- 购物车 -->
<van-tab title="购物车">
<div class="tab-content">
<van-submit-bar
:price="totalPrice"
button-text="提交订单"
@submit="onSubmit"
>
<van-checkbox v-model="selectAll" @change="onSelectAll">
全选
</van-checkbox>
</van-submit-bar>
<van-card
v-for="item in cartItems"
:key="item.id"
:num="item.num"
:price="item.price"
:title="item.title"
:thumb="item.thumb"
>
<template #num>
<van-stepper
v-model="item.num"
:min="1"
@change="onStepperChange"
/>
</template>
<template #footer>
<van-checkbox
v-model="item.selected"
@change="onItemSelect"
/>
</template>
</van-card>
</div>
</van-tab>
<!-- 我的 -->
<van-tab title="我的">
<div class="tab-content">
<van-cell-group>
<van-cell
title="个人信息"
is-link
@click="showUserInfo"
/>
<van-cell
title="我的订单"
is-link
@click="showOrders"
/>
<van-cell
title="设置"
is-link
@click="showSettings"
/>
</van-cell-group>
</div>
</van-tab>
</van-tabs>
<!-- 弹出层 -->
<van-popup v-model:show="showPopup" position="bottom" :style="{ height: '30%' }">
<div class="popup-content">
<h3>弹出层内容</h3>
<p>这是一个从底部弹出的层</p>
<van-button type="primary" @click="showPopup = false">
关闭
</van-button>
</div>
</van-popup>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { showToast, showDialog, showNotify } from 'vant'
// 标签页
const activeTab = ref(0)
// 轮播图
const banners = ref([
'https://fastly.jsdelivr.net/npm/@vant/assets/apple-1.jpeg',
'https://fastly.jsdelivr.net/npm/@vant/assets/apple-2.jpeg',
'https://fastly.jsdelivr.net/npm/@vant/assets/apple-3.jpeg'
])
// 网格导航
const gridItems = ref([
{ id: 1, icon: 'home-o', text: '首页' },
{ id: 2, icon: 'search', text: '搜索' },
{ id: 3, icon: 'friends-o', text: '朋友' },
{ id: 4, icon: 'setting-o', text: '设置' }
])
// 商品列表
const products = ref([])
const loading = ref(false)
const finished = ref(false)
// 分类
const activeSidebar = ref(0)
const categories = ref([
{ id: 1, name: '电子产品' },
{ id: 2, name: '服装鞋帽' },
{ id: 3, name: '家居用品' },
{ id: 4, name: '运动户外' }
])
// 购物车
const cartItems = ref([
{
id: 1,
title: 'iPhone 14',
price: 5999,
num: 1,
thumb: 'https://fastly.jsdelivr.net/npm/@vant/assets/ipad.jpeg',
selected: true
},
{
id: 2,
title: 'MacBook Pro',
price: 12999,
num: 1,
thumb: 'https://fastly.jsdelivr.net/npm/@vant/assets/ipad.jpeg',
selected: false
}
])
const selectAll = ref(false)
// 弹出层
const showPopup = ref(false)
// 计算属性
const totalPrice = computed(() => {
return cartItems.value
.filter(item => item.selected)
.reduce((total, item) => total + item.price * item.num, 0)
})
// 方法
function onClickLeft() {
showToast('返回')
}
function onClickRight() {
showToast('按钮')
}
function onTabChange(index) {
console.log('切换到标签页:', index)
}
function onGridClick(item) {
showToast(item.text)
}
function onProductClick(item) {
showDialog({
title: '商品详情',
message: `商品: ${item.title}\n价格: ¥${item.price}`
})
}
function onLoad() {
loading.value = true
// 模拟异步加载
setTimeout(() => {
for (let i = 0; i < 10; i++) {
products.value.push({
id: products.value.length + 1,
title: `商品 ${products.value.length + 1}`,
price: Math.floor(Math.random() * 1000) + 100,
thumb: 'https://fastly.jsdelivr.net/npm/@vant/assets/ipad.jpeg'
})
}
loading.value = false
if (products.value.length >= 50) {
finished.value = true
}
}, 1000)
}
function onSelectAll(checked) {
cartItems.value.forEach(item => {
item.selected = checked
})
}
function onItemSelect() {
const selectedCount = cartItems.value.filter(item => item.selected).length
selectAll.value = selectedCount === cartItems.value.length
}
function onStepperChange() {
// 数量变化处理
}
function onSubmit() {
const selectedItems = cartItems.value.filter(item => item.selected)
if (selectedItems.length === 0) {
showToast('请选择商品')
return
}
showDialog({
title: '确认订单',
message: `总价: ¥${totalPrice.value}`,
confirmButtonText: '确认支付',
cancelButtonText: '取消'
}).then(() => {
showNotify({ type: 'success', message: '支付成功' })
}).catch(() => {
showToast('取消支付')
})
}
function showUserInfo() {
showPopup.value = true
}
function showOrders() {
showToast('我的订单')
}
function showSettings() {
showToast('设置')
}
onMounted(() => {
onLoad()
})
</script>
<style scoped>
.mobile-app {
height: 100vh;
background-color: #f7f8fa;
}
.tab-content {
padding: 16px;
}
.banner-image {
width: 100%;
height: 200px;
object-fit: cover;
}
.popup-content {
padding: 20px;
text-align: center;
}
.popup-content h3 {
margin-bottom: 16px;
}
.popup-content p {
margin-bottom: 20px;
color: #666;
}
</style>
选择建议
企业级应用
- Element Plus: 功能最全面,生态最完善
- Ant Design Vue: 设计语言统一,组件质量高
- Arco Design: 字节跳动出品,现代化设计
现代 Web 应用
- Naive UI: 轻量现代,TypeScript 友好
- Vuetify: Material Design 风格
- PrimeVue: 主题丰富,定制性强
移动端应用
- Vant: 专业的移动端组件库
- NutUI: 京东出品的移动端组件库
跨平台应用
- Quasar: 一套代码,多端运行
- Ionic Vue: 混合应用开发
最佳实践
1. 按需引入
javascript
// 推荐:按需引入
import { ElButton, ElInput } from 'element-plus'
// 避免:全量引入
import ElementPlus from 'element-plus'
2. 主题定制
scss
// 定制主题变量
$--color-primary: #409eff;
$--color-success: #67c23a;
$--color-warning: #e6a23c;
$--color-danger: #f56c6c;
3. 组件封装
vue
<!-- 封装业务组件 -->
<template>
<el-table
:data="data"
:loading="loading"
v-bind="$attrs"
>
<slot></slot>
</el-table>
</template>
<script setup>
defineProps({
data: Array,
loading: Boolean
})
</script>
4. 国际化支持
javascript
// 配置国际化
import { createI18n } from 'vue-i18n'
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
app.use(ElementPlus, {
locale: zhCn
})
下一步
选择合适的 UI 组件库能够大大提升开发效率,建议根据项目需求和团队技术栈来选择!