# 创建Vue3+TypeScript项目
npm init vite@latest my-vue3-ts-app -- --template vue-ts
# 进入项目目录
cd my-vue3-ts-app
# 安装依赖
npm install
# 启动开发服务器
npm run dev
my-vue3-ts-app/
├── node_modules/ # 依赖包
├── public/ # 静态资源
├── src/ # 源代码
│ ├── assets/ # 静态资源
│ ├── components/ # 组件
│ ├── composables/ # 组合式函数
│ ├── router/ # 路由配置
│ ├── stores/ # 状态管理
│ ├── views/ # 视图
│ ├── App.vue # 根组件
│ ├── main.ts # 入口文件
│ ├── env.d.ts # 环境变量类型定义
├── .eslintrc.cjs # ESLint配置
├── .gitignore # Git忽略配置
├── index.html # 入口HTML
├── package.json # 项目配置
├── tsconfig.json # TypeScript配置
└── vite.config.ts # Vite配置
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"strict": true,
"jsx": "preserve",
"moduleResolution": "Node",
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"lib": ["ESNext", "DOM"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
// src/env.d.ts
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
// 环境变量类型定义
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string
readonly VITE_API_BASE_URL: string
// 更多环境变量...
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
// .eslintrc.cjs
module.exports = {
root: true,
env: {
browser: true,
node: true,
es6: true
},
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true
}
},
extends: [
'plugin:vue/vue3-recommended',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier/skip-formatting'
],
rules: {
'vue/multi-word-component-names': 'off',
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-empty-function': 'off'
}
};
// .prettierrc.cjs
module.exports = {
printWidth: 80,
tabWidth: 2,
useTabs: false,
semi: false,
singleQuote: true,
quoteProps: 'as-needed',
trailingComma: 'es5',
bracketSpacing: true,
arrowParens: 'avoid',
htmlWhitespaceSensitivity: 'strict',
vueIndentScriptAndStyle: true,
endOfLine: 'auto'
};
// src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Home',
component: HomeView
},
{
path: '/about',
name: 'About',
// 路由懒加载
component: () => import('../views/AboutView.vue')
}
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes
})
export default router
// src/stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
title: 'Pinia Counter'
}),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++
},
incrementBy(value: number) {
this.count += value
}
}
})
<!-- src/components/HelloWorld.vue -->
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<button @click="increment">Count: {{ count }}</button>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from 'vue'
import { useCounterStore } from '../stores/counter'
const props = defineProps<{
msg: string
}>()
const counterStore = useCounterStore()
const count = computed(() => counterStore.count)
const increment = () => {
counterStore.increment()
}
onMounted(() => {
console.log('Component mounted')
})
</script>
<style scoped>
h1 {
color: #2c3e50;
}
</style>
// src/composables/useFetch.ts
import { ref, computed, onMounted } from 'vue'
export function useFetch<T>(url: string) {
const data = ref<T | null>(null)
const error = ref<string | null>(null)
const loading = ref<boolean>(true)
const fetchData = async () => {
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
data.value = await response.json()
} catch (err) {
if (err instanceof Error) {
error.value = err.message
} else {
error.value = 'An unexpected error occurred'
}
} finally {
loading.value = false
}
}
onMounted(fetchData)
return {
data,
error,
loading,
refetch: fetchData
}
}
// src/utils/axios.ts
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 5000
})
// 请求拦截器
service.interceptors.request.use(
(config: AxiosRequestConfig) => {
// 可以在这里添加认证信息
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => {
console.error('Request error:', error)
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
return response.data
},
(error) => {
console.error('Response error:', error)
return Promise.reject(error)
}
)
export default service
// package.json 添加测试脚本
{
"scripts": {
"test": "vitest",
"coverage": "vitest run --coverage"
}
}
// vite.config.ts 添加测试配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
environment: 'jsdom',
include: ['**/__tests__/**/*.test.[jt]s?(x)'],
setupFiles: ['./test/setup.ts']
}
})
npm run build
server {
listen 80;
server_name your-domain.com;
root /path/to/your/project/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://backend-api-server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
通过以上步骤,我们完成了一个基于Vue3+TypeScript的项目搭建,包含了:
这个技术方案为你提供了一个从0到1的Vue3+TypeScript项目搭建指南,可以作为企业级应用开发的基础模板。