| name | vue-service-creator |
| description | name: vue-service-creator Use when this capability is needed. |
name: vue-service-creator
description: |
【Vue服务脚手架】快速创建 Vue 3 / Nuxt 3 前端项目,支持 Composition API、Pinia、Vue Router。
触发时机:
- 用户要求"创建Vue项目"、"Nuxt项目"
- 需要搭建 Vue 3 前端项目
生成完整项目结构、配置文件和示例代码。
category: development
Vue Service Creator — Vue 3 / Nuxt 3 项目脚手架
生成标准化 Vue 3 / Nuxt 3 项目,内置 Composition API、Pinia、TypeScript 最佳实践。
Goal
快速创建 Vue 3 / Nuxt 3 前端项目,支持 Composition API、Pinia、Vue Router
Trigger
1. Vue 3 vs Nuxt 3 选择指南
| 场景 | 推荐框架 | 理由 |
|---|
| SPA 后台管理系统 | Vue 3 + Vite | 轻量、灵活、构建快 |
| 内容站点 / 博客 | Nuxt 3 | SSR/SSG、SEO 友好 |
| 企业官网 | Nuxt 3 | 预渲染、路由自动生成 |
| 移动端 H5 | Vue 3 + Vite | 纯前端、打包体积小 |
| 全栈应用 | Nuxt 3 | 内置 server routes、API 层 |
| 组件库 / SDK | Vue 3 + Vite | 纯库模式、无框架依赖 |
2. 技术栈总览
| 层次 | Vue 3 方案 | Nuxt 3 方案 |
|---|
| 构建工具 | Vite 6 | Nuxt 内置 (Vite) |
| 状态管理 | Pinia | Pinia (内置支持) |
| 路由 | Vue Router 4 | 文件系统路由 (自动) |
| 样式 | Tailwind CSS / UnoCSS | Tailwind CSS / UnoCSS |
| 测试 | Vitest + Vue Test Utils | Vitest + Vue Test Utils |
| HTTP | ofetch / axios | useFetch / $fetch |
| 表单 | VeeValidate / FormKit | VeeValidate / FormKit |
| UI 库 | Element Plus / Naive UI | Element Plus / Naive UI |
3. 项目结构
Vue 3 + Vite
{project-name}/
├── src/
│ ├── api/ # API 服务层
│ │ ├── request.ts # HTTP 客户端封装
│ │ ├── user.ts # 用户相关 API
│ │ └── index.ts
│ ├── assets/ # 静态资源
│ │ ├── images/
│ │ └── styles/
│ │ └── main.css
│ ├── components/ # 通用组件
│ │ ├── ui/ # 基础 UI 组件
│ │ │ ├── BaseButton.vue
│ │ │ ├── BaseInput.vue
│ │ │ └── index.ts
│ │ └── layout/ # 布局组件
│ │ ├── AppHeader.vue
│ │ ├── AppSidebar.vue
│ │ └── AppLayout.vue
│ ├── composables/ # 组合式函数
│ │ ├── useAuth.ts
│ │ ├── useApi.ts
│ │ └── index.ts
│ ├── pages/ # 页面组件
│ │ ├── Home.vue
│ │ ├── Login.vue
│ │ └── Dashboard.vue
│ ├── router/ # 路由配置
│ │ ├── index.ts
│ │ └── guards.ts
│ ├── stores/ # Pinia 状态
│ │ ├── modules/
│ │ │ ├── auth.ts
│ │ │ └── app.ts
│ │ └── index.ts
│ ├── types/ # TypeScript 类型
│ │ ├── api.d.ts
│ │ ├── env.d.ts
│ │ └── index.ts
│ ├── utils/ # 工具函数
│ │ ├── storage.ts
│ │ └── format.ts
│ ├── App.vue
│ └── main.ts
├── public/
├── tests/
│ ├── setup.ts
│ └── components/
├── .env.example
├── .eslintrc.cjs
├── .prettierrc
├── components.d.ts # unplugin-vue-components 自动生成
├── auto-imports.d.ts # unplugin-auto-import 自动生成
├── tailwind.config.ts
├── tsconfig.json
├── vite.config.ts
├── package.json
└── README.md
Nuxt 3
{project-name}/
├── app.vue # 根组件
├── error.vue # 错误页面
├── assets/
│ └── css/
│ └── main.css
├── components/ # 自动导入组件
│ ├── ui/
│ │ ├── BaseButton.vue
│ │ └── BaseInput.vue
│ └── layout/
│ ├── AppHeader.vue
│ └── AppSidebar.vue
├── composables/ # 自动导入组合式函数
│ ├── useAuth.ts
│ └── useApi.ts
├── layouts/ # 布局模板
│ ├── default.vue
│ └── auth.vue
├── middleware/ # 路由中间件
│ ├── auth.ts
│ └── guest.ts
├── pages/ # 文件系统路由
│ ├── index.vue
│ ├── login.vue
│ ├── dashboard/
│ │ ├── index.vue
│ │ └── settings.vue
│ └── users/
│ ├── index.vue
│ └── [id].vue
├── plugins/ # 插件
│ └── api.ts
├── public/ # 静态资源
├── server/ # 服务端
│ ├── api/ # API 路由
│ │ ├── auth/
│ │ │ ├── login.post.ts
│ │ │ └── me.get.ts
│ │ └── users/
│ │ ├── index.get.ts
│ │ └── [id].get.ts
│ ├── middleware/ # 服务端中间件
│ │ └── auth.ts
│ ├── plugins/
│ └── utils/
│ └── db.ts
├── stores/ # Pinia stores
│ ├── auth.ts
│ └── app.ts
├── types/
│ └── index.d.ts
├── tests/
├── nuxt.config.ts
├── tailwind.config.ts
├── tsconfig.json
├── package.json
└── README.md
4. 配置文件
vite.config.ts (Vue 3)
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
import { fileURLToPath, URL } from 'node:url';
export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
resolvers: [ElementPlusResolver()],
dts: 'auto-imports.d.ts',
}),
Components({
resolvers: [ElementPlusResolver()],
dts: 'components.d.ts',
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
: {
: ,
: {
: {
: ,
: ,
: path.(, ),
},
},
},
: {
: {
: {
: {
: [, , ],
},
},
},
},
});
nuxt.config.ts (Nuxt 3)
export default defineNuxtConfig({
devtools: { enabled: true },
modules: [
'@pinia/nuxt',
'@nuxtjs/tailwindcss',
'@vueuse/nuxt',
],
css: ['~/assets/css/main.css'],
runtimeConfig: {
jwtSecret: process.env.JWT_SECRET || '',
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api',
},
},
routeRules: {
'/dashboard/**': { ssr: false },
'/': { prerender: true },
'/api/**': { cors: true },
},
nitro: {
compressPublicAssets: true,
},
app: {
head: {
title: 'My Nuxt App',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
],
},
: { : , : },
},
: {
: ,
},
});
tsconfig.json
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"jsx": "preserve",
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"noEmit": true,
"paths":
5. Composition API 核心模式
ref 与 reactive
import { ref, reactive, toRefs } from 'vue';
const count = ref(0);
const user = ref<User | null>(null);
const form = reactive({
username: '',
password: '',
remember: false,
});
const { username, password } = toRefs(form);
computed
import { computed } from 'vue';
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
const fullNameRW = computed({
get: () => `${firstName.value} ${lastName.value}`,
set: (val: string) => {
const [first, last] = val.split(' ');
firstName.value = first;
lastName.value = last;
},
});
watch 与 watchEffect
import { watch, watchEffect } from 'vue';
watch(
() => route.params.id,
async (newId) => {
if (newId) await fetchUser(Number(newId));
},
{ immediate: true }
);
watchEffect(async () => {
const data = await fetchUser(userId.value);
user.value = data;
});
生命周期钩子
import { onMounted, onUnmounted, onUpdated } from 'vue';
onMounted(() => {
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
});
6. Pinia 状态管理
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import { apiClient } from '@/api/request';
interface User {
id: number;
username: string;
email: string;
role: string;
}
export const useAuthStore = defineStore('auth', () => {
const user = ref<User | null>(null);
const token = ref<string>(localStorage.getItem('token') || '');
const isAuthenticated = computed(() => !!token.value);
const isAdmin = computed(() => user.value?.role === 'admin');
async function login(username: , : ) {
{ data } = apiClient.(, {
username,
password,
});
token. = data.;
user. = data.;
.(, data.);
}
() {
(!token.) ;
{
{ data } = apiClient.();
user. = data;
} {
();
}
}
() {
user. = ;
token. = ;
.();
}
{
user,
token,
isAuthenticated,
isAdmin,
login,
fetchCurrentUser,
logout,
};
});
7. 路由配置
Vue Router (Vue 3)
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router';
const routes: RouteRecordRaw[] = [
{
path: '/',
component: () => import('@/components/layout/AppLayout.vue'),
children: [
{ path: '', name: 'home', component: () => import('@/pages/Home.vue') },
{
path: 'dashboard',
name: 'dashboard',
component: () => import('@/pages/Dashboard.vue'),
meta: { requiresAuth: true },
},
{
path: 'users',
children: [
{ path: '', name: 'user-list', component: () => import('@/pages/users/UserList.vue') },
{ path: ':id', name: 'user-detail', component: () => import() },
],
},
],
},
{ : , : , : () },
{ : , : , : () },
];
router = ({
: (),
routes,
});
router;
路由守卫
import type { Router } from 'vue-router';
import { useAuthStore } from '@/stores/modules/auth';
export function setupRouteGuards(router: Router) {
router.beforeEach(async (to, _from, next) => {
const authStore = useAuthStore();
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
next({ name: 'login', query: { redirect: to.fullPath } });
} else if (to.meta.guestOnly && authStore.isAuthenticated) {
next({ name: 'home' });
} else {
next();
}
});
}
Nuxt 文件路由
pages/
├── index.vue → /
├── about.vue → /about
├── login.vue → /login
├── users/
│ ├── index.vue → /users
│ ├── [id].vue → /users/:id
│ └── [id]/
│ └── edit.vue → /users/:id/edit
└── [...slug].vue → /* (catch-all)
Nuxt 中间件:
export default defineNuxtRouteMiddleware((to, from) => {
const authStore = useAuthStore();
if (!authStore.isAuthenticated) {
return navigateTo('/login', { redirectCode: 302 });
}
});
8. 组件模式
Slots 插槽
<!-- BaseCard.vue -->
<template>
<div class="rounded-lg border bg-white p-4 shadow-sm">
<header v-if="$slots.header" class="mb-3 border-b pb-2">
<slot name="header" />
</header>
<main>
<slot /> <!-- 默认插槽 -->
</main>
<footer v-if="$slots.footer" class="mt-3 border-t pt-2">
<slot name="footer" />
</footer>
</div>
</template>
<!-- 使用 -->
<BaseCard>
<template #header>
<h2>用户信息</h2>
</template>
<p>姓名:{{ user.name }}</p>
<template #footer>
<BaseButton @click="edit">编辑</BaseButton>
</template>
</BaseCard>
provide / inject
import { provide, ref } from 'vue';
const theme = ref<'light' | 'dark'>('light');
provide('theme', theme);
provide('toggleTheme', () => {
theme.value = theme.value === 'light' ? 'dark' : 'light';
});
import { inject } from 'vue';
const theme = inject<Ref<'light' | 'dark'>>('theme');
const toggleTheme = inject<() => void>('toggleTheme');
Composables 组合式函数
import { ref } from 'vue';
import type { Ref } from 'vue';
export function useApi<T>(apiFn: (...args: any[]) => Promise<T>) {
const data: Ref<T | null> = ref(null);
const loading = ref(false);
const error = ref<Error | null>(null);
async function execute(...args: any[]) {
loading.value = true;
error.value = null;
try {
data.value = await apiFn(...args);
return data.value;
} catch (err) {
error.value = err as Error;
throw err;
} finally {
loading.value = false;
}
}
{ data, loading, error, execute };
}
9. 测试配置
import { defineConfig } from 'vitest/config';
import vue from '@vitejs/plugin-vue';
import { fileURLToPath } from 'node:url';
export default defineConfig({
plugins: [vue()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./tests/setup.ts'],
include: ['tests/**/*.test.ts', 'src/**/*.test.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
},
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
});
import { config } from '@vue/test-utils';
config.global.mocks = {
$t: (key: string) => key,
$route: { path: '/' },
};
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import BaseButton from '@/components/ui/BaseButton.vue';
describe('BaseButton', () => {
it('renders slot content', () => {
const wrapper = mount(BaseButton, {
slots: { default: 'Click me' },
});
expect(wrapper.text()).toBe('Click me');
});
it('emits click event', async () => {
const wrapper = mount(BaseButton);
await wrapper.trigger('click');
expect(wrapper.emitted('click')).toHaveLength(1);
});
it('is disabled when loading', () => {
const wrapper = mount(BaseButton, {
props: { loading: },
});
(wrapper.()).();
});
});
10. 样式方案
Tailwind CSS 配置
import type { Config } from 'tailwindcss';
export default {
content: [
'./index.html',
'./src/**/*.{vue,ts,tsx}',
],
theme: {
extend: {
colors: {
primary: { 50: '#eff6ff', 500: '#3b82f6', 600: '#2563eb', 700: '#1d4ed8' },
},
},
},
plugins: [require('@tailwindcss/forms')],
} satisfies Config;
UnoCSS 配置(替代方案)
import { defineConfig, presetUno, presetAttributify, presetIcons } from 'unocss';
export default defineConfig({
presets: [
presetUno(),
presetAttributify(),
presetIcons({ scale: 1.2 }),
],
shortcuts: {
'btn': 'px-4 py-2 rounded-md font-medium transition-colors',
'btn-primary': 'btn bg-blue-600 text-white hover:bg-blue-700',
'btn-secondary': 'btn bg-gray-200 text-gray-900 hover:bg-gray-300',
},
});
11. 快速使用示例
# 创建 Vue 3 项目
创建一个 Vue 3 + Vite 项目,用于企业后台管理系统,使用 Element Plus + Pinia
# 创建 Nuxt 3 项目
创建一个 Nuxt 3 项目,用于技术博客,支持 SSR 和暗黑模式
# 添加功能模块
为项目添加用户管理模块,包含列表、详情、编辑页面
# 生成组件
生成一个 DataTable 组件,支持排序、分页、筛选,基于 Composition API
# 生成 composable
写一个 usePagination composable,支持分页逻辑复用
# 生成 store
写一个 useProductStore,包含商品的 CRUD 操作
参考资料
Source: afine907/skills — distributed by TomeVault.