基于 Vue 3 + Vite + <script setup>
。涵盖 Element Plus / Naive UI / Ant Design Vue / Vuetify 3 常用场景。
0. 基础工程配置(通用)
# 创建项目(Node 16+)
npm create vite@latest my-app -- --template vue-ts
cd my-app && npm i
# 自动按需导入(强烈推荐)
npm i -D unplugin-auto-import unplugin-vue-components
vite.config.ts(基础)
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
dts: 'src/auto-imports.d.ts'
}),
Components({ dts: 'src/components.d.ts' })
]
})
main.ts
(通用)
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
1. Element Plus(企业后台常用)
安装与按需引入
npm i element-plus @element-plus/icons-vue
自动导入(推荐)
// vite.config.ts
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
Components({
resolvers: [ElementPlusResolver()],
})
手动全量(不推荐生产)
// main.ts
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
app.use(ElementPlus)
快速用法
<template>
<el-button type="primary" @click="open = true">打开</el-button>
<el-dialog v-model="open" title="对话框">
<el-form :model="form" :rules="rules" ref="formRef" label-width="80px">
<el-form-item label="姓名" prop="name"><el-input v-model="form.name"/></el-form-item>
<el-form-item label="年龄" prop="age"><el-input-number v-model="form.age" :min="0"/></el-form-item>
</el-form>
<template #footer>
<el-button @click="open=false">取消</el-button>
<el-button type="primary" @click="submit">提交</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const open = ref(false)
const formRef = ref()
const form = ref({ name: '', age: 18 })
const rules = { name: [{ required: true, message: '必填', trigger: 'blur' }] }
const submit = () => formRef.value?.validate((ok: boolean)=> ok && (open.value=false))
</script>
主题与暗色
- 引入基础样式后,可用
class="dark"
+ CSS 变量启用暗色方案(需引入element-plus/theme-chalk/dark/css-vars.css
)。
import 'element-plus/theme-chalk/dark/css-vars.css'
图标
import * as ElIcons from '@element-plus/icons-vue'
Object.entries(ElIcons).forEach(([k, c]) => app.component(k, c as any))
2. Naive UI(风格轻盈、默认按需)
安装
npm i naive-ui @css-render/vue3-ssr # ssr 可选
快速用法
<template>
<n-config-provider :theme="dark ? darkTheme : null">
<n-space>
<n-button type="primary" @click="show=true">Open</n-button>
<n-switch v-model:value="dark">Dark</n-switch>
</n-space>
<n-modal v-model:show="show" preset="dialog" title="提示" content="操作完成"/>
<n-form :model="form" :rules="rules" ref="formRef">
<n-form-item label="邮箱" path="email"><n-input v-model:value="form.email"/></n-form-item>
</n-form>
</n-config-provider>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { darkTheme } from 'naive-ui'
const dark = ref(false)
const show = ref(false)
const formRef = ref()
const form = ref({ email: '' })
const rules = { email: [{ required: true, message: '必填' }] }
</script>
自动导入(可选)
// vite.config.ts
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
Components({ resolvers: [NaiveUiResolver()] })
图标
- 推荐
@vicons/*
套件(例如@vicons/ionicons5
),与<n-icon>
搭配:
<n-icon><HomeOutline/></n-icon>
3. Ant Design Vue(中后台 + 设计规范)
安装
npm i ant-design-vue @ant-design/icons-vue
引入方式
// 全量(简单但体积大)
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/reset.css'
app.use(Antd)
// 按需(推荐)
import { Button, Modal, Form, Input } from 'ant-design-vue'
app.use(Button).use(Modal).use(Form).use(Input)
快速用法
<template>
<a-space>
<a-button type="primary" @click="open=true">Open</a-button>
<a-button danger @click="count++">Count: {{ count }}</a-button>
</a-space>
<a-modal v-model:open="open" title="标题">
<a-form :model="form" :rules="rules" ref="formRef" layout="vertical">
<a-form-item label="姓名" name="name" :rules="[{ required: true, message: '必填' }]">
<a-input v-model:value="form.name"/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const open = ref(false)
const count = ref(0)
const formRef = ref()
const form = ref({ name: '' })
const rules = { }
</script>
主题定制(令牌 Token)
- 可通过
<a-config-provider :theme="{ token: { colorPrimary: '#1677ff' } }">
在应用层定制主色等 Token。
图标
import * as Icons from '@ant-design/icons-vue'
Object.entries(Icons).forEach(([k, c]) => app.component(k, c as any))
4. Vuetify 3(Material Design)
安装
npm i vuetify @mdi/font # @mdi/font 提供图标字体
main.ts 配置
import { createApp } from 'vue'
import App from './App.vue'
import 'vuetify/styles'
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
const vuetify = createVuetify({ components, directives })
createApp(App).use(vuetify).mount('#app')
快速用法
<template>
<v-app>
<v-container>
<v-btn @click="dialog=true">Open</v-btn>
<v-dialog v-model="dialog" max-width="420">
<v-card>
<v-card-title>标题</v-card-title>
<v-card-text>
<v-form ref="formRef" v-model="valid">
<v-text-field v-model="name" label="姓名" :rules="[v=>!!v||'必填']"/>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer/>
<v-btn text @click="dialog=false">取消</v-btn>
<v-btn color="primary" :disabled="!valid" @click="dialog=false">提交</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-container>
</v-app>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const dialog = ref(false)
const name = ref('')
const valid = ref(false)
</script>
主题与暗色
const vuetify = createVuetify({
theme: {
defaultTheme: 'light',
themes: {
light: { colors: { primary: '#1867C0' } },
dark: { dark: true }
}
}
})
5. 自动按需导入(整合示例)
// vite.config.ts
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, NaiveUiResolver, AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
vue(),
AutoImport({ imports: ['vue'], dts: 'src/auto-imports.d.ts' }),
Components({
dts: 'src/components.d.ts',
resolvers: [
ElementPlusResolver(),
NaiveUiResolver(),
AntDesignVueResolver({ importStyle: false })
]
})
]
})
注:Vuetify 以官方注册方式为主,通常不走 resolver(也可单独配置社区 resolver)。
6. 表单校验策略对比
- Element Plus:
<el-form>
+rules
,字段级trigger
(blur
/change
)。 - Naive UI:
<n-form>
+rules
,支持异步校验、path
指定字段。 - Ant Design Vue:
<a-form>
+rules
或name
层级规则,v-model:value
。 - Vuetify:
<v-form v-model="valid">
+ 各输入组件:rules="[(v)=>true||'msg']"
。
如需复杂 Schema,统一接入 zod
/yup
:
import { z } from 'zod'
const schema = z.object({ email: z.string().email() })
7. 国际化与布局
- 国际化:四者均可与
vue-i18n
搭配;Element Plus/AntD 提供内置 Locale 组件。 - 布局:AntD
<a-layout>
、Vuetify<v-layout>
、Element<el-container>
,Naive UI 以n-layout
/n-layout-sider
组合为主。
npm i vue-i18n
8. 性能与体积小贴士
- 强制按需(避免全量引入)。
- 生产构建使用
vite build --report
分析体积(或rollup-plugin-visualizer
)。 - 图片与图标优先 SVG(Ant/Element/N-UI 均支持自定义图标组件)。
- 大型表格虚拟滚动:优先选库内置表格优化(或
vue-virtual-scroller
)。
9. 迁移与选型建议
- 数据录入密集 + 稳健:Element Plus / Ant Design Vue。
- 设计统一 + Material:Vuetify。
- 极简风格 + 轻量主题:Naive UI。
- 海外移动端 + PWA:可评估 Quasar(未在本文详述)。
10. 常用样板片段(可复制)
顶栏 + 侧边栏布局(AntD 版本)
<template>
<a-layout style="min-height: 100vh">
<a-layout-sider collapsible v-model:collapsed="collapsed">
<div class="logo">My Admin</div>
<a-menu theme="dark" mode="inline" :items="items" />
</a-layout-sider>
<a-layout>
<a-layout-header style="background:transparent">
<a-space>
<a-button type="text" @click="collapsed=!collapsed">Toggle</a-button>
</a-space>
</a-layout-header>
<a-layout-content style="margin:16px">
<RouterView />
</a-layout-content>
</a-layout>
</a-layout>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const collapsed = ref(false)
const items = [
{ key: '1', label: '首页' },
{ key: '2', label: '用户' }
]
</script>
过滤表格(Element Plus 版本)
<template>
<el-space direction="vertical" style="width:100%">
<el-input v-model="q" placeholder="搜索..." clearable />
<el-table :data="rows.filter(r=>r.name.includes(q))" height="420">
<el-table-column prop="name" label="姓名"/>
<el-table-column prop="age" label="年龄"/>
<el-table-column label="操作">
<template #default="{ row }">
<el-button size="small" @click="edit(row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
</el-space>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const q = ref('')
const rows = ref([{name:'Ada',age:28},{name:'Alan',age:35}])
const edit = (r:any)=> console.log(r)
</script>
暗色切换(Naive UI 版本)
<template>
<n-config-provider :theme="dark?darkTheme:null">
<n-space align="center">
<n-switch v-model:value="dark">Dark</n-switch>
<n-button>Primary</n-button>
</n-space>
</n-config-provider>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { darkTheme } from 'naive-ui'
const dark = ref(false)
</script>
登录卡片(Vuetify 版本)
<template>
<v-container class="d-flex align-center" style="min-height:100vh">
<v-card width="420">
<v-card-title>登录</v-card-title>
<v-card-text>
<v-text-field label="邮箱" v-model="email" type="email"/>
<v-text-field label="密码" v-model="pwd" type="password"/>
</v-card-text>
<v-card-actions>
<v-spacer/>
<v-btn color="primary" @click="login">登录</v-btn>
</v-card-actions>
</v-card>
</v-container>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const email = ref(''); const pwd = ref('')
const login = ()=>{/* 调用 API */}
</script>