用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/dallay/profiletailors.com --skill vue命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Use when writing Playwright tests, fixing flaky tests, debugging failures, implementing Page Object Model, configuring CI/CD, optimizing performance, mocking APIs, handling authentication or OAuth, testing accessibility (axe-core), file uploads/downloads, date/time mocking, WebSockets, geolocation, permissions, multi-tab/popup flows, mobile/responsive layouts, touch gestures, GraphQL, error handling, offline mode, multi-user collaboration, third-party services (payments, email verification), console error monitoring, global setup/teardown, test annotations (skip, fixme, slow), test tags (@smoke, @fast, @critical, filtering with --grep), project dependencies, security testing (XSS, CSRF, auth), performance budgets (Web Vitals, Lighthouse), iframes, component testing, canvas/WebGL, service workers/PWA, test coverage, i18n/localization, Electron apps, or browser extension testing. Covers E2E, component, API, visual, accessibility, security, Electron, and extension testing.
Use when creating features, domain models, use cases, or organizing backend code with Hexagonal Architecture (Ports and Adapters) and CQRS.
Use when bootstrapping a new Spring Boot 4 backend from Spring Initializr, choosing Kotlin + WebFlux + Gradle defaults, defining a hexagonal package-by-feature structure, and wiring local development services for a reactive stack.
基于 SOC 职业分类
正在显示 SKILL.md
| name | vue |
| description | Use when working with .vue files, composables, Pinia stores, or form validation. |
| allowed-tools | Read, Edit, Write, Glob, Grep, Bash |
| metadata | {"author":"profiletailors","version":"1.0"} |
Conventions for Vue 3 development with Composition API, TypeScript, and the profiletailors component ecosystem.
.vue components@profiletailors/ui component libraryThis project follows a component maintainability standard that defines layering, composable extraction, component decomposition, type contracts, and code quality checks.
Read the full guide at:
.agents/skills/frontend-platform/vue/references/vue-component-maintainability.md
Key rules enforced by this standard:
<script setup> exceeds ~80 lines or contains computed/watch logicany, withDefaults for optional propsALWAYS use <script setup lang="ts">:
<script setup lang="ts">
// 1. Imports
import {computed, ref} from 'vue';
import {useUserStore} from '@/stores/user';
// 2. Type definitions
type Props = {
title: string;
count?: number;
isActive?: boolean;
};
// 3. Props with defaults
const props = withDefaults(defineProps<Props>(), {
count: 0,
isActive: false,
});
// 4. Emits with types
const emit = defineEmits<{
(e: 'update', value: number): void;
(e: 'close'): void;
}>();
// 5. Composables and stores
const userStore = useUserStore();
// 6. Reactive state
const localCount = ref(props.count);
// 7. Computed properties
const doubled = computed(() => localCount.value * 2);
// 8. Methods
const increment = () => {
localCount.value++;
emit('update', localCount.value);
};
</script>
<template>
<div class="card">
<h2>{{ title }}</h2>
<p>Count: {{ localCount }} (Doubled: {{ doubled }})</p>
<button @click="increment">Increment</button>
</div>
</template>
<style scoped>
.card {
padding: var(--space-4);
}
</style>
ALWAYS type state, getters, and actions:
// stores/user.ts
import {defineStore} from 'pinia';
import {api, isAxiosError} from '@/api';
import {toast} from 'vue-sonner';
type User = {
id: string;
name: string;
email: string;
};
type UserState = {
currentUser: User | null;
isLoading: boolean;
error: string | null;
};
export const useUserStore = defineStore('user', {
state: (): UserState => ({
currentUser: null,
isLoading: false,
error: null,
}),
getters: {
isAuthenticated: (state): boolean => state.currentUser !== null,
userInitials: (state): string => {
if (!state.currentUser) return '';
state..
.()
.( n[])
.();
},
},
: {
(: ): <> {
. = ;
. = ;
{
response = api.(id);
. = response.;
} (e) {
error = e ? e : ();
message = (e) && e.?.?.
? e...
: error. || ;
. = message;
toast.(, {: message});
} {
. = ;
}
},
(): {
. = ;
},
},
});
Return reactive values, prefix with use:
// composables/useCounter.ts
import {ref, computed, type Ref, type ComputedRef} from 'vue';
type UseCounterReturn = {
count: Ref<number>;
doubled: ComputedRef<number>;
increment: () => void;
decrement: () => void;
reset: () => void;
};
export const useCounter = (initial = 0): UseCounterReturn => {
const count = ref(initial);
const doubled = computed(() => count.value * 2);
const increment = () => count.value++;
const decrement = () => count.value--;
const reset = () => count.value = initial;
return {count, doubled, increment, decrement, reset};
};
CRITICAL: Manual validation on blur, NOT automatic:
<script setup lang="ts">
import {useForm} from 'vee-validate';
import {toTypedSchema} from '@vee-validate/zod';
import {z} from 'zod';
import {
FormField,
FormItem,
FormLabel,
FormControl,
FormMessage,
Input,
Button,
} from '@profiletailors/ui';
const schema = z.object({
email: z.string().email('Invalid email format'),
password: z.string().min(8, 'Must be at least 8 characters'),
});
type FormData = z.infer<typeof schema>;
const {handleSubmit, validateField, resetForm} = useForm<FormData>({
validationSchema: toTypedSchema(schema),
validateOnMount: false, // ✅ CRITICAL: Don't validate on mount
});
const onSubmit = handleSubmit((values) => {
console.log('Form submitted:', values);
});
</script>
<template>
<form @submit="onSubmit">
<FormField v-slot="{ componentField }" name="email">
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<!-- ✅ Manual validation on blur -->
<Input
type="email"
v-bind="componentField"
@blur="validateField('email')"
/>
</FormControl>
<FormMessage/>
</FormItem>
</FormField>
<Button type="submit">Submit</Button>
</form>
</template>
| Scenario | Approach |
|---|---|
| Parent → Child | Props |
| Child → Parent | emit() |
| Sibling/Distant | Pinia store |
| Provide/Inject | Rarely, for deeply nested |
NEVER use global event buses.
Use Shadcn-Vue components from @profiletailors/ui:
<script setup lang="ts">
import {Button, Card, CardHeader, CardTitle, CardContent} from '@profiletailors/ui';
</script>
<template>
<Card>
<CardHeader>
<CardTitle>Dashboard</CardTitle>
</CardHeader>
<CardContent>
<Button variant="default" @click="handleAction">
Take Action
</Button>
</CardContent>
</Card>
</template>
<script setup lang="ts">
import {onUnmounted, shallowRef} from 'vue';
// ✅ Use shallowRef for large objects that don't need deep reactivity
const largeData = shallowRef<LargeObject | null>(null);
// ✅ Clean up side effects
const intervalId = setInterval(() => {
// polling logic
}, 5000);
onUnmounted(() => {
clearInterval(intervalId);
});
</script>
<template>
<!-- ✅ v-once for truly static content -->
<footer v-once>
<p>© 2024 CVIX</p>
</footer>
<!-- ✅ v-memo for expensive lists -->
<div v-for="item in items" :key="item.id" v-memo="[item.id, item.updated]">
{{ item.name }}
</div>
</template>
❌ Options API - Always use Composition API with <script setup>
❌ any type - Always provide proper TypeScript types
❌ Mutating props - Props are read-only, emit events instead
❌ Global event bus - Use Pinia for cross-component state
❌ validate-on-blur prop - Use manual validateField() on @blur
❌ Field injection - Use constructor pattern in stores
<script setup lang="ts">
import {useI18n} from 'vue-i18n';
const {t} = useI18n();
</script>
<template>
<h1>{{ t('dashboard.title') }}</h1>
<p>{{ t('dashboard.welcome', { name: user.name }) }}</p>
</template>
# Development
pnpm --filter @profiletailors/webapp dev
# Testing
pnpm --filter @profiletailors/webapp vitest run
pnpm --filter @profiletailors/webapp vitest --watch
# Type checking
pnpm --filter @profiletailors/webapp vue-tsc --noEmit
# Linting
pnpm --filter @profiletailors/webapp lint