[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6D2H0ZZBJG77MBGPZHA6MWD":3,"skill-same:01M6D2H0ZZBJG77MBGPZHA6MWD":86},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":31,"publishedAt":52,"linkOnly":53,"markdownContent":54,"markdownBody":55,"frontMatter":56,"refName":57,"commitSha":58,"skillDirectory":59,"forksCount":60,"repositoryHtmlUrl":61,"repositoryHomepageUrl":62,"ownerHtmlUrl":63,"ownerBlogUrl":62,"ownerDescription":64,"updatedAt":65,"install":66,"resources":79},"01M6D2H0ZZBJG77MBGPZHA6MWD","ui-stack-vue",null,"Building interfaces in Vue 3 and Nuxt: project layout, script setup with typed defineProps, defineEmits and defineModel, attribute fallthrough, slots and scoped slots, template refs, ref, reactive and computed, watch versus watchEffect, composables and VueUse, provide and inject, Pinia, keyed v-for, transitions, Nuxt 4 (app directory, pages, layouts, useFetch and useAsyncData keys, $fetch in handlers, server routes, runtime config, useSeoMeta, NuxtImg, middleware), VeeValidate forms, Reka UI and Nuxt UI, testing, vue-tsc, and the traps. Read when the project uses Vue or Nuxt.","enowdev\u002Fenowxcli","enowdev","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F179420349?v=4","01M6D1MVX2KC0X3Z9NESE7P22K","Enow Dev.","01M6D1MW1FMZGBDKWAJY4GQX81",67,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-stack-vue\u002FSKILL.md",[18,23,26],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",{"categoryNo":27,"categoryName":28,"taxonomyType":29,"taxonomyTypeDesc":30},"01JBSEEDCAT000000000000032","设计师","PROFESSION","适用职业",[32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51],"ui stack vue","ui","vue","stack","read","pages","watch","runtime","layout","project","transitions","forms","fetch","testing","pinia","nuxt","template","server","config","directory","2026-11-10 10:12:30",false,"---\nname: ui-stack-vue\ndescription: \"Building interfaces in Vue 3 and Nuxt: project layout, script setup with typed defineProps, defineEmits and defineModel, attribute fallthrough, slots and scoped slots, template refs, ref, reactive and computed, watch versus watchEffect, composables and VueUse, provide and inject, Pinia, keyed v-for, transitions, Nuxt 4 (app directory, pages, layouts, useFetch and useAsyncData keys, $fetch in handlers, server routes, runtime config, useSeoMeta, NuxtImg, middleware), VeeValidate forms, Reka UI and Nuxt UI, testing, vue-tsc, and the traps. Read when the project uses Vue or Nuxt.\"\n---\n\n# Vue 3 and Nuxt\n\nGenerated Vue mixes the Options API into `\u003Cscript setup>`, destructures a\n`reactive` object and loses its reactivity, watches one ref to set another,\nmutates props, fetches page data with `$fetch` so a Nuxt page requests it\ntwice, and leaves the starter's green look in place. This is Vue 3.5 with\nNuxt 4 (4.5 in 2026) as written now: typed `\u003Cscript setup>`, composables,\nkeyed data fetching, and the Nuxt built-ins.\n\n## 1. Project layout\n\n```text\nVite (npm create vue@latest)           Nuxt 4\nsrc\u002Fmain.ts  src\u002FApp.vue               app\u002Fapp.vue  app\u002Ferror.vue\nsrc\u002Frouter\u002F  src\u002Fstores\u002F               app\u002Fpages\u002Forders\u002Findex.vue, [id].vue\nsrc\u002Fcomponents\u002Fui\u002F                     app\u002Flayouts\u002Fdefault.vue, dashboard.vue\nsrc\u002Fcomponents\u002Forders\u002F                 app\u002Fcomponents\u002F  app\u002Fcomposables\u002F\nsrc\u002Fcomposables\u002F  src\u002Fviews\u002F           app\u002Fmiddleware\u002F  app\u002Fassets\u002Fcss\u002Fmain.css\n                                       server\u002Fapi\u002Forders.get.ts   server only\n                                       shared\u002F   types both sides use\n```\n\n- Nuxt auto-imports components, composables and Vue's APIs; a Vite project\n  imports them explicitly.\n- Delete the starter: create-vue's `HelloWorld.vue`, `TheWelcome.vue` and\n  its `base.css` and `main.css` (the green links), or `\u003CNuxtWelcome \u002F>`.\n\n## 2. Components\n\n- Single-file components with `\u003Cscript setup lang=\"ts\">`, named for what\n  they are (multi-word: `OrderTable.vue`), one per file.\n- Props with `defineProps\u003C{ ... }>()`, destructured with defaults (3.5);\n  events with typed `defineEmits`; `v-model` on components through\n  `defineModel`. Slots for content, not a prop per variation.\n\n```vue\n\u003C!-- components\u002Fui\u002FTextField.vue -->\n\u003Cscript setup lang=\"ts\">\nimport { useId } from \"vue\";\nconst model = defineModel\u003Cstring>({ required: true });\nconst { label, error } = defineProps\u003C{ label: string; error?: string }>();\nconst id = useId(); \u002F\u002F stable between server and client (3.5)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"field\">\n    \u003Clabel :for=\"id\">{{ label }}\u003C\u002Flabel>\n    \u003Cinput :id=\"id\" v-model=\"model\" :aria-invalid=\"!!error\"\n      :aria-describedby=\"error ? `${id}-error` : undefined\" \u002F>\n    \u003Cp v-if=\"error\" :id=\"`${id}-error`\">{{ error }}\u003C\u002Fp>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\n- Attributes a component does not declare (`class`, `type`, `aria-*`,\n  listeners) fall through to its root element, so a Button stays a button.\n  When the root is a wrapper, `defineOptions({ inheritAttrs: false })` and\n  `v-bind=\"$attrs\"` on the native element.\n- Scoped slots let the caller render each item; `defineSlots` types them\n  and `generic` types the item:\n\n```vue\n\u003Cscript setup lang=\"ts\" generic=\"T extends { id: string }\">\ndefineProps\u003C{ items: T[] }>();\ndefineSlots\u003C{ row(props: { item: T }): any; empty(): any }>();\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cul v-if=\"items.length\">\n    \u003Cli v-for=\"item in items\" :key=\"item.id\">\u003Cslot name=\"row\" :item=\"item\" \u002F>\u003C\u002Fli>\n  \u003C\u002Ful>\n  \u003Cslot v-else name=\"empty\" \u002F>\n\u003C\u002Ftemplate>\n```\n\n- Template refs with `useTemplateRef\u003CHTMLInputElement>(\"search\")` and\n  `ref=\"search\"` (3.5); DOM work in `onMounted`.\n- Styles scoped, or the project's Tailwind; tokens as CSS variables in one\n  place.\n\n## 3. Reactivity\n\n- `ref` by default, for primitives and objects; `computed` for anything\n  derived; `reactive` only for an object never reassigned or destructured\n  (destructuring copies; `toRefs` if you must).\n- Derive with `computed` instead of watching one value to set another:\n\n```ts\n\u002F\u002F not this\nconst fullName = ref(\"\");\nwatch([first, last], () => { fullName.value = `${first.value} ${last.value}`; });\n\u002F\u002F this\nconst fullName = computed(() => `${first.value} ${last.value}`);\n```\n\n- `watch` for effects outside the component (a request, `localStorage`, a\n  chart) on named sources; `watchEffect` when the effect should track\n  whatever it reads. `onWatcherCleanup` (3.5) aborts the previous run.\n- Destructured props are reactive in the template and in `computed`, but a\n  `watch` needs a getter: `watch(() => id, load)`.\n- `shallowRef` for large data replaced whole (an API response, a chart's\n  dataset). `.value` in script, never in the template.\n\n## 4. Shared logic and state\n\n- Logic several components share goes in a composable (`useX`), not a\n  mixin; it accepts refs or getters (`MaybeRefOrGetter`) and reads them\n  with `toValue`. VueUse already has most browser ones (`useMediaQuery`,\n  `useLocalStorage`, `onClickOutside`, `useEventListener`): use them before\n  writing one.\n- `provide` and `inject` with a typed `InjectionKey` for compound components\n  (tabs and their panels, a form and its fields), not for app state.\n- Pinia for shared client state, as setup stores; `storeToRefs` to\n  destructure state and getters (actions destructure directly):\n\n```ts\nexport const useCartStore = defineStore(\"cart\", () => {\n  const items = ref\u003CCartItem[]>([]);\n  const count = computed(() => items.value.reduce((n, item) => n + item.quantity, 0));\n  function add(item: CartItem) { items.value.push(item); }\n  return { items, count, add };\n});\n\u002F\u002F in a component: const cart = useCartStore(); const { count } = storeToRefs(cart);\n```\n\n- Server data is not store state: `useFetch` in Nuxt; TanStack Query for\n  Vue or Pinia Colada in a Vite app (`frontend-data`).\n\n## 5. Templates\n\n- `v-for` with a stable `:key` from the data; never `v-if` and `v-for` on\n  the same element (a `\u003Ctemplate v-for>` around, or a filtered `computed`).\n- Loading, error and empty as three branches in the template.\n- `\u003CTransition>` and `\u003CTransitionGroup>` for purposeful motion, turned off\n  under `prefers-reduced-motion` (`motion-stacks`).\n- `\u003CTeleport to=\"body\">` for an overlay when no library renders it.\n- `v-html` only with sanitised content.\n\n## 6. Nuxt\n\n- `NuxtLink` for internal links, `NuxtImg` or `NuxtPicture` when\n  `@nuxt\u002Fimage` is installed (`width`, `height`, `sizes=\"100vw md:50vw\"`),\n  `@nuxt\u002Ffonts` to self-host the faces the CSS names, `useSeoMeta` or\n  `useHead` for a title and description per page, layouts in `app\u002Flayouts\u002F`\n  chosen with `definePageMeta({ layout: \"dashboard\" })`.\n- Page data with `useFetch` or `useAsyncData`: fetched on the server and\n  handed to the client without a second request.\n\n```vue\n\u003Cscript setup lang=\"ts\">\nconst route = useRoute();\nconst status = computed(() => String(route.query.status ?? \"open\"));\nconst { data: orders, status: load, error, refresh } = await useFetch(\"\u002Fapi\u002Forders\", {\n  query: { status }, \u002F\u002F refetches when it changes\n});\nuseSeoMeta({ title: \"Orders\", description: \"[What this page lists]\" });\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003COrdersSkeleton v-if=\"load === 'pending'\" \u002F>\n  \u003CLoadError v-else-if=\"error\" what=\"orders\" @retry=\"refresh\" \u002F>\n  \u003CNoOrders v-else-if=\"!orders?.length\" \u002F>\n  \u003COrderTable v-else :orders=\"orders\" \u002F>\n\u003C\u002Ftemplate>\n```\n\n- `useAsyncData` takes a key that names its data (`order-42`); in Nuxt 4\n  calls with one key share one state, so different data needs different\n  keys. `lazy: true` (or `useLazyFetch`) lets navigation finish first.\n- `$fetch` in event handlers and actions (a click, a submit), never at the\n  top of `\u003Cscript setup>` for page data, where it runs on the server and\n  again on the client; `useFetch` never inside a handler.\n- Server routes in `server\u002Fapi\u002F` (`orders.get.ts`, `orders.post.ts`) with\n  `defineEventHandler`, validating with `getValidatedQuery` and\n  `readValidatedBody` (`backend-api`).\n- `runtimeConfig`: top-level keys are server-only (`NUXT_API_SECRET`),\n  `public` ones ship to the browser (`NUXT_PUBLIC_SITE_URL`); read with\n  `useRuntimeConfig()`.\n- Route middleware in `app\u002Fmiddleware\u002F` redirects with `navigateTo(\"\u002Flogin\")`;\n  the server route still checks the user on every request.\n- `useState(\"key\", () => ...)` for SSR-safe shared state; `\u003CClientOnly>`\n  or `onMounted` for browser-only parts; `app\u002Ferror.vue` and\n  `createError({ statusCode: 404, statusMessage: \"Order not found\" })` for\n  failures.\n\n## 7. Forms and accessible components\n\n- VeeValidate with a zod schema through `toTypedSchema` from\n  `@vee-validate\u002Fzod` (`frontend-forms`):\n\n```ts\nconst { defineField, handleSubmit, errors, isSubmitting } = useForm({\n  validationSchema: toTypedSchema(z.object({ email: z.string().email(\"Enter an email like name@example.com\") })),\n});\nconst [email, emailAttrs] = defineField(\"email\");\nconst onSubmit = handleSubmit((values) => $fetch(\"\u002Fapi\u002Finvites\", { method: \"POST\", body: values }));\n```\n\n  In the template: `\u003Cform @submit=\"onSubmit\">`, the input with\n  `v-model=\"email\" v-bind=\"emailAttrs\"`, `errors.email` under the field\n  with `aria-describedby`, and the button `:disabled=\"isSubmitting\"`.\n- With Nuxt UI, `UForm` takes the schema and the state directly and\n  `UFormField` wires the label, hint and error.\n- Accessible widgets from Reka UI (headless, formerly Radix Vue),\n  shadcn-vue (Reka UI with Tailwind), Nuxt UI v4 (Reka UI and Tailwind v4)\n  or Headless UI for Vue; PrimeVue for data-heavy screens. Themed from the\n  tokens: Nuxt UI's colours in `app.config.ts` (`ui.colors`, green and\n  slate by default) and a `@theme static` scale for the brand colour.\n\n## 8. Testing\n\nVitest with `@vue\u002Ftest-utils` (`mount`, then\n`await wrapper.get(\"button\").trigger(\"click\")` and `wrapper.emitted()`) or\n`@testing-library\u002Fvue` (queries by role); `@nuxt\u002Ftest-utils` and\n`mountSuspended` for components that use Nuxt composables; Playwright for\nflows (`frontend-testing`).\n\n## 9. Traps\n\n- Destructuring `reactive()` or a store loses reactivity: `toRefs`,\n  `storeToRefs`. Reassigning a `reactive` object breaks every reference to\n  it: use `ref`.\n- Watchers instead of computed; mutating props, including an object prop's\n  fields (emit, or `defineModel`).\n- `v-if` with `v-for` on one element; index keys on lists that change.\n- `$fetch` for page data in setup, `useFetch` in a handler, two\n  `useAsyncData` calls sharing a key for different data.\n- `window`, `document` or `localStorage` read during setup, which runs on\n  the server: `onMounted`, `import.meta.client`, or `\u003CClientOnly>`.\n- Class instances or functions in `useState` or Pinia state: the SSR\n  payload cannot carry them.\n- A date or random value rendered on the server and again on the client:\n  format with a fixed locale and time zone, or put `data-allow-mismatch`\n  (3.5) on that one element.\n\n## Check it\n\n- Types: `npm run type-check` (create-vue runs `vue-tsc --build`), or\n  `npx nuxt typecheck`; then `npm run build`, `npm run lint` and\n  `npx vitest run`.\n- Look at the result with the `preview` tool: with Vite, `url`\n  `http:\u002F\u002Flocalhost:5173\u002F` and `start` `npm run dev -- --port 5173\n  --strictPort`; with Nuxt, `url` `http:\u002F\u002Flocalhost:3000\u002F` and `start`\n  `npm run dev -- --port 3000`.\n- Hydration mismatches in the console the `preview` report lists are bugs.\n\n## Avoid\n\nOptions API and `\u003Cscript setup>` mixed in one component; destructured\n`reactive` state; `watch` to derive a value; mutated props; `v-if` with\n`v-for`; `$fetch` for page data at setup; shared `useAsyncData` keys; a\nstore for server data; browser APIs in setup; hand-rolled dialogs and\nmenus; Nuxt UI left on its default green and slate; the starter's\ncomponents and colours left in place.\n","# Vue 3 and Nuxt\n\nGenerated Vue mixes the Options API into `\u003Cscript setup>`, destructures a\n`reactive` object and loses its reactivity, watches one ref to set another,\nmutates props, fetches page data with `$fetch` so a Nuxt page requests it\ntwice, and leaves the starter's green look in place. This is Vue 3.5 with\nNuxt 4 (4.5 in 2026) as written now: typed `\u003Cscript setup>`, composables,\nkeyed data fetching, and the Nuxt built-ins.\n\n## 1. Project layout\n\n```text\nVite (npm create vue@latest)           Nuxt 4\nsrc\u002Fmain.ts  src\u002FApp.vue               app\u002Fapp.vue  app\u002Ferror.vue\nsrc\u002Frouter\u002F  src\u002Fstores\u002F               app\u002Fpages\u002Forders\u002Findex.vue, [id].vue\nsrc\u002Fcomponents\u002Fui\u002F                     app\u002Flayouts\u002Fdefault.vue, dashboard.vue\nsrc\u002Fcomponents\u002Forders\u002F                 app\u002Fcomponents\u002F  app\u002Fcomposables\u002F\nsrc\u002Fcomposables\u002F  src\u002Fviews\u002F           app\u002Fmiddleware\u002F  app\u002Fassets\u002Fcss\u002Fmain.css\n                                       server\u002Fapi\u002Forders.get.ts   server only\n                                       shared\u002F   types both sides use\n```\n\n- Nuxt auto-imports components, composables and Vue's APIs; a Vite project\n  imports them explicitly.\n- Delete the starter: create-vue's `HelloWorld.vue`, `TheWelcome.vue` and\n  its `base.css` and `main.css` (the green links), or `\u003CNuxtWelcome \u002F>`.\n\n## 2. Components\n\n- Single-file components with `\u003Cscript setup lang=\"ts\">`, named for what\n  they are (multi-word: `OrderTable.vue`), one per file.\n- Props with `defineProps\u003C{ ... }>()`, destructured with defaults (3.5);\n  events with typed `defineEmits`; `v-model` on components through\n  `defineModel`. Slots for content, not a prop per variation.\n\n```vue\n\u003C!-- components\u002Fui\u002FTextField.vue -->\n\u003Cscript setup lang=\"ts\">\nimport { useId } from \"vue\";\nconst model = defineModel\u003Cstring>({ required: true });\nconst { label, error } = defineProps\u003C{ label: string; error?: string }>();\nconst id = useId(); \u002F\u002F stable between server and client (3.5)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"field\">\n    \u003Clabel :for=\"id\">{{ label }}\u003C\u002Flabel>\n    \u003Cinput :id=\"id\" v-model=\"model\" :aria-invalid=\"!!error\"\n      :aria-describedby=\"error ? `${id}-error` : undefined\" \u002F>\n    \u003Cp v-if=\"error\" :id=\"`${id}-error`\">{{ error }}\u003C\u002Fp>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\n- Attributes a component does not declare (`class`, `type`, `aria-*`,\n  listeners) fall through to its root element, so a Button stays a button.\n  When the root is a wrapper, `defineOptions({ inheritAttrs: false })` and\n  `v-bind=\"$attrs\"` on the native element.\n- Scoped slots let the caller render each item; `defineSlots` types them\n  and `generic` types the item:\n\n```vue\n\u003Cscript setup lang=\"ts\" generic=\"T extends { id: string }\">\ndefineProps\u003C{ items: T[] }>();\ndefineSlots\u003C{ row(props: { item: T }): any; empty(): any }>();\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cul v-if=\"items.length\">\n    \u003Cli v-for=\"item in items\" :key=\"item.id\">\u003Cslot name=\"row\" :item=\"item\" \u002F>\u003C\u002Fli>\n  \u003C\u002Ful>\n  \u003Cslot v-else name=\"empty\" \u002F>\n\u003C\u002Ftemplate>\n```\n\n- Template refs with `useTemplateRef\u003CHTMLInputElement>(\"search\")` and\n  `ref=\"search\"` (3.5); DOM work in `onMounted`.\n- Styles scoped, or the project's Tailwind; tokens as CSS variables in one\n  place.\n\n## 3. Reactivity\n\n- `ref` by default, for primitives and objects; `computed` for anything\n  derived; `reactive` only for an object never reassigned or destructured\n  (destructuring copies; `toRefs` if you must).\n- Derive with `computed` instead of watching one value to set another:\n\n```ts\n\u002F\u002F not this\nconst fullName = ref(\"\");\nwatch([first, last], () => { fullName.value = `${first.value} ${last.value}`; });\n\u002F\u002F this\nconst fullName = computed(() => `${first.value} ${last.value}`);\n```\n\n- `watch` for effects outside the component (a request, `localStorage`, a\n  chart) on named sources; `watchEffect` when the effect should track\n  whatever it reads. `onWatcherCleanup` (3.5) aborts the previous run.\n- Destructured props are reactive in the template and in `computed`, but a\n  `watch` needs a getter: `watch(() => id, load)`.\n- `shallowRef` for large data replaced whole (an API response, a chart's\n  dataset). `.value` in script, never in the template.\n\n## 4. Shared logic and state\n\n- Logic several components share goes in a composable (`useX`), not a\n  mixin; it accepts refs or getters (`MaybeRefOrGetter`) and reads them\n  with `toValue`. VueUse already has most browser ones (`useMediaQuery`,\n  `useLocalStorage`, `onClickOutside`, `useEventListener`): use them before\n  writing one.\n- `provide` and `inject` with a typed `InjectionKey` for compound components\n  (tabs and their panels, a form and its fields), not for app state.\n- Pinia for shared client state, as setup stores; `storeToRefs` to\n  destructure state and getters (actions destructure directly):\n\n```ts\nexport const useCartStore = defineStore(\"cart\", () => {\n  const items = ref\u003CCartItem[]>([]);\n  const count = computed(() => items.value.reduce((n, item) => n + item.quantity, 0));\n  function add(item: CartItem) { items.value.push(item); }\n  return { items, count, add };\n});\n\u002F\u002F in a component: const cart = useCartStore(); const { count } = storeToRefs(cart);\n```\n\n- Server data is not store state: `useFetch` in Nuxt; TanStack Query for\n  Vue or Pinia Colada in a Vite app (`frontend-data`).\n\n## 5. Templates\n\n- `v-for` with a stable `:key` from the data; never `v-if` and `v-for` on\n  the same element (a `\u003Ctemplate v-for>` around, or a filtered `computed`).\n- Loading, error and empty as three branches in the template.\n- `\u003CTransition>` and `\u003CTransitionGroup>` for purposeful motion, turned off\n  under `prefers-reduced-motion` (`motion-stacks`).\n- `\u003CTeleport to=\"body\">` for an overlay when no library renders it.\n- `v-html` only with sanitised content.\n\n## 6. Nuxt\n\n- `NuxtLink` for internal links, `NuxtImg` or `NuxtPicture` when\n  `@nuxt\u002Fimage` is installed (`width`, `height`, `sizes=\"100vw md:50vw\"`),\n  `@nuxt\u002Ffonts` to self-host the faces the CSS names, `useSeoMeta` or\n  `useHead` for a title and description per page, layouts in `app\u002Flayouts\u002F`\n  chosen with `definePageMeta({ layout: \"dashboard\" })`.\n- Page data with `useFetch` or `useAsyncData`: fetched on the server and\n  handed to the client without a second request.\n\n```vue\n\u003Cscript setup lang=\"ts\">\nconst route = useRoute();\nconst status = computed(() => String(route.query.status ?? \"open\"));\nconst { data: orders, status: load, error, refresh } = await useFetch(\"\u002Fapi\u002Forders\", {\n  query: { status }, \u002F\u002F refetches when it changes\n});\nuseSeoMeta({ title: \"Orders\", description: \"[What this page lists]\" });\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003COrdersSkeleton v-if=\"load === 'pending'\" \u002F>\n  \u003CLoadError v-else-if=\"error\" what=\"orders\" @retry=\"refresh\" \u002F>\n  \u003CNoOrders v-else-if=\"!orders?.length\" \u002F>\n  \u003COrderTable v-else :orders=\"orders\" \u002F>\n\u003C\u002Ftemplate>\n```\n\n- `useAsyncData` takes a key that names its data (`order-42`); in Nuxt 4\n  calls with one key share one state, so different data needs different\n  keys. `lazy: true` (or `useLazyFetch`) lets navigation finish first.\n- `$fetch` in event handlers and actions (a click, a submit), never at the\n  top of `\u003Cscript setup>` for page data, where it runs on the server and\n  again on the client; `useFetch` never inside a handler.\n- Server routes in `server\u002Fapi\u002F` (`orders.get.ts`, `orders.post.ts`) with\n  `defineEventHandler`, validating with `getValidatedQuery` and\n  `readValidatedBody` (`backend-api`).\n- `runtimeConfig`: top-level keys are server-only (`NUXT_API_SECRET`),\n  `public` ones ship to the browser (`NUXT_PUBLIC_SITE_URL`); read with\n  `useRuntimeConfig()`.\n- Route middleware in `app\u002Fmiddleware\u002F` redirects with `navigateTo(\"\u002Flogin\")`;\n  the server route still checks the user on every request.\n- `useState(\"key\", () => ...)` for SSR-safe shared state; `\u003CClientOnly>`\n  or `onMounted` for browser-only parts; `app\u002Ferror.vue` and\n  `createError({ statusCode: 404, statusMessage: \"Order not found\" })` for\n  failures.\n\n## 7. Forms and accessible components\n\n- VeeValidate with a zod schema through `toTypedSchema` from\n  `@vee-validate\u002Fzod` (`frontend-forms`):\n\n```ts\nconst { defineField, handleSubmit, errors, isSubmitting } = useForm({\n  validationSchema: toTypedSchema(z.object({ email: z.string().email(\"Enter an email like name@example.com\") })),\n});\nconst [email, emailAttrs] = defineField(\"email\");\nconst onSubmit = handleSubmit((values) => $fetch(\"\u002Fapi\u002Finvites\", { method: \"POST\", body: values }));\n```\n\n  In the template: `\u003Cform @submit=\"onSubmit\">`, the input with\n  `v-model=\"email\" v-bind=\"emailAttrs\"`, `errors.email` under the field\n  with `aria-describedby`, and the button `:disabled=\"isSubmitting\"`.\n- With Nuxt UI, `UForm` takes the schema and the state directly and\n  `UFormField` wires the label, hint and error.\n- Accessible widgets from Reka UI (headless, formerly Radix Vue),\n  shadcn-vue (Reka UI with Tailwind), Nuxt UI v4 (Reka UI and Tailwind v4)\n  or Headless UI for Vue; PrimeVue for data-heavy screens. Themed from the\n  tokens: Nuxt UI's colours in `app.config.ts` (`ui.colors`, green and\n  slate by default) and a `@theme static` scale for the brand colour.\n\n## 8. Testing\n\nVitest with `@vue\u002Ftest-utils` (`mount`, then\n`await wrapper.get(\"button\").trigger(\"click\")` and `wrapper.emitted()`) or\n`@testing-library\u002Fvue` (queries by role); `@nuxt\u002Ftest-utils` and\n`mountSuspended` for components that use Nuxt composables; Playwright for\nflows (`frontend-testing`).\n\n## 9. Traps\n\n- Destructuring `reactive()` or a store loses reactivity: `toRefs`,\n  `storeToRefs`. Reassigning a `reactive` object breaks every reference to\n  it: use `ref`.\n- Watchers instead of computed; mutating props, including an object prop's\n  fields (emit, or `defineModel`).\n- `v-if` with `v-for` on one element; index keys on lists that change.\n- `$fetch` for page data in setup, `useFetch` in a handler, two\n  `useAsyncData` calls sharing a key for different data.\n- `window`, `document` or `localStorage` read during setup, which runs on\n  the server: `onMounted`, `import.meta.client`, or `\u003CClientOnly>`.\n- Class instances or functions in `useState` or Pinia state: the SSR\n  payload cannot carry them.\n- A date or random value rendered on the server and again on the client:\n  format with a fixed locale and time zone, or put `data-allow-mismatch`\n  (3.5) on that one element.\n\n## Check it\n\n- Types: `npm run type-check` (create-vue runs `vue-tsc --build`), or\n  `npx nuxt typecheck`; then `npm run build`, `npm run lint` and\n  `npx vitest run`.\n- Look at the result with the `preview` tool: with Vite, `url`\n  `http:\u002F\u002Flocalhost:5173\u002F` and `start` `npm run dev -- --port 5173\n  --strictPort`; with Nuxt, `url` `http:\u002F\u002Flocalhost:3000\u002F` and `start`\n  `npm run dev -- --port 3000`.\n- Hydration mismatches in the console the `preview` report lists are bugs.\n\n## Avoid\n\nOptions API and `\u003Cscript setup>` mixed in one component; destructured\n`reactive` state; `watch` to derive a value; mutated props; `v-if` with\n`v-for`; `$fetch` for page data at setup; shared `useAsyncData` keys; a\nstore for server data; browser APIs in setup; hand-rolled dialogs and\nmenus; Nuxt UI left on its default green and slate; the starter's\ncomponents and colours left in place.\n",{"name":5,"description":7},"main","38293fe1b096e6bf3646c7344c38786ed688f464","crates\u002Fenowx-core\u002Fskills\u002Fui-stack-vue",21,"https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli","https:\u002F\u002Fenowx.ai","https:\u002F\u002Fgithub.com\u002Fenowdev","Pretending to be a Developer","2026-10-06 02:05:35",{"source":8,"skillName":5,"commands":67,"prompt":77,"zipAvailable":78},[68,71,74],{"tool":69,"command":70},"npx","npx skills add enowdev\u002Fenowxcli --skill ui-stack-vue",{"tool":72,"command":73},"bunx","bunx skills add enowdev\u002Fenowxcli --skill ui-stack-vue",{"tool":75,"command":76},"pnpm","pnpm dlx skills add enowdev\u002Fenowxcli --skill ui-stack-vue","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-stack-vue\u002FSKILL.md",true,[80],{"path":81,"resourceType":82,"resourceTypeDesc":83,"sizeBytes":84,"sourceHtmlUrl":85},"references\u002Fapi_reference.md","REFERENCE","参考文档",13339,"https:\u002F\u002Fgithub.com\u002Ffadrienne\u002FMyGitHub\u002Fblob\u002Fmain\u002Fopenscience\u002Fbackend\u002Fcli\u002Fskills\u002Fdatabases\u002Fena-database\u002Freferences\u002Fapi_reference.md",[87,100,123,135,162,184],{"skillNo":88,"name":89,"nameZh":6,"description":90,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":91,"categories":92,"keywords":96,"publishedAt":52},"01M6D2HQYSQD9N7MTNDT4A7CBM","demo-skill","Project-scope demo skill for tests.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fworkspace\u002F.agents\u002Fskills\u002Fdemo-skill\u002FSKILL.md",[93],{"categoryNo":94,"categoryName":95,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[97,98,99,41,36],"demo skill","demo","tests",{"skillNo":101,"name":102,"nameZh":6,"description":103,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":104,"categories":105,"keywords":112,"publishedAt":52},"01M6D2HM0GCPM0TM2HPG9HHR2R","writing","Writing words people read: the specific thing instead of claims, words to drop, sentences, interface copy (buttons, labels, errors, empty states, confirmations), page copy, notifications and emails, alt text and link text, numbers and dates, words that translate well, documentation and voice. Read before writing a page's copy, an interface's text or a document.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fwriting\u002FSKILL.md",[106,109],{"categoryNo":107,"categoryName":108,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":110,"categoryName":111,"taxonomyType":29,"taxonomyTypeDesc":30},"01JBSEEDCAT000000000000036","内容创作者",[102,113,114,115,116,117,36,118,119,120,121,122],"text","emails","empty","drop","document","page","voice","notifications","documentation","translate",{"skillNo":124,"name":33,"nameZh":6,"description":125,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":126,"categories":127,"keywords":129,"publishedAt":52},"01M6D2HG07YS49NM196Q0R3AZ8","Designing or restyling an interface: direction, layout, type, colour, icons, components, states, responsive behaviour. Read before building a page, a screen or a set of components.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui\u002FSKILL.md",[128],{"categoryNo":27,"categoryName":28,"taxonomyType":29,"taxonomyTypeDesc":30},[33,130,131,132,133,118,40,36,134],"icons","components","SET","responsive","before building",{"skillNo":136,"name":137,"nameZh":6,"description":138,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":139,"categories":140,"keywords":147,"publishedAt":52},"01M6D2HB59HZHWG1RYZDV4SJ91","ui-themes","Light and dark themes, and colour that holds in both: tokens with a value per theme, and what each kind of content needs to stay visible (icons, SVG drawings and line art, logos, photos, screenshots, charts, code, maps, borders, shadows, focus, form controls), switching without a flash, and checking both. Read before building a second theme or a theme toggle, and before any drawing, logo or chart on a page that has two themes.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-themes\u002FSKILL.md",[141,144],{"categoryNo":142,"categoryName":143,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":145,"categoryName":146,"taxonomyType":29,"taxonomyTypeDesc":30},"01JBSEEDCAT000000000000030","开发工程师",[33,148,149,150,151,152,153,154,155,36,134,156,118,157,158,159,130,160,161],"ui themes","switching","photos","tokens","drawing","focus","maps","Art","flash","code","Charts","content","screenshots","logo",{"skillNo":163,"name":164,"nameZh":6,"description":165,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":166,"categories":167,"keywords":171,"publishedAt":52},"01M6D2H5R5KRAE524C8QX0T7Q0","ui-structure","Structure before styling: the blueprint written before any interface code (the screen's job, the regions its kind of page needs, their order and why, the layout skeleton in numbers, the components and their states), the anatomy and order of a homepage, a dashboard, a list, a detail page, settings, a form, sign-in, pricing, docs, an article and a checkout, and how to turn the blueprint into code (tokens, layout primitives, components, sections, page). Read first, before building or rebuilding any page or screen.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-structure\u002FSKILL.md",[168,169,170],{"categoryNo":107,"categoryName":108,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":145,"categoryName":146,"taxonomyType":29,"taxonomyTypeDesc":30},{"categoryNo":110,"categoryName":111,"taxonomyType":29,"taxonomyTypeDesc":30},[172,33,173,131,174,175,157,176,177,178,40,151,179,180,181,36,134,118,182,183],"structure","ui structure","pricing","dashboard","Blueprint","article","anatomy","styling","primitives","list","why","how",{"skillNo":185,"name":186,"nameZh":6,"description":187,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":188,"categories":189,"keywords":190,"publishedAt":52},"01M6D2GW10WYJMQ5MP0YWWX00Y","ui-stack-tailwind","Building with Tailwind CSS v4 (and v3) without the Tailwind-default look: setup per framework (Vite, Next.js, Astro, Laravel, monorepos), @theme tokens in OKLCH named by role, dark mode with @custom-variant, the layout system as tokens (one container, section spacing, a type scale, stretched rows) and how to fix each preview layout line with classes, state, aria, data, group, peer, has, motion and container-query variants, component recipes (button, field, card, navigation, table, dialog) with every state, motion with --animate-* and starting:, cn with tailwind-merge, cva, class order, @utility, arbitrary values, typography, v3 to v4 migration, and the generated look. Read when the project uses Tailwind.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-stack-tailwind\u002FSKILL.md",[],[33,191,35,192,193,40,41,194,195,196,197,198,199,200,201,183,202,203,204,205],"Tailwind","ui stack tailwind","default","preview","next","query","aria","fix","generated","dialog","laravel","astro","merge","component","css"]