用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/dallay/cortex --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.
Manages shadcn-vue components and projects — adding, searching, fixing, debugging, styling, and composing UI. Provides project context, component docs, and usage examples. Applies when working with shadcn-vue, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn-vue init", "create an app with --preset", or "switch to --preset".
Use when asked to improve accessibility, run a11y audit, ensure WCAG compliance, add screen reader support, fix keyboard navigation, or make content accessible.
基于 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 libraryALWAYS 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