【DS】vue3项目数据配置架构
【问】
开发一个vue3项目,如何配置兼顾以下需求:
1、dev环境下后端数据用mock配置,随时可调试
2、随时可以build,但build后后端数据采用本地静态数据。
【答】
针对你的需求,这里提供一个完整的 Vue3 项目配置方案。这个方案使用不同的环境变量来控制数据源,实现开发环境用 mock,生产环境用静态数据。
项目结构
project/
├── src/
│ ├── api/
│ │ ├── index.js # 统一API入口
│ │ ├── mock/ # Mock数据
│ │ │ ├── user.js
│ │ │ └── product.js
│ │ ├── local/ # 本地静态数据
│ │ │ ├── user.js
│ │ │ └── product.js
│ │ └── services/ # 真实API服务(可选)
│ ├── plugins/
│ │ └── api.js # API插件配置
│ └── main.js
├── public/
│ └── data/ # 构建后的静态数据文件
├── .env.development
├── .env.production
├── .env.local
└── vue.config.js
1. 环境变量配置
.env.development (开发环境 - 使用 mock)
VUE_APP_API_SOURCE=mock
VUE_APP_API_BASE_URL=http://localhost:3000/api
.env.production (生产环境 - 使用静态数据)
VUE_APP_API_SOURCE=local
VUE_APP_API_BASE_URL=/data
NODE_ENV=production
.env.local (本地调试 - 可切换到真实API)
VUE_APP_API_SOURCE=real # 可选:mock, local, real
VUE_APP_API_BASE_URL=http://real-api.com/api
2. 数据层配置
src/api/index.js - 统一API入口
import mockAPI from './mock'
import localAPI from './local'
import realAPI from './services'
const apiSources = {
mock: mockAPI,
local: localAPI,
real: realAPI
}
// 根据环境变量选择数据源
const apiSource = process.env.VUE_APP_API_SOURCE || 'mock'
export default apiSources[apiSource]
src/api/mock/user.js - Mock 数据示例
import Mock from 'mockjs' // 可以使用 mockjs 或直接返回数据
export default {
// 模拟用户列表
getUsers() {
return Promise.resolve({
code: 200,
data: [
{ id: 1, name: '张三', age: 25 },
{ id: 2, name: '李四', age: 30 }
]
})
},
// 模拟用户详情
getUserById(id) {
return Promise.resolve({
code: 200,
data: { id, name: '模拟用户', age: 28 }
})
}
}
src/api/local/user.js - 本地静态数据
// 本地静态数据
const localData = {
users: [
{ id: 1, name: '静态用户1', age: 25 },
{ id: 2, name: '静态用户2', age: 30 }
]
}
export default {
// 获取用户列表
getUsers() {
return Promise.resolve({
code: 200,
data: localData.users
})
},
// 获取用户详情
getUserById(id) {
const user = localData.users.find(u => u.id === id)
return Promise.resolve({
code: user ? 200 : 404,
data: user || null
})
}
}
3. Vue 插件配置
src/plugins/api.js - API插件
import api from '../api'
export default {
install(app) {
// 全局注入API
app.config.globalProperties.$api = api
// 提供注入方式
app.provide('api', api)
}
}
src/main.js - 主入口
import { createApp } from 'vue'
import App from './App.vue'
import apiPlugin from './plugins/api'
const app = createApp(App)
// 注册API插件
app.use(apiPlugin)
app.mount('#app')
4. 组件中使用示例
src/components/UserList.vue
<template>
<div>
<h2>用户列表</h2>
<ul>
<li v-for="user in users" :key="user.id">
{{ user.name }} - {{ user.age }}
</li>
</ul>
</div>
</template>
<script setup>
import { ref, onMounted, inject } from 'vue'
const api = inject('api') // 使用依赖注入
const users = ref([])
onMounted(async () => {
const response = await api.user.getUsers()
if (response.code === 200) {
users.value = response.data
}
})
</script>
5. 构建配置
vue.config.js - Vue CLI配置
const path = require('path')
module.exports = {
// 构建时复制静态数据到public目录
chainWebpack: config => {
// 开发环境配置
config.when(process.env.NODE_ENV === 'development', config => {
config.plugin('define').tap(args => {
args[0]['process.env'].VUE_APP_API_SOURCE = JSON.stringify('mock')
return args
})
})
// 生产环境配置
config.when(process.env.NODE_ENV === 'production', config => {
config.plugin('define').tap(args => {
args[0]['process.env'].VUE_APP_API_SOURCE = JSON.stringify('local')
return args
})
})
},
// 开发服务器代理配置(如果需要)
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
},
// 开发环境mock服务
setupMiddlewares: (middlewares, devServer) => {
if (!devServer) {
throw new Error('webpack-dev-server is not defined')
}
// 可以在这里添加mock中间件
// devServer.app.use('/api', mockMiddleware)
return middlewares
}
}
}
6. 使用 Vite 的配置(如果使用 Vite)
vite.config.js
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '')
return {
plugins: [vue()],
// 环境变量配置
define: {
'process.env.VUE_APP_API_SOURCE': JSON.stringify(
mode === 'production' ? 'local' : 'mock'
)
},
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
},
build: {
// 构建时将静态数据复制到dist目录
assetsDir: 'data',
rollupOptions: {
// 如果有额外的静态数据文件需要复制
// input: {
// main: resolve(__dirname, 'index.html'),
// },
}
}
}
})
7. 快速切换数据源的脚本
package.json 中添加脚本
{
"scripts": {
"serve": "vue-cli-service serve",
"serve:mock": "VUE_APP_API_SOURCE=mock vue-cli-service serve",
"serve:local": "VUE_APP_API_SOURCE=local vue-cli-service serve",
"serve:real": "VUE_APP_API_SOURCE=real vue-cli-service serve",
"build": "vue-cli-service build",
"build:mock": "VUE_APP_API_SOURCE=mock vue-cli-service build",
"build:local": "VUE_APP_API_SOURCE=local vue-cli-service build"
}
}
8. 高级功能:自动切换
如果需要更智能的切换,可以创建一个配置文件:
src/config/api.config.js
// 根据环境自动选择API源
const getApiSource = () => {
if (import.meta.env?.PROD) {
return 'local' // 生产环境用静态数据
}
// 开发环境可以根据需要配置
const configSource = import.meta.env?.VUE_APP_API_SOURCE
return configSource || 'mock' // 默认使用mock
}
export const API_SOURCE = getApiSource()
export const API_BASE_URL = import.meta.env?.VUE_APP_API_BASE_URL || ''
使用建议
- 开发时:使用
npm run serve或npm run serve:mock启动mock服务 - 调试静态数据:使用
npm run serve:local验证静态数据 - 构建测试:
npm run build会使用本地静态数据构建 - 特殊构建:
npm run build:mock可以构建带mock数据的版本(用于某些测试场景)
这种架构的好处:
- 清晰的关注点分离
- 易于切换数据源
- 便于测试和调试
- 构建后的包不包含mock逻辑,体积更小
- 可以根据需要轻松扩展新的数据源
【问】考虑到在远程服务器上为项目提供预览,如下考量和结构是最合理的吗?
【答】
你的结构基本合理,但针对远程服务器预览和静态数据部署的场景,我建议进行一些优化。以下是改进方案:
🔧 优化后的项目结构
project/
├── src/
│ ├── api/
│ │ ├── index.ts # API入口:根据环境选择数据源
│ │ ├── types/ # TypeScript类型定义
│ │ │ ├── user.ts
│ │ │ └── product.ts
│ │ ├── adapters/ # 数据适配器(核心)
│ │ │ ├── mock.adapter.ts # Mock适配器
│ │ │ ├── static.adapter.ts # 静态数据适配器
│ │ │ └── real.adapter.ts # 真实API适配器
│ │ └── services/ # API函数定义
│ │ ├── user.service.ts
│ │ └── product.service.ts
│ ├── plugins/
│ │ └── api.ts # API插件
│ └── main.ts
├── public/
│ ├── static-data/ # 构建后的静态JSON文件(自动生成)
│ │ ├── users.json
│ │ └── products.json
│ └── api-mock/ # 用于预览的Mock API(可选的纯静态方案)
│ ├── users/
│ │ ├── 1.json
│ │ └── index.json
│ └── products/
│ └── index.json
├── scripts/
│ ├── generate-static-data.js # 自动生成静态数据
│ └── start-preview.js # 启动预览服务器
├── .env
├── .env.development
├── .env.preview # 新增:预览环境
├── .env.production
└── vite.config.ts
🎯 关键改进点
1. 环境配置细化
.env.preview(预览环境)
# 预览环境配置
VITE_APP_ENV=preview
VITE_APP_TITLE=预览环境
VITE_API_SOURCE=static # 预览时使用静态数据
VITE_API_BASE_URL=./static-data # 相对路径加载JSON
VITE_USE_MOCK=false
NODE_ENV=production
2. 数据适配器模式
src/api/adapters/static.adapter.ts - 静态数据适配器
import type { ApiAdapter } from './base.adapter'
export class StaticAdapter implements ApiAdapter {
private baseUrl: string
constructor(baseUrl: string = './static-data') {
this.baseUrl = baseUrl
}
async request<T>(endpoint: string, options?: RequestInit): Promise<T> {
// 静态数据适配逻辑
const response = await fetch(`${this.baseUrl}/${endpoint}.json`)
if (!response.ok) {
throw new Error(`静态数据加载失败: ${endpoint}`)
}
return response.json()
}
// 特殊处理:分页、查询等
async getUsers(params?: any) {
const data = await this.request<{ users: any[] }>('users')
// 在这里实现简单的分页/过滤逻辑
return this.applyFilters(data.users, params)
}
private applyFilters(data: any[], params: any) {
// 简单的客户端过滤
let result = [...data]
if (params?.page && params?.pageSize) {
const start = (params.page - 1) * params.pageSize
result = result.slice(start, start + params.pageSize)
}
if (params?.name) {
result = result.filter(item =>
item.name.includes(params.name)
)
}
return result
}
}
3. 构建时生成静态数据
scripts/generate-static-data.js
const fs = require('fs-extra')
const path = require('path')
async function generateStaticData() {
const mockDir = path.join(__dirname, '../src/api/mock')
const outputDir = path.join(__dirname, '../public/static-data')
// 清空并创建目录
await fs.emptyDir(outputDir)
// 从mock文件生成静态数据
const users = require(path.join(mockDir, 'user.js')).default
const products = require(path.join(mockDir, 'product.js')).default
// 生成JSON文件
await fs.writeJson(
path.join(outputDir, 'users.json'),
await users.getUsers(),
{ spaces: 2 }
)
await fs.writeJson(
path.join(outputDir, 'products.json'),
await products.getProducts(),
{ spaces: 2 }
)
console.log('✅ 静态数据生成完成!')
}
// package.json中添加脚本
// "scripts": {
// "build:preview": "node scripts/generate-static-data.js && vite build --mode preview"
// }
4. 预览服务器配置
scripts/start-preview.js(可选,用于纯静态预览)
const express = require('express')
const serveStatic = require('serve-static')
const path = require('path')
const app = express()
const port = 4173
// 静态文件服务
app.use(serveStatic(path.join(__dirname, '../dist'), {
index: ['index.html']
}))
// Mock API路由(如果需要)
app.get('/api/users', (req, res) => {
const data = require(path.join(__dirname, '../public/api-mock/users/index.json'))
res.json(data)
})
app.listen(port, () => {
console.log(`🚀 预览服务器运行在: http://localhost:${port}`)
})
5. 智能API选择器
src/api/index.ts - 核心选择逻辑
import { MockAdapter } from './adapters/mock.adapter'
import { StaticAdapter } from './adapters/static.adapter'
import { RealAdapter } from './adapters/real.adapter'
import type { ApiAdapter } from './adapters/base.adapter'
// 环境检测
const getAdapter = (): ApiAdapter => {
const env = import.meta.env
// 1. 命令行参数优先
const urlParams = new URLSearchParams(window.location.search)
if (urlParams.has('mock')) {
console.log('🎭 使用Mock模式(URL参数)')
return new MockAdapter()
}
// 2. 环境变量
const apiSource = env.VITE_API_SOURCE
switch (apiSource) {
case 'mock':
console.log('🎭 使用Mock模式')
return new MockAdapter()
case 'static':
console.log('📄 使用静态数据模式')
return new StaticAdapter(env.VITE_API_BASE_URL)
case 'real':
console.log('🌐 使用真实API模式')
return new RealAdapter(env.VITE_API_BASE_URL)
default:
// 3. 自动检测
if (env.DEV) {
console.log('⚡ 开发环境,使用Mock模式')
return new MockAdapter()
} else {
console.log('📦 生产环境,使用静态数据模式')
return new StaticAdapter()
}
}
}
// 导出适配后的API
const adapter = getAdapter()
export const api = {
user: {
getUsers: (params) => adapter.getUsers(params),
getUserById: (id) => adapter.get(`users/${id}`)
},
product: {
getProducts: (params) => adapter.getProducts(params)
}
}
6. Vite配置优化
vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { viteMockServe } from 'vite-plugin-mock'
export default defineConfig(({ mode, command }) => {
const env = loadEnv(mode, process.cwd(), '')
return {
plugins: [
vue(),
// 只在开发环境启用mock
viteMockServe({
enable: command === 'serve' && mode === 'development',
mockPath: 'src/api/mock'
})
],
// 预览环境特殊配置
build: {
outDir: 'dist',
assetsDir: 'assets',
// 预览环境不压缩,便于调试
minify: mode === 'preview' ? false : 'terser',
sourcemap: mode === 'preview',
rollupOptions: {
output: {
// 静态数据文件单独目录
assetFileNames: (assetInfo) => {
if (assetInfo.name?.endsWith('.json')) {
return 'static-data/[name][extname]'
}
return 'assets/[name]-[hash][extname]'
}
}
}
},
server: {
port: 5173,
proxy: mode === 'development' && env.VITE_API_SOURCE === 'real' ? {
'/api': {
target: env.VITE_API_BASE_URL,
changeOrigin: true
}
} : undefined
}
}
})
7. package.json脚本
{
"scripts": {
"dev": "vite --mode development",
"dev:mock": "vite --mode development",
"dev:real": "vite --mode development --env-file .env.local",
"build": "vite build --mode production",
"build:preview": "npm run generate-static && vite build --mode preview",
"preview": "vite preview --port 4173",
"preview:static": "node scripts/start-preview.js",
"generate-static": "node scripts/generate-static-data.js",
"deploy:preview": "npm run build:preview && scp -r dist/* user@server:/path/to/preview"
}
}
🚀 远程服务器部署流程
# 1. 生成预览版本(包含静态数据)
npm run build:preview
# 2. 部署到服务器
npm run deploy:preview
# 3. 或者使用更简单的部署(生成zip包)
npm run build:preview && zip -r preview.zip dist/
📊 不同环境的配置总结
| 环境 | 数据源 | 构建体积 | 部署方式 | 用途 |
|---|---|---|---|---|
| 开发 | Mock API | 小 | 本地 | 快速开发调试 |
| 预览 | 静态JSON | 中等 | 远程服务器 | 给客户/产品预览 |
| 生产 | 真实API | 最小 | CDN | 正式上线 |
✅ 你的结构分析及改进建议
你的结构优势:
- ✅ 清晰的目录分离
- ✅ 环境变量控制切换
- ✅ 支持多种数据源
可改进点:
- 静态数据管理:建议使用构建脚本生成,而非手动维护
- 预览环境配置:需要专门的预览环境配置
- 数据适配器模式:统一接口,便于扩展
- 构建优化:静态数据单独处理,不污染构建包
- 远程部署:考虑静态服务器的路由问题(SPA需要处理404)
🎯 最终建议
对于远程预览服务器,最佳实践是:
# 1. 使用专门的预览环境
VITE_API_SOURCE=static
# 2. 构建时自动生成静态数据
npm run build:preview
# 3. 部署到支持SPA的静态服务器(如Nginx)
# Nginx配置添加:
# try_files $uri $uri/ /index.html;
# 4. 或者使用Vercel/Netlify等现代部署平台
# 它们自动处理SPA路由问题
这样配置后,你的项目可以:
- 🚀 快速开发调试(Mock模式)
- 🌐 随时构建预览版本(静态数据)
- 📦 一键部署到远程服务器
- 🔧 无缝切换到真实API
【补充:不懂就问】不懂就问,既然有了services层,为啥又要adapters层呢,是为了给services的api请求添加额外功能吗?
【答】
问得非常好!这是一个关于架构设计的核心问题。让我详细解释这两层的区别和设计目的。
📊 核心区别:关注点分离
Services 层:做什么 (What to do)
// services/user.service.ts
class UserService {
// 定义业务逻辑接口
async getUsers(params?: PaginationParams): Promise<User[]> {
// 这里不关心数据从哪里来
// 只定义需要获取用户数据
}
async createUser(userData: CreateUserDto): Promise<User> {
// 定义创建用户的业务逻辑
}
}
职责:
- 定义业务接口(函数签名)
- 声明需要的数据和参数
- 业务逻辑的抽象定义
Adapters 层:怎么做 (How to do it)
// adapters/mock.adapter.ts
class MockUserAdapter implements UserAdapter {
async getUsers(params?: PaginationParams): Promise<User[]> {
// 具体实现:从Mock数据获取
return mockData.users
}
}
// adapters/real.adapter.ts
class RealUserAdapter implements UserAdapter {
async getUsers(params?: PaginationParams): Promise<User[]> {
// 具体实现:从真实API获取
return axios.get('/api/users', { params })
}
}
职责:
- 具体的数据获取实现
- 处理不同数据源的差异
- 数据格式转换
🎯 为什么需要两层?
场景1:切换数据源时
// ❌ 不好的设计:业务逻辑和数据获取耦合
class UserService {
async getUsers() {
// 直接调用axios
if (process.env.USE_MOCK) {
return mockData // Mock逻辑
} else {
return axios.get('/api/users') // 真实API逻辑
}
}
}
// ✅ 好的设计:分离关注点
// Service只定义接口
interface IUserService {
getUsers(): Promise<User[]>
}
// Adapter实现具体逻辑
class MockUserService implements IUserService {
async getUsers() { return mockData }
}
class RealUserService implements IUserService {
async getUsers() { return axios.get('/api/users') }
}
// 使用时根据环境选择
const userService = isMock ? new MockUserService() : new RealUserService()
场景2:添加额外功能
// adapters/cache.adapter.ts
class CachedUserAdapter implements UserAdapter {
constructor(private adapter: UserAdapter) {}
async getUsers(params?: PaginationParams): Promise<User[]> {
const cacheKey = `users-${JSON.stringify(params)}`
// 1. 先检查缓存
if (localStorage.getItem(cacheKey)) {
return JSON.parse(localStorage.getItem(cacheKey)!)
}
// 2. 实际获取数据
const data = await this.adapter.getUsers(params)
// 3. 设置缓存
localStorage.setItem(cacheKey, JSON.stringify(data))
return data
}
}
// 使用装饰器模式叠加功能
const adapter = new CachedUserAdapter(
new LoggingUserAdapter(
new RealUserAdapter()
)
)
🔄 完整的数据流示例
// 1. 定义接口(Service层)
interface IUserService {
getUsers(page: number, size: number): Promise<User[]>
getUserById(id: number): Promise<User>
}
// 2. 适配器接口(Adapter层)
interface IUserAdapter extends IUserService {
// 可能添加一些适配器特有的方法
initialize?(): Promise<void>
}
// 3. 具体实现
// Mock适配器
class MockUserAdapter implements IUserAdapter {
async getUsers(page: number, size: number) {
// 从Mock.js生成数据
return Mock.mock({
'list|10': [{
'id|+1': 1,
'name': '@cname',
'email': '@email'
}]
}).list
}
}
// API适配器
class ApiUserAdapter implements IUserAdapter {
private client: AxiosInstance
constructor(baseURL: string) {
this.client = axios.create({ baseURL })
}
async getUsers(page: number, size: number) {
const response = await this.client.get('/users', {
params: { page, size }
})
return response.data
}
}
// 4. 工厂函数创建实例
function createUserService(): IUserService {
const env = import.meta.env
let adapter: IUserAdapter
if (env.VITE_API_SOURCE === 'mock') {
adapter = new MockUserAdapter()
} else if (env.VITE_API_SOURCE === 'static') {
adapter = new StaticUserAdapter(env.VITE_API_BASE_URL)
} else {
adapter = new ApiUserAdapter(env.VITE_API_BASE_URL)
}
// 可以在这里添加统一的拦截器/装饰器
if (env.VITE_ENABLE_CACHE) {
return new CachedUserService(adapter)
}
return adapter
}
// 5. 在组件中使用
const userService = createUserService()
// 组件不关心数据来源
const users = await userService.getUsers(1, 10)
🎨 更直观的类比
| 概念 | 类比 | 说明 |
|---|---|---|
| Service | 餐厅菜单 | 定义有什么菜(接口) |
| Adapter | 厨房/外卖 | 怎么做菜(实现) |
| 组件使用 | 顾客点餐 | 只关心吃什么,不关心怎么做 |
📁 实际项目结构建议
src/
├── api/
│ ├── services/ # 定义业务接口
│ │ ├── user.service.ts # interface IUserService
│ │ ├── product.service.ts # interface IProductService
│ │ └── index.ts # 导出所有接口
│ │
│ ├── adapters/ # 具体实现
│ │ ├── mock/ # Mock实现
│ │ │ ├── user.adapter.ts
│ │ │ └── product.adapter.ts
│ │ ├── http/ # HTTP API实现
│ │ │ ├── user.adapter.ts
│ │ │ └── product.adapter.ts
│ │ ├── static/ # 静态数据实现
│ │ │ ├── user.adapter.ts
│ │ │ └── product.adapter.ts
│ │ └── index.ts # 适配器工厂
│ │
│ ├── types/ # TypeScript类型
│ │ ├── user.ts
│ │ ├── product.ts
│ │ └── common.ts
│ │
│ └── index.ts # 统一入口
│
├── composables/ # 组合式函数
│ └── useUserApi.ts # 封装Service使用
└── components/
└── UserList.vue
🚀 简化的实际方案
如果你的项目不是特别复杂,可以简化:
// 简化版:直接在Service中根据环境切换
// src/api/services/user.service.ts
class UserService {
private getAdapter() {
if (import.meta.env.VITE_API_SOURCE === 'mock') {
return {
getUsers: () => mockData,
getUserById: (id) => mockData.find(u => u.id === id)
}
} else {
return {
getUsers: (params) => axios.get('/api/users', { params }),
getUserById: (id) => axios.get(`/api/users/${id}`)
}
}
}
async getUsers(params?: any) {
const adapter = this.getAdapter()
return adapter.getUsers(params)
}
}
// 组件中使用
const userService = new UserService()
const users = await userService.getUsers()
✅ 总结:何时需要两层?
| 场景 | 建议 | 说明 |
|---|---|---|
| 小型项目 | 直接在Service中切换 | 快速开发,不必过度设计 |
| 中型项目 | 简单的Adapter模式 | 数据源可能变化 |
| 大型/复杂项目 | 完整的Service+Adapter | 多数据源、需要装饰器模式、需要测试隔离 |
你的需求场景:需要Mock和静态数据切换,推荐使用简化Adapter模式,既能保持灵活性,又不会过度复杂化。

浙公网安备 33010602011771号