组件开发
Zap Admin Vue3采用组件化开发模式,提供丰富的可复用组件,并支持自定义组件开发。
组件目录结构
src/components/
├── common/ # 通用基础组件
│ ├── Button.vue
│ ├── Icon.vue
│ └── ...
├── form/ # 表单相关组件
│ ├── FormItem.vue
│ ├── FormGenerator.vue
│ └── ...
├── table/ # 表格相关组件
│ ├── Table.vue
│ ├── TableColumn.vue
│ └── ...
├── business/ # 业务组件
│ ├── UserCard.vue
│ ├── DataCard.vue
│ └── ...
├── layout/ # 布局组件
│ ├── Sidebar.vue
│ ├── Navbar.vue
│ └── ...
└── index.js # 组件全局注册
组件开发规范
命名规范
- 组件文件名使用PascalCase命名法,如
MyComponent.vue
- 组件名与文件名保持一致
- 基础组件以Base前缀开头,如
BaseButton.vue
- 业务组件以功能模块为前缀,如
UserAvatar.vue
代码结构
<template>
<!-- 组件模板 -->
</template>
<script setup>
// 组件逻辑
</script>
<style scoped>
/* 组件样式 */
</style>
组件设计原则
- 单一职责 - 每个组件只做一件事
- 可复用性 - 通过props控制组件行为
- 可组合性 - 组件可以相互组合使用
- 明确接口 - 定义清晰的props和events
- 样式隔离 - 使用scoped样式避免冲突
组件示例
基础按钮组件
<template>
<button
class="base-button"
:class="[
type ? 'base-button--' + type : '',
size ? 'base-button--' + size : '',
{
'is-disabled': disabled,
'is-loading': loading,
'is-plain': plain,
'is-round': round,
'is-circle': circle
}
]"
:disabled="disabled || loading"
@click="handleClick"
>
<span v-if="loading" class="base-button__loading">
<svg class="animate-spin" viewBox="0 0 20 20">
<path d="..." />
</svg>
</span>
<slot />
</button>
</template>
<script setup>
const props = defineProps({
type: {
type: String,
default: 'default',
validator: (value) => {
return ['default', 'primary', 'success', 'warning', 'danger', 'info'].includes(value)
}
},
size: {
type: String,
default: 'medium',
validator: (value) => {
return ['medium', 'small', 'mini'].includes(value)
}
},
disabled: Boolean,
loading: Boolean,
plain: Boolean,
round: Boolean,
circle: Boolean
})
const emit = defineEmits(['click'])
const handleClick = (evt) => {
emit('click', evt)
}
</script>
<style scoped lang="scss">
.base-button {
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 1;
white-space: nowrap;
cursor: pointer;
transition: all 0.3s;
user-select: none;
border: 1px solid transparent;
border-radius: 4px;
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
&--primary {
color: #fff;
background-color: var(--color-primary);
border-color: var(--color-primary);
&:hover {
background-color: lighten(var(--color-primary), 10%);
border-color: lighten(var(--color-primary), 10%);
}
}
&--medium {
padding: 10px 20px;
font-size: 14px;
}
&--small {
padding: 8px 16px;
font-size: 12px;
}
&--mini {
padding: 6px 12px;
font-size: 12px;
}
&__loading {
margin-right: 6px;
animation: rotate 2s linear infinite;
}
&.is-disabled {
opacity: 0.6;
cursor: not-allowed;
}
&.is-loading {
pointer-events: none;
}
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>
组件全局注册
在 src/components/index.js
中全局注册常用组件:
import { defineAsyncComponent } from 'vue'
// 自动导入components目录下的所有组件
const components = globalThis._importMeta_.glob('./**/*.vue')
export default {
install(app) {
Object.entries(components).forEach(([path, component]) => {
const name = path
.split('/')
.pop()
.replace(/\.\w+$/, '')
// 注册组件
app.component(
name,
defineAsyncComponent(component)
)
})
}
}
组件测试
使用Vitest进行组件单元测试:
// tests/components/Button.spec.js
import { mount } from '@vue/test-utils'
import BaseButton from '@/components/common/BaseButton.vue'
describe('BaseButton.vue', () => {
it('renders button with default props', () => {
const wrapper = mount(BaseButton, {
slots: {
default: 'Click me'
}
})
expect(wrapper.text()).toContain('Click me')
expect(wrapper.classes()).toContain('base-button')
expect(wrapper.classes()).toContain('base-button--default')
expect(wrapper.classes()).toContain('base-button--medium')
})
it('emits click event when clicked', async () => {
const wrapper = mount(BaseButton)
await wrapper.trigger('click')
expect(wrapper.emitted()).toHaveProperty('click')
})
it('shows loading spinner when loading prop is true', () => {
const wrapper = mount(BaseButton, {
props: {
loading: true
}
})
expect(wrapper.find('.base-button__loading').exists()).toBe(true)
})
})
组件文档
使用VuePress或Storybook为组件编写文档:
// docs/components/Button.md
# BaseButton
基础按钮组件,支持多种类型、尺寸和状态。
## 基本用法
```vue
<template>
<BaseButton @click="handleClick">点击我</BaseButton>
</template>
```
## Props
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|------|------|------|------|------|
| type | 按钮类型 | string | primary/success/warning/danger/info | default |
| size | 按钮尺寸 | string | medium/small/mini | medium |
| loading | 是否加载中 | boolean | - | false |
| disabled | 是否禁用 | boolean | - | false |
## Events
| 事件名 | 说明 | 回调参数 |
|------|------|------|
| click | 点击事件 | event |