Install
openclaw skills install vue3-antd-commons-skillVue3全家桶+Ant Design+publish-commons开发技能总结,包含核心特性、TypeScript集成、主题定制、网络请求等内容
openclaw skills install vue3-antd-commons-skill本技能总结涵盖Vue3全家桶与Ant Design结合开发的核心知识,包括Vue3核心特性、TypeScript集成、Ant Design组件库使用、主题定制、网络请求、状态管理等内容,帮助开发者快速掌握Vue3+Ant Design项目开发技能。
<template>
<div>
<p>计数:{{ count }}</p>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
// 响应式数据
const count = ref<number>(0)
// 计算属性
const doubleCount = computed(() => count.value * 2)
// 方法
const increment = () => count.value++
const decrement = () => count.value--
// 生命周期钩子
onMounted(() => {
console.log('组件挂载完成')
})
onUnmounted(() => {
console.log('组件卸载完成')
})
</script>
import { ref, reactive } from 'vue'
// ref - 用于基本类型和对象
const count = ref<number>(0)
const user = ref<User>({ name: '张三', age: 25 })
// reactive - 用于对象
const state = reactive({
count: 0,
user: { name: '张三', age: 25 }
})
// 访问方式
console.log(count.value) // ref需要.value
console.log(state.count) // reactive直接访问
import { ref, reactive, shallowRef, readonly, toRef, toRefs } from 'vue'
// shallowRef - 浅层响应式
const shallowState = shallowRef({ count: 0 })
// readonly - 只读响应式
const readonlyState = readonly(reactive({ count: 0 }))
// toRef - 创建单个ref
const state = reactive({ count: 0 })
const countRef = toRef(state, 'count')
// toRefs - 创建多个ref
const { count } = toRefs(state)
import { onMounted, onUpdated, onUnmounted, onBeforeMount, onBeforeUpdate, onBeforeUnmount } from 'vue'
onBeforeMount(() => {
console.log('组件挂载前')
})
onMounted(() => {
console.log('组件挂载完成')
})
onBeforeUpdate(() => {
console.log('组件更新前')
})
onUpdated(() => {
console.log('组件更新完成')
})
onBeforeUnmount(() => {
console.log('组件卸载前')
})
onUnmounted(() => {
console.log('组件卸载完成')
})
<script setup lang="ts">
// 基本类型
interface Props {
title: string
count?: number
disabled?: boolean
}
const props = withDefaults(defineProps<Props>(), {
count: 0,
disabled: false
})
</script>
<script setup lang="ts">
interface Emits {
(e: 'update', value: number): void
(e: 'submit', data: FormData): void
(e: 'cancel'): void
}
const emit = defineEmits<Emits>()
const handleUpdate = (value: number) => {
emit('update', value)
}
</script>
<script setup lang="ts">
const count = ref(0
const increment = () => count.value++
defineExpose({
count,
increment
})
</script>
import { RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue')
},
{
path: '/user/:id',
name: 'User',
component: () => import('@/views/User.vue'),
props: true
}
]
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
interface User {
id: number
name: string
email: string
}
export const useUserStore = defineStore('user', () => {
const user = ref<User | null>(null)
const isLoggedIn = computed(() => !!user.value)
const setUser = (userData: User) => {
user.value = userData
}
const clearUser = () => {
user.value = null
}
return {
user,
isLoggedIn,
setUser,
clearUser
}
})
<template>
<a-space direction="vertical">
<a-button type="primary" @click="handleClick">主要按钮</a-button>
<a-button type="default">默认按钮</a-button>
<a-button type="dashed">虚线按钮</a-button>
<a-button type="text">文本按钮</a-button>
<a-button type="link">链接按钮</a-button>
</a-space>
</template>
<script setup lang="ts">
const handleClick = () => {
console.log('按钮被点击')
}
</script>
<template>
<a-form
:model="formData"
:rules="rules"
@finish="handleSubmit"
>
<a-form-item label="用户名" name="username">
<a-input v-model:value="formData.username" />
</a-form-item>
<a-form-item label="密码" name="password">
<a-input-password v-model:value="formData.password" />
</a-form-item>
<a-form-item label="邮箱" name="email">
<a-input v-model:value="formData.email" />
</a-form-item>
<a-form-item>
<a-button type="primary" html-type="submit">提交</a-button>
</a-form-item>
</a-form>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
import type { Rule } from 'ant-design-vue/es/form'
interface FormData {
username: string
password: string
email: string
}
const formData = reactive<FormData>({
username: '',
password: '',
email: ''
})
const rules: Record<string, Rule[]> = {
username: [
{ required: true, message: '请输入用户名' }
],
password: [
{ required: true, message: '请输入密码' },
{ min: 6, message: '密码长度不能少于6位' }
],
email: [
{ required: true, message: '请输入邮箱' },
{ type: 'email', message: '请输入有效的邮箱地址' }
]
}
const handleSubmit = (values: FormData) => {
console.log('表单提交', values)
}
</script>
<template>
<a-table
:columns="columns"
:data-source="dataSource"
:loading="loading"
:pagination="pagination"
@change="handleTableChange"
/>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import type { TableProps, ColumnType } from 'ant-design-vue'
interface User {
id: number
name: string
age: number
email: string
}
const columns: ColumnType<User>[] = [
{
title: 'ID',
dataIndex: 'id',
key: 'id'
},
{
title: '姓名',
dataIndex: 'name',
key: 'name'
},
{
title: '年龄',
dataIndex: 'age',
key: 'age'
},
{
title: '邮箱',
dataIndex: 'email',
key: 'email'
}
]
const dataSource = ref<User[]>([
{ id: 1, name: '张三', age: 25, email: 'zhangsan@example.com' },
{ id: 2, name: '李四', age: 30, email: 'lisi@example.com' }
])
const loading = ref(false)
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0
})
const handleTableChange: TableProps['onChange'] = (pag, filters, sorter) => {
console.log('表格变化', pag, filters, sorter)
}
</script>
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue')
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue')
},
{
path: '/user/:id',
name: 'User',
component: () => import('@/views/User.vue'),
props: true
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('@/views/NotFound.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes
})
// 全局前置守卫
router.beforeEach((to, from, next) => {
console.log('路由跳转', to, from)
next()
})
// 全局后置钩子
router.afterEach((to, from) => {
console.log('路由跳转完成', to, from)
})
export default router
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
// 导航到指定路由
const navigateToHome = () => {
router.push('/')
}
// 导航到命名路由
const navigateToUser = (id: number) => {
router.push({ name: 'User', params: { id } })
}
// 导航到带查询参数的路由
const navigateToSearch = (query: string) => {
router.push({ path: '/search', query: { q: query } })
}
// 替换当前路由
const replaceRoute = () => {
router.replace('/')
}
// 后退
const goBack = () => {
router.go(-1)
}
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
const increment = () => {
count.value++
}
const decrement = () => {
count.value--
}
const reset = () => {
count.value = 0
}
return {
count,
doubleCount,
increment,
decrement,
reset
}
})
<template>
<div>
<p>计数:{{ counterStore.count }}</p>
<p>双倍计数:{{ counterStore.doubleCount }}</p>
<button @click="counterStore.increment()">+1</button>
<button @click="counterStore.decrement()">-1</button>
<button @click="counterStore.reset()">重置</button>
</div>
</template>
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
const counterStore = useCounterStore()
</script>
interface RequestConfig extends RequestInit {
timeout?: number
token?: string
}
interface ResponseData<T = any> {
code: number
data: T
message: string
}
class HttpClient {
private baseURL: string
private timeout: number
private token: string | null
constructor(baseURL: string = '', timeout: number = 5000) {
this.baseURL = baseURL
this.timeout = timeout
this.token = localStorage.getItem('token')
}
private async request<T>(url: string, config: RequestConfig = {}): Promise<ResponseData<T>> {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), config.timeout || this.timeout)
try {
const headers: HeadersInit = {
'Content-Type': 'application/json',
...config.headers
}
if (this.token) {
headers['Authorization'] = `Bearer ${this.token}`
}
const response = await fetch(`${this.baseURL}${url}`, {
...config,
headers,
signal: controller.signal
})
clearTimeout(timeoutId)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return await response.json()
} catch (error) {
clearTimeout(timeoutId)
throw error
}
}
get<T>(url: string, config?: RequestConfig): Promise<ResponseData<T>> {
return this.request<T>(url, { ...config, method: 'GET' })
}
post<T>(url: string, data?: any, config?: RequestConfig): Promise<ResponseData<T>> {
return this.request<T>(url, {
...config,
method: 'POST',
body: JSON.stringify(data)
})
}
put<T>(url: string, data?: any, config?: RequestConfig): Promise<ResponseData<T>> {
return this.request<T>(url, {
...config,
method: 'PUT',
body: JSON.stringify(data)
})
}
delete<T>(url: string, config?: RequestConfig): Promise<ResponseData<T>> {
return this.request<T>(url, { ...config, method: 'DELETE' })
}
setToken(token: string) {
this.token = token
localStorage.setItem('token', token)
}
clearToken() {
this.token = null
localStorage.removeItem('token')
}
}
export const http = new HttpClient('https://api.example.com')
import { http } from '@/utils/http'
interface User {
id: number
name: string
email: string
}
// 获取用户列表
export const getUsers = () => {
return http.get<User[]>('/users')
}
// 获取用户详情
export const getUser = (id: number) => {
return http.get<User>(`/users/${id}`)
}
// 创建用户
export const createUser = (data: Partial<User>) => {
return http.post<User>('/users', data)
}
// 更新用户
export const updateUser = (id: number, data: Partial<User>) => {
return http.put<User>(`/users/${id}`, data)
}
// 删除用户
export const deleteUser = (id: number) => {
return http.delete(`/users/${id}`)
}
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
},
build: {
rollupOptions: {
output: {
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'pinia'],
'antd-vendor': ['ant-design-vue']
}
}
}
},
server: {
port: 3000,
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})
// 路由懒加载
const routes = [
{
path: '/',
component: () => import('@/views/Home.vue')
},
{
path: '/about',
component: () => import('@/views/About.vue')
}
]
// 组件懒加载
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() =>
import('@/components/AsyncComponent.vue')
)
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "none",
"printWidth": 100
}
publish-commons是一个基于Vue3和Ant Design Vue的组件库,提供了丰富的业务组件和工具函数,帮助开发者快速构建企业级应用。
# 安装组件库
npm install publish-commons
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import PublishCommons from 'publish-commons'
import 'publish-commons/dist/style.css'
const app = createApp(App)
app.use(PublishCommons)
app.mount('#app')
<template>
<div>
<!-- 使用publish-commons组件 -->
<pc-button type="primary">主要按钮</pc-button>
<pc-table :columns="columns" :data-source="dataSource" />
<pc-form :model="formData" @submit="handleSubmit" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const columns = [
{ title: '姓名', dataIndex: 'name' },
{ title: '年龄', dataIndex: 'age' }
]
const dataSource = ref([
{ name: '张三', age: 25 },
{ name: '李四', age: 30 }
])
const formData = ref({
name: '',
age: 0
})
const handleSubmit = (data: any) => {
console.log('表单提交', data)
}
</script>
import type { PcButtonProps, PcTableProps } from 'publish-commons'
// 按钮组件类型
const buttonProps: PcButtonProps = {
type: 'primary',
size: 'large',
disabled: false
}
// 表格组件类型
const tableProps: PcTableProps = {
columns: [
{ title: '姓名', dataIndex: 'name' },
{ title: '年龄', dataIndex: 'age' }
],
dataSource: [
{ name: '张三', age: 25 },
{ name: '李四', age: 30 }
],
loading: false
}
src/
├── assets/ # 静态资源
├── components/ # 公共组件
├── composables/ # 组合式函数
├── router/ # 路由配置
├── stores/ # Pinia状态管理
├── utils/ # 工具函数
├── views/ # 页面组件
└── App.vue # 根组件
<template>
<div class="user-card">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<button @click="handleEdit">编辑</button>
</div>
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue'
interface User {
id: number
name: string
email: string
}
interface Props {
user: User
}
interface Emits {
(e: 'edit', user: User): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const handleEdit = () => {
emit('edit', props.user)
}
</script>
<style scoped>
.user-card {
padding: 16px;
border: 1px solid #eee;
border-radius: 4px;
}
</style>
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import Counter from '@/components/Counter.vue'
describe('Counter', () => {
it('renders count', () => {
const wrapper = mount(Counter)
expect(wrapper.text()).toContain('计数:0')
})
it('increments count', async () => {
const wrapper = mount(Counter)
const button = wrapper.find('button')
await button.trigger('click')
expect(wrapper.text()).toContain('计数:1')
})
})
命名规范
代码风格
注释规范
本技能总结涵盖了Vue3全家桶与Ant Design结合开发的核心知识,包括:
通过学习这些内容,开发者可以快速掌握Vue3+Ant Design+publish-commons项目开发技能,构建高质量的企业级应用。