Vue UI组件库:Ant Design Vue、Vant(移动端)
【摘要】 一、引言Ant Design Vue和Vant是Vue生态系统中两大主流UI组件库,分别专注于企业级中后台系统和移动端H5应用。Ant Design Vue基于蚂蚁金服的Ant Design设计体系,提供完整的企业级组件;Vant则由有赞前端团队开发,是移动端组件库的标杆。市场地位与技术影响力Ant Design Vue: GitHub 18k+ stars,中国企业级应用首选Vant: G...
一、引言
市场地位与技术影响力
-
Ant Design Vue: GitHub 18k+ stars,中国企业级应用首选 -
Vant: GitHub 22k+ stars,移动端Vue组件库第一选择 -
设计体系: 均遵循完整的设计规范,提供一致的用户体验
graph TD
A[Vue UI组件库生态] --> B[PC端企业级]
A --> C[移动端H5]
B --> B1[Ant Design Vue]
B --> B2[Element Plus]
C --> C1[Vant]
C --> C2[Cube UI]
B1 --> B11[设计体系完整]
B1 --> B12[企业级组件丰富]
B1 --> B13[TypeScript支持完善]
C1 --> C11[移动端优化]
C1 --> C12[轻量高效]
C1 --> C13[用户体验优秀]
二、技术背景
1. 发展历程与技术架构
class ComponentLibraryEvolution {
constructor() {
this.history = {
'ant-design-vue': {
'2017': '基于Ant Design React的Vue实现',
'2018': '1.0版本发布,支持Vue 2',
'2019': '2.0版本,完整组件生态',
'2020': '支持Vue 3,TypeScript重写',
'2021': '3.0版本,全面拥抱Vue 3生态',
'2022': '暗黑主题,国际化完善',
'2023': '性能优化,微前端支持'
},
'vant': {
'2017': '有赞移动端组件库开源',
'2018': '2.0版本,Vue 2完整支持',
'2019': '3.0版本,组件丰富度大幅提升',
'2020': '4.0版本,Vue 3支持',
'2021': 'Vant 4正式版,全面现代化',
'2022': '小程序支持,多端统一',
'2023': '性能极致优化,Tree-shaking完善'
}
};
this.architectureComparison = {
'设计理念': {
'ant-design-vue': '企业级设计系统,注重一致性和规范性',
'vant': '移动端优先,注重交互体验和性能'
},
'技术栈': {
'ant-design-vue': 'Vue 3 + TypeScript + Less',
'vant': 'Vue 3 + TypeScript + CSS Variables'
},
'组件设计': {
'ant-design-vue': '原子化设计,组合性强',
'vant': '场景化设计,开箱即用'
},
'主题定制': {
'ant-design-vue': 'Less变量,编译时定制',
'vant': 'CSS变量,运行时动态主题'
}
};
}
}
2. 核心设计哲学对比
interface DesignPhilosophy {
name: string;
principles: string[];
target: string;
characteristics: string[];
}
const antDesignPhilosophy: DesignPhilosophy = {
name: "Ant Design Vue",
principles: [
"一致性:设计语言和交互模式统一",
"效率:提升设计和开发效率",
"可控性:为用户保留充分控制权",
"自然:符合用户真实世界认知"
],
target: "企业级中后台产品",
characteristics: [
"基于Ant Design设计体系",
"完整的国际化解决方案",
"丰富的企业级组件",
"强大的TypeScript支持"
]
};
const vantPhilosophy: DesignPhilosophy = {
name: "Vant",
principles: [
"简洁:视觉简洁,交互直接",
"高效:快速响应,流畅体验",
"实用:解决实际业务场景",
"友好:符合移动端使用习惯"
],
target: "移动端H5应用和小程序",
characteristics: [
"移动端交互优化",
"轻量级,高性能",
"多端适配支持",
"丰富的业务组件"
]
};
三、核心特性深度解析
1. Ant Design Vue 架构原理
// Ant Design Vue 组件架构示例
import { defineComponent, ref, computed, provide, inject } from 'vue';
import { useConfigProvider } from './config-provider';
// 配置注入系统
export const useForm = () => {
const configProvider = useConfigProvider();
return {
size: configProvider.componentSize,
disabled: configProvider.disabled,
// 全局表单配置
};
};
// 基础表单组件实现
export const AForm = defineComponent({
name: 'AForm',
props: {
model: { type: Object, required: true },
rules: { type: Object, default: () => ({}) },
layout: { type: String, default: 'horizontal' }
},
setup(props, { slots }) {
const fields = ref(new Set());
const validateResults = ref(new Map());
// 提供表单上下文
provide('formContext', {
registerField: (field: any) => {
fields.value.add(field);
},
unregisterField: (field: any) => {
fields.value.delete(field);
},
validateField: async (fieldName: string) => {
// 字段验证逻辑
}
});
// 表单验证方法
const validate = async () => {
const results = await Promise.all(
Array.from(fields.value).map(field => field.validate())
);
return results.every(result => result);
};
return () => (
<form class={['ant-form', `ant-form-${props.layout}`]}>
{slots.default?.()}
</form>
);
}
});
// 表单字段组件
export const AFormItem = defineComponent({
name: 'AFormItem',
props: {
label: String,
name: String,
rules: Array
},
setup(props, { slots }) {
const formContext = inject('formContext');
const errorMessage = ref('');
const validate = async () => {
// 验证逻辑
};
// 注册到表单
onMounted(() => {
formContext?.registerField({ validate, name: props.name });
});
onUnmounted(() => {
formContext?.unregisterField({ validate, name: props.name });
});
return () => (
<div class="ant-form-item">
<label class="ant-form-item-label">{props.label}</label>
<div class="ant-form-item-control">
{slots.default?.()}
{errorMessage.value && (
<div class="ant-form-item-explain">{errorMessage.value}</div>
)}
</div>
</div>
);
}
});
2. Vant 移动端优化原理
// Vant 移动端优化实现
import { defineComponent, ref, onMounted, onUnmounted } from 'vue';
// 触摸反馈混合
export const useTouch = () => {
const touchState = ref({
startX: 0,
startY: 0,
deltaX: 0,
deltaY: 0,
direction: '' as 'horizontal' | 'vertical' | ''
});
const resetTouch = () => {
touchState.value = {
startX: 0,
startY: 0,
deltaX: 0,
deltaY: 0,
direction: ''
};
};
const touchStart = (event: TouchEvent) => {
resetTouch();
const touch = event.touches[0];
touchState.value.startX = touch.clientX;
touchState.value.startY = touch.clientY;
};
const touchMove = (event: TouchEvent) => {
const touch = event.touches[0];
touchState.value.deltaX = touch.clientX - touchState.value.startX;
touchState.value.deltaY = touch.clientY - touchState.value.startY;
// 判断滑动方向
if (!touchState.value.direction) {
touchState.value.direction = Math.abs(touchState.value.deltaX) >
Math.abs(touchState.value.deltaY) ? 'horizontal' : 'vertical';
}
};
return {
touchState,
touchStart,
touchMove,
resetTouch
};
};
// 按钮组件实现
export const VanButton = defineComponent({
name: 'VanButton',
props: {
type: { type: String, default: 'default' },
size: { type: String, default: 'normal' },
loading: Boolean,
disabled: Boolean
},
emits: ['click'],
setup(props, { emit, slots }) {
const { touchState, touchStart, touchMove, resetTouch } = useTouch();
const isActive = ref(false);
const handleTouchStart = (event: TouchEvent) => {
if (props.loading || props.disabled) return;
touchStart(event);
isActive.value = true;
};
const handleTouchEnd = () => {
if (props.loading || props.disabled) return;
isActive.value = false;
// 触发点击事件
if (Math.abs(touchState.value.deltaX) < 10 &&
Math.abs(touchState.value.deltaY) < 10) {
emit('click');
}
resetTouch();
};
return () => (
<button
class={[
'van-button',
`van-button--${props.type}`,
`van-button--${props.size}`,
{
'van-button--loading': props.loading,
'van-button--disabled': props.disabled,
'van-button--active': isActive.value
}
]}
onTouchstart={handleTouchStart}
onTouchend={handleTouchEnd}
onTouchcancel={handleTouchEnd}
>
{props.loading && <i class="van-button__loading-icon" />}
<span class="van-button__text">{slots.default?.()}</span>
</button>
);
}
});
// 移动端适配工具
export const useMobileAdapter = () => {
const isMobile = ref(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent));
const viewportWidth = ref(window.innerWidth);
const updateViewport = () => {
viewportWidth.value = window.innerWidth;
};
onMounted(() => {
window.addEventListener('resize', updateViewport);
});
onUnmounted(() => {
window.removeEventListener('resize', updateViewport);
});
return {
isMobile,
viewportWidth
};
};
四、实际应用场景与代码实现
1. Ant Design Vue 企业级后台系统
<!-- 企业级数据管理系统 -->
<template>
<a-config-provider :locale="zhCN">
<div class="enterprise-layout">
<!-- 顶部导航 -->
<a-layout-header class="header">
<div class="header-content">
<div class="logo">
<img src="/logo.png" alt="Logo">
<h1>企业管理系统</h1>
</div>
<a-menu
v-model:selectedKeys="selectedKeys"
theme="dark"
mode="horizontal"
:style="{ lineHeight: '64px' }"
>
<a-menu-item key="dashboard">仪表盘</a-menu-item>
<a-menu-item key="users">用户管理</a-menu-item>
<a-sub-menu key="system">
<template #title>系统管理</template>
<a-menu-item key="roles">角色管理</a-menu-item>
<a-menu-item key="permissions">权限管理</a-menu-item>
</a-sub-menu>
</a-menu>
<div class="header-actions">
<a-dropdown :trigger="['click']">
<a class="user-info">
<a-avatar :size="32" src="/avatar.png" />
<span>管理员</span>
<down-outlined />
</a>
<template #overlay>
<a-menu>
<a-menu-item key="profile">
<user-outlined /> 个人中心
</a-menu-item>
<a-menu-item key="settings">
<setting-outlined /> 系统设置
</a-menu-item>
<a-menu-divider />
<a-menu-item key="logout" @click="handleLogout">
<logout-outlined /> 退出登录
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</div>
</a-layout-header>
<a-layout>
<!-- 侧边栏 -->
<a-layout-sider width="200" style="background: #fff">
<a-menu
mode="inline"
v-model:selectedKeys="selectedKeys"
v-model:openKeys="openKeys"
style="height: 100%"
>
<a-sub-menu key="data">
<template #icon><database-outlined /></template>
<template #title>数据管理</template>
<a-menu-item key="data-list">数据列表</a-menu-item>
<a-menu-item key="data-import">数据导入</a-menu-item>
</a-sub-menu>
<a-sub-menu key="workflow">
<template #icon><workflow-outlined /></template>
<template #title>工作流</template>
<a-menu-item key="approval">审批流程</a-menu-item>
<a-menu-item key="tasks">任务管理</a-menu-item>
</a-sub-menu>
</a-menu>
</a-layout-sider>
<!-- 主要内容 -->
<a-layout-content class="content">
<div class="content-wrapper">
<!-- 面包屑 -->
<a-breadcrumb class="breadcrumb">
<a-breadcrumb-item>首页</a-breadcrumb-item>
<a-breadcrumb-item>{{ currentPage }}</a-breadcrumb-item>
</a-breadcrumb>
<!-- 页面内容 -->
<div class="page-content">
<router-view />
</div>
</div>
</a-layout-content>
</a-layout>
</div>
</a-config-provider>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useRoute } from 'vue-router';
import {
DownOutlined,
UserOutlined,
SettingOutlined,
LogoutOutlined,
DatabaseOutlined,
WorkflowOutlined
} from '@ant-design/icons-vue';
import zhCN from 'ant-design-vue/es/locale/zh_CN';
const route = useRoute();
const selectedKeys = ref([route.name]);
const openKeys = ref(['data']);
const currentPage = computed(() => {
const pageMap = {
'dashboard': '仪表盘',
'users': '用户管理',
'data-list': '数据列表'
};
return pageMap[route.name] || '未知页面';
});
const handleLogout = () => {
// 退出登录逻辑
};
</script>
<style scoped>
.enterprise-layout {
height: 100vh;
}
.header {
background: #001529;
padding: 0 20px;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: 0 auto;
}
.logo {
display: flex;
align-items: center;
color: white;
img {
width: 32px;
height: 32px;
margin-right: 12px;
}
h1 {
color: white;
margin: 0;
font-size: 18px;
}
}
.user-info {
color: white;
display: flex;
align-items: center;
gap: 8px;
}
.content {
padding: 24px;
background: #f0f2f5;
}
.content-wrapper {
max-width: 1200px;
margin: 0 auto;
background: white;
padding: 24px;
border-radius: 6px;
min-height: calc(100vh - 112px);
}
.breadcrumb {
margin-bottom: 16px;
}
</style>
2. Vant 移动端电商应用
<!-- 移动端电商首页 -->
<template>
<div class="mobile-app">
<!-- 顶部导航栏 -->
<van-sticky>
<van-nav-bar
title="电商商城"
left-text="返回"
right-text="搜索"
@click-left="onClickLeft"
@click-right="onClickRight"
>
<template #right>
<van-icon name="search" size="18" />
</template>
</van-nav-bar>
</van-sticky>
<!-- 轮播图 -->
<van-swipe class="banner-swipe" :autoplay="3000" indicator-color="white">
<van-swipe-item v-for="banner in banners" :key="banner.id">
<img :src="banner.image" class="banner-image" />
</van-swipe-item>
</van-swipe>
<!-- 功能入口 -->
<van-grid :column-num="4" :border="false" class="function-grid">
<van-grid-item
v-for="item in functions"
:key="item.id"
:icon="item.icon"
:text="item.text"
@click="onFunctionClick(item)"
/>
</van-grid>
<!-- 秒杀专区 -->
<van-panel title="限时秒杀" class="seckill-panel">
<template #header>
<div class="panel-header">
<span class="title">限时秒杀</span>
<van-count-down :time="countdownTime" class="countdown">
<template #default="timeData">
<span class="countdown-item">{{ timeData.hours }}</span>:
<span class="countdown-item">{{ timeData.minutes }}</span>:
<span class="countdown-item">{{ timeData.seconds }}</span>
</template>
</van-count-down>
<span class="more">更多</span>
</div>
</template>
<van-card
v-for="product in seckillProducts"
:key="product.id"
:price="product.price"
:desc="product.desc"
:title="product.title"
:thumb="product.thumb"
:origin-price="product.originPrice"
class="product-card"
>
<template #tags>
<van-tag type="danger">秒杀</van-tag>
<van-tag plain type="danger">限时</van-tag>
</template>
<template #footer>
<van-button size="mini" type="danger" @click="addToCart(product)">
立即抢购
</van-button>
</template>
</van-card>
</van-panel>
<!-- 商品列表 -->
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
class="product-list"
>
<van-card
v-for="item in productList"
:key="item.id"
:price="item.price"
:desc="item.desc"
:title="item.title"
:thumb="item.thumb"
@click="goToDetail(item)"
>
<template #tags>
<van-tag v-for="tag in item.tags" :key="tag" type="primary">
{{ tag }}
</van-tag>
</template>
<template #footer>
<van-button
size="mini"
icon="shopping-cart-o"
@click.stop="addToCart(item)"
>
加入购物车
</van-button>
</template>
</van-card>
</van-list>
<!-- 底部导航 -->
<van-tabbar v-model="activeTab" fixed>
<van-tabbar-item name="home" icon="home-o">首页</van-tabbar-item>
<van-tabbar-item name="category" icon="apps-o">分类</van-tabbar-item>
<van-tabbar-item name="cart" icon="shopping-cart-o" :badge="cartCount">
购物车
</van-tabbar-item>
<van-tabbar-item name="mine" icon="user-o">我的</van-tabbar-item>
</van-tabbar>
<!-- 购物车弹窗 -->
<van-popup v-model:show="showCart" position="bottom" round>
<cart-panel @close="showCart = false" />
</van-popup>
</div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue';
import { useRouter } from 'vue-router';
import {
NavBar,
Swipe,
SwipeItem,
Grid,
GridItem,
Panel,
Card,
Tag,
Button,
List,
Tabbar,
TabbarItem,
Popup,
CountDown,
Icon,
Sticky
} from 'vant';
import CartPanel from './components/CartPanel.vue';
const router = useRouter();
// 响应式数据
const activeTab = ref('home');
const loading = ref(false);
const finished = ref(false);
const showCart = ref(false);
const countdownTime = ref(2 * 60 * 60 * 1000); // 2小时
// 模拟数据
const banners = ref([
{ id: 1, image: '/banner1.jpg', link: '/activity/1' },
{ id: 2, image: '/banner2.jpg', link: '/activity/2' },
{ id: 3, image: '/banner3.jpg', link: '/activity/3' }
]);
const functions = ref([
{ id: 1, icon: 'coupon-o', text: '优惠券' },
{ id: 2, icon: 'gift-o', text: '积分商城' },
{ id: 3, icon: 'points-o', text: '会员中心' },
{ id: 4, icon: 'gold-coin-o', text: '砍价' },
{ id: 5, icon: 'cash-back-o', text: '返现' },
{ id: 6, icon: 'free-postage-o', text: '包邮' },
{ id: 7, icon: 'discount-o', text: '折扣' },
{ id: 8, icon: 'more-o', text: '更多' }
]);
const seckillProducts = ref([
{
id: 1,
title: 'iPhone 15 Pro',
desc: '最新款苹果手机',
price: '7999',
originPrice: '8999',
thumb: '/iphone.jpg'
}
// ...更多商品
]);
const productList = ref([]);
const cartCount = computed(() => {
// 从状态管理获取购物车数量
return 5;
});
// 方法
const onClickLeft = () => {
router.back();
};
const onClickRight = () => {
router.push('/search');
};
const onFunctionClick = (item) => {
// 处理功能点击
console.log('功能点击:', item);
};
const addToCart = (product) => {
// 添加到购物车逻辑
showCart.value = true;
};
const goToDetail = (product) => {
router.push(`/product/${product.id}`);
};
const onLoad = async () => {
// 加载更多商品
loading.value = true;
// 模拟API调用
setTimeout(() => {
const newProducts = Array.from({ length: 10 }, (_, index) => ({
id: productList.value.length + index + 1,
title: `商品 ${productList.value.length + index + 1}`,
desc: '这是一个很好的商品',
price: (Math.random() * 1000).toFixed(2),
thumb: `/product${index % 5 + 1}.jpg`,
tags: ['热卖', '新品', '推荐'].slice(0, Math.floor(Math.random() * 3) + 1)
}));
productList.value.push(...newProducts);
loading.value = false;
// 模拟数据加载完成
if (productList.value.length >= 50) {
finished.value = true;
}
}, 1000);
};
onMounted(() => {
// 初始化数据
onLoad();
});
</script>
<style scoped>
.mobile-app {
padding-bottom: 50px; /* 为底部导航留出空间 */
background: #f5f5f5;
min-height: 100vh;
}
.banner-swipe {
height: 200px;
margin: 10px;
border-radius: 8px;
overflow: hidden;
}
.banner-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.function-grid {
margin: 10px 0;
background: white;
}
.seckill-panel {
margin: 10px;
border-radius: 8px;
overflow: hidden;
}
.panel-header {
display: flex;
align-items: center;
padding: 12px 16px;
.title {
font-size: 16px;
font-weight: bold;
margin-right: 12px;
}
.countdown {
flex: 1;
.countdown-item {
display: inline-block;
width: 22px;
height: 22px;
line-height: 22px;
text-align: center;
background: #ee0a24;
color: white;
border-radius: 2px;
margin: 0 2px;
}
}
.more {
color: #969799;
font-size: 14px;
}
}
.product-card {
margin: 0;
:deep(.van-card__content) {
min-height: auto;
}
}
.product-list {
margin: 10px;
.van-card {
margin-bottom: 10px;
border-radius: 8px;
overflow: hidden;
}
}
</style>
五、核心特性对比分析
1. 表单处理能力对比
<!-- Ant Design Vue 复杂表单 -->
<template>
<a-form
:model="formState"
:rules="rules"
:label-col="labelCol"
:wrapper-col="wrapperCol"
ref="formRef"
>
<a-form-item label="用户名" name="username">
<a-input v-model:value="formState.username" placeholder="请输入用户名" />
</a-form-item>
<a-form-item label="邮箱" name="email">
<a-input v-model:value="formState.email" placeholder="请输入邮箱" />
</a-form-item>
<a-form-item label="手机号" name="phone">
<a-input v-model:value="formState.phone" placeholder="请输入手机号" />
</a-form-item>
<a-form-item label="角色" name="role">
<a-select v-model:value="formState.role" placeholder="请选择角色">
<a-select-option value="admin">管理员</a-select-option>
<a-select-option value="user">普通用户</a-select-option>
<a-select-option value="guest">访客</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="状态" name="status">
<a-radio-group v-model:value="formState.status">
<a-radio value="active">激活</a-radio>
<a-radio value="inactive">未激活</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="描述" name="description">
<a-textarea
v-model:value="formState.description"
placeholder="请输入描述"
:rows="4"
/>
</a-form-item>
<a-form-item :wrapper-col="{ span: 14, offset: 4 }">
<a-button type="primary" @click="onSubmit">提交</a-button>
<a-button style="margin-left: 10px" @click="resetForm">重置</a-button>
</a-form-item>
</a-form>
</template>
<script setup>
import { reactive, ref } from 'vue';
import { message } from 'ant-design-vue';
const formRef = ref();
const labelCol = { span: 4 };
const wrapperCol = { span: 14 };
const formState = reactive({
username: '',
email: '',
phone: '',
role: undefined,
status: 'active',
description: ''
});
const rules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 20, message: '用户名长度在3-20个字符', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }
],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
],
role: [
{ required: true, message: '请选择角色', trigger: 'change' }
]
};
const onSubmit = async () => {
try {
await formRef.value.validate();
message.success('提交成功!');
// 处理提交逻辑
} catch (error) {
message.error('表单验证失败!');
}
};
const resetForm = () => {
formRef.value.resetFields();
};
</script>
<!-- Vant 移动端表单 -->
<template>
<van-form @submit="onSubmit" class="mobile-form">
<van-cell-group>
<van-field
v-model="form.username"
name="username"
label="用户名"
placeholder="请输入用户名"
:rules="[{ required: true, message: '请填写用户名' }]"
/>
<van-field
v-model="form.phone"
name="phone"
label="手机号"
placeholder="请输入手机号"
:rules="[{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确' }]"
/>
<van-field
v-model="form.sms"
center
clearable
label="短信验证码"
placeholder="请输入短信验证码"
>
<template #button>
<van-button
size="small"
type="primary"
@click="onSendSms"
:disabled="smsCountdown > 0"
>
{{ smsCountdown > 0 ? `${smsCountdown}s后重试` : '发送验证码' }}
</van-button>
</template>
</van-field>
<van-field
v-model="form.address"
name="address"
label="收货地址"
placeholder="请输入收货地址"
:rules="[{ required: true, message: '请填写收货地址' }]"
/>
</van-cell-group>
<van-cell-group title="配送方式">
<van-radio-group v-model="form.shipping" direction="horizontal">
<van-radio name="express">快递配送</van-radio>
<van-radio name="pickup">门店自提</van-radio>
</van-radio-group>
</van-cell-group>
<van-cell-group title="支付方式">
<van-radio-group v-model="form.payment">
<van-radio name="wechat">微信支付</van-radio>
<van-radio name="alipay">支付宝</van-radio>
<van-radio name="card">银行卡</van-radio>
</van-radio-group>
</van-cell-group>
<div style="margin: 16px;">
<van-button round block type="primary" native-type="submit">
立即提交
</van-button>
</div>
</van-form>
</template>
<script setup>
import { ref } from 'vue';
import { showToast } from 'vant';
const form = ref({
username: '',
phone: '',
sms: '',
address: '',
shipping: 'express',
payment: 'wechat'
});
const smsCountdown = ref(0);
const onSendSms = () => {
// 发送验证码逻辑
smsCountdown.value = 60;
const timer = setInterval(() => {
smsCountdown.value--;
if (smsCountdown.value <= 0) {
clearInterval(timer);
}
}, 1000);
showToast('验证码已发送');
};
const onSubmit = (values) => {
console.log('提交数据:', values);
showToast('提交成功');
};
</script>
<style scoped>
.mobile-form {
padding-bottom: 20px;
}
:deep(.van-cell-group__title) {
font-size: 16px;
font-weight: bold;
padding: 16px;
}
:deep(.van-radio-group) {
padding: 0 16px;
}
:deep(.van-radio) {
margin-right: 20px;
}
</style>
六、性能优化与最佳实践
1. 按需引入配置
// Ant Design Vue 按需引入
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import Components from 'unplugin-vue-components/vite';
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
export default defineConfig({
plugins: [
vue(),
Components({
resolvers: [
AntDesignVueResolver({
importStyle: 'less', // 使用less样式
}),
],
}),
],
css: {
preprocessorOptions: {
less: {
modifyVars: {
'primary-color': '#1890ff', // 主题色
'border-radius-base': '4px', // 圆角
},
javascriptEnabled: true,
},
},
},
});
// Vant 按需引入
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from 'unplugin-vue-components/resolvers';
export default defineConfig({
plugins: [
vue(),
Components({
resolvers: [VantResolver()],
}),
],
});
2. 主题定制方案
// Ant Design Vue 主题定制
// styles/antd.less
@primary-color: #1890ff; // 全局主色
@link-color: #1890ff; // 链接色
@success-color: #52c41a; // 成功色
@warning-color: #faad14; // 警告色
@error-color: #f5222d; // 错误色
@font-size-base: 14px; // 主字号
@heading-color: rgba(0, 0, 0, 0.85); // 标题色
@text-color: rgba(0, 0, 0, 0.65); // 主文本色
@text-color-secondary: rgba(0, 0, 0, 0.45); // 次文本色
@disabled-color: rgba(0, 0, 0, 0.25); // 失效色
@border-radius-base: 4px; // 组件/浮层圆角
@border-color-base: #d9d9d9; // 边框色
@box-shadow-base: 0 2px 8px rgba(0, 0, 0, 0.15); // 浮层阴影
// Vant 主题定制
// styles/vant.css
:root {
--van-primary-color: #1989fa;
--van-success-color: #07c160;
--van-danger-color: #ee0a24;
--van-warning-color: #ff976a;
--van-text-color: #323233;
--van-text-color-2: #969799;
--van-text-color-3: #c8c9cc;
--van-border-color: #ebedf0;
--van-active-color: #f2f3f5;
--van-background-color: #f7f8fa;
--van-background-color-light: #fafafa;
}
七、部署与工程化
1. 完整项目配置
// vue.config.js (Ant Design Vue项目)
const { defineConfig } = require('@vue/cli-service');
const path = require('path');
module.exports = defineConfig({
transpileDependencies: true,
configureWebpack: {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
},
css: {
loaderOptions: {
less: {
lessOptions: {
modifyVars: {
'primary-color': '#1890ff',
'link-color': '#1890ff',
'border-radius-base': '4px',
},
javascriptEnabled: true,
},
},
},
},
devServer: {
port: 8080,
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
}
});
// vite.config.js (Vant项目)
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { VantResolver } from 'unplugin-vue-components/resolvers';
import Components from 'unplugin-vue-components/vite';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
import path from 'path';
export default defineConfig({
plugins: [
vue(),
Components({
resolvers: [VantResolver()],
}),
createSvgIconsPlugin({
iconDirs: [path.resolve(process.cwd(), 'src/icons')],
symbolId: 'icon-[dir]-[name]',
}),
],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
build: {
target: 'es2015',
cssTarget: 'chrome80',
rollupOptions: {
output: {
manualChunks: {
'vant': ['vant'],
'vue-vendor': ['vue', 'vue-router', 'pinia'],
},
},
},
},
});
八、技术趋势与未来展望
1. 技术发展趋势分析
class ComponentLibraryTrends {
constructor() {
this.trends = {
'design_systems': {
'现状': '组件库趋于设计系统化',
'趋势': '设计令牌(Design Tokens)普及',
'影响': '更好的主题定制和品牌一致性'
},
'performance_optimization': {
'现状': 'Tree-shaking和代码分割',
'趋势': '更细粒度的组件懒加载',
'影响': '更小的打包体积和更快的加载'
},
'type_safety': {
'现状': 'TypeScript全面支持',
'趋势': '更完善的类型定义和类型安全',
'影响': '更好的开发体验和代码质量'
},
'mobile_first': {
'现状': '移动端优先设计',
'趋势': 'PWA和移动端原生体验',
'影响': '更好的移动端用户体验'
},
'accessibility': {
'现状': '基础无障碍支持',
'趋势': '完整的无障碍(a11y)解决方案',
'影响': '更包容的产品设计'
}
};
}
getFuturePredictions() {
return {
'2024_predictions': {
'AI集成': 'AI辅助组件开发和代码生成',
'低代码平台': '基于组件库的可视化搭建',
'微前端成熟': '更好的微前端组件共享',
'Web Components': '更好的原生组件互操作'
},
'技术突破方向': {
'性能极致优化': '组件级代码分割和预加载',
'开发体验提升': '智能代码提示和实时预览',
'多端统一': '一套代码多端适配',
'智能化组件': '自适应和智能响应的组件'
}
};
}
}
总结
核心价值总结
-
Ant Design Vue: 企业级应用的完整解决方案,提供规范的设计语言和丰富的业务组件 -
Vant: 移动端开发的优选方案,注重交互体验和性能优化
技术选型建议
-
企业级中后台系统: 优先选择Ant Design Vue,设计规范完整,组件丰富 -
移动端H5应用: 选择Vant,移动端交互优化,用户体验优秀 -
需要多端适配: 根据主要平台选择,或考虑混合使用 -
TypeScript项目: 两者都提供完善的TypeScript支持
最佳实践
-
按需引入减少打包体积 -
主题定制保持品牌一致性 -
性能优化关注移动端体验 -
无障碍访问提升产品包容性
未来展望
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)