一键导入
vue-frontend
Vue 3 Composition API + TypeScript patterns. Load when building components, composables, Pinia state, or reviewing Vue
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Vue 3 Composition API + TypeScript patterns. Load when building components, composables, Pinia state, or reviewing Vue
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
External API evaluation and MCP server wrapper generation. Load when integrating a REST/GraphQL API as a workspace tool
Capability-based provider system patterns. Load when creating providers or wiring module-provider integration
Full-stack WebSocket subscription development. Load when adding WS routes, topic services, or debugging WS data flows
Systematic Python type error resolution (mypy/pyright). Load when fixing backend type check failures
Type-first Python with Pydantic, Protocol, and discriminated unions. Load when writing models or enforcing typing
Systematic type error resolution for Python (mypy/pyright) and TypeScript (vue-tsc). Use when fixing type errors, resolving type check failures, or optimizing type imports.
| name | vue-frontend |
| description | Vue 3 Composition API + TypeScript patterns. Load when building components, composables, Pinia state, or reviewing Vue |
| user-invocable | false |
Standards and patterns for Vue 3 applications using Composition API, TypeScript, Pinia, and Vite — covering component design, state management, performance, data fetching, and testing.
Always use <script setup lang="ts"> syntax:
<script setup lang="ts">
import { ref, computed } from 'vue'
interface Props {
title: string
variant?: 'primary' | 'secondary'
}
const props = withDefaults(defineProps<Props>(), {
variant: 'primary',
})
const emit = defineEmits<{
close: []
submit: [value: string]
}>()
const inputValue = ref('')
const isValid = computed(() => inputValue.value.length > 0)
</script>
<template>
<div :class="['card', `card--${props.variant}`]">
<h2>{{ title }}</h2>
<slot />
</div>
</template>
<style scoped>
.card { /* component styles */ }
</style>
Component design rules:
withDefaults + defineProps<T>() for typed props with defaultsdefineEmits<T>() with named tuple syntax for typed eventsExtract shared logic into composables/ directory:
// composables/useFetch.ts
import { ref, type Ref } from 'vue'
interface UseFetchReturn<T> {
data: Ref<T | null>
error: Ref<string | null>
loading: Ref<boolean>
execute: () => Promise<void>
}
export function useFetch<T>(url: string | Ref<string>): UseFetchReturn<T> {
const data = ref<T | null>(null) as Ref<T | null>
const error = ref<string | null>(null)
const loading = ref(false)
async function execute() {
loading.value = true
error.value = null
try {
const resolvedUrl = typeof url === 'string' ? url : url.value
const response = await fetch(resolvedUrl)
if (!response.ok) throw new Error(`HTTP ${response.status}`)
data.value = await response.json()
} catch (e) {
error.value = e instanceof Error ? e.message : 'Unknown error'
} finally {
loading.value = false
}
}
return { data, error, loading, execute }
}
Composable rules:
use (e.g., useFetch, useAuth, useWebSocket)onUnmounted or watch cleanup callbackswatch / watchEffect with precise dependency listsprovide / inject sparingly for deep dependency injectionStructure stores by domain, not by data shape:
// stores/orders.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useOrderStore = defineStore('orders', () => {
// State
const orders = ref<Order[]>([])
const loading = ref(false)
// Getters (computed)
const pendingOrders = computed(() =>
orders.value.filter(o => o.status === 'pending')
)
const orderCount = computed(() => orders.value.length)
// Actions
async function fetchOrders() {
loading.value = true
try {
orders.value = await orderApi.getAll()
} finally {
loading.value = false
}
}
function addOrder(order: Order) {
orders.value.push(order)
}
return { orders, loading, pendingOrders, orderCount, fetchOrders, addOrder }
})
State management rules:
ref() for simple state, reactive() only for complex nested objectscomputed() for all derived state — never derive in templates| Technique | When to Use |
|---|---|
defineAsyncComponent | Lazy-load heavy components not needed on initial render |
v-once | Static content that never changes |
v-memo | List items where re-render is expensive and deps are known |
shallowRef / shallowReactive | Large objects where deep reactivity is unnecessary |
<Suspense> | Async component loading with fallback UI |
computed over watch | When derived value is sufficient (no side effects needed) |
Lazy loading example:
import { defineAsyncComponent } from 'vue'
const HeavyChart = defineAsyncComponent(() =>
import('./components/HeavyChart.vue')
)
Avoid these performance traps:
computed valuesv-if + v-for on the same element (use computed filtered list)key on v-for listsimport { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
component: () => import('./views/HomeView.vue'), // code splitting
},
{
path: '/trading',
component: () => import('./views/TradingView.vue'),
meta: { requiresAuth: true },
},
],
})
// Navigation guard
router.beforeEach((to) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
return { path: '/login' }
}
})
Routing rules:
meta fields for route metadata (auth, breadcrumbs)useRoute() and useRouter() in <script setup> — never this.$route<script setup lang="ts">
import { ref } from 'vue'
const email = ref('')
const emailError = ref<string | null>(null)
function validateEmail() {
if (!email.value) {
emailError.value = 'Email is required'
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value)) {
emailError.value = 'Invalid email format'
} else {
emailError.value = null
}
}
function handleSubmit() {
validateEmail()
if (emailError.value) return
// submit logic
}
</script>
<template>
<form @submit.prevent="handleSubmit" novalidate>
<label for="email">Email</label>
<input
id="email"
v-model="email"
type="email"
autocomplete="email"
:aria-invalid="!!emailError"
:aria-describedby="emailError ? 'email-error' : undefined"
@blur="validateEmail"
/>
<p v-if="emailError" id="email-error" role="alert">
{{ emailError }}
</p>
<button type="submit">Submit</button>
</form>
</template>
Form rules:
v-model for controlled bindingsfor / aria-describedby// Global error handler (main.ts)
app.config.errorHandler = (err, instance, info) => {
console.error('Vue error:', err, info)
// report to error tracking service
}
Component-level:
onErrorCaptured for local error boundariestry/catch with user-friendly messagesstrict: true in tsconfig.jsondefineProps<T>() — never defineProps({}) with runtime validationref<string>(''), ref<Order | null>(null)defineEmits<{ update: [value: string] }>()type imports for interfaces: import type { Order } from '@/types'useFetch<T>(url)<style scoped> for component styles — prevents leakage<script setup>v-html without sanitization — XSS vector; sanitize rigorously or avoidthis.$refs in setup — Use template refs with ref<HTMLElement | null>(null)computed when no side effects are needed