先澄清一个关键点:
❗️ Vue 3 是前端框架,运行在浏览器;Express 是后端框架,运行在 Node.js。它们不能“共用模板”或“共用 EJS”。
✅ 正确理解你的需求:
你想构建一个:
- 后端:使用
Express + pnpm + ESM,提供 API 读写 CSV 文件 - 前端:使用
Vue 3(不是 EJS!),通过 AJAX 调用后端 API,渲染数据、提交表单 - 数据存储:CSV 文件(由 Express 后端读写)
🚀 完整项目:Vue3 + Express + CSV 文件系统(前后端分离)
📁 项目结构
my-vue-express-csv-app/
├── backend/ 👈 Express 后端(读写 CSV)
│ ├── index.js
│ ├── csvUtils.js
│ ├── package.json
│ └── data/users.csv
│
└── frontend/ 👈 Vue 3 前端(调用 API)
├── package.json
├── vite.config.js
└── src/
├── main.js
├── App.vue
└── components/
├── UserList.vue
└── UserForm.vue
✅ 第一部分:搭建 Express 后端(CSV 读写 API)
步骤1:创建 backend 项目
mkdir -p my-vue-express-csv-app/backend
cd my-vue-express-csv-app/backend
pnpm init
backend/package.json:
{
"name": "backend",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "nodemon index.js"
}
}
安装依赖:
pnpm add express cors
pnpm add -D nodemon
✅
cors用于允许前端跨域访问
步骤2:创建 csvUtils.js
// backend/csvUtils.js
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const DATA_DIR = path.join(__dirname, 'data');
const CSV_FILE = path.join(DATA_DIR, 'users.csv');
await fs.mkdir(DATA_DIR, { recursive: true });
export async function readUsers() {
try {
const data = await fs.readFile(CSV_FILE, 'utf8');
const lines = data.trim().split('\n');
if (lines.length <= 1) return [];
const headers = lines[0].split(',');
const users = [];
for (let i = 1; i < lines.length; i++) {
if (!lines[i].trim()) continue;
const values = lines[i].split(',');
const user = {};
headers.forEach((header, index) => {
user[header] = values[index] || '';
});
users.push(user);
}
return users;
} catch (error) {
if (error.code === 'ENOENT') {
await fs.writeFile(CSV_FILE, 'id,name,email,age,created_at\n', 'utf8');
console.log('✅ users.csv created');
return [];
}
throw error;
}
}
export async function writeUser(user) {
const users = await readUsers();
const nextId = users.length > 0 ? Math.max(...users.map(u => parseInt(u.id))) + 1 : 1;
const now = new Date().toISOString();
const line = `${nextId},${user.name},${user.email},${user.age || ''},${now}\n`;
await fs.appendFile(CSV_FILE, line, 'utf8');
console.log(`✅ User ${user.name} written to CSV`);
return { id: nextId, ...user, created_at: now };
}
步骤3:创建 index.js(Express API)
// backend/index.js
import express from 'express';
import cors from 'cors';
import { readUsers, writeUser } from './csvUtils.js';
const app = express();
const PORT = process.env.PORT || 3001;
app.use(cors()); // 允许前端访问
app.use(express.json()); // 解析 JSON 请求体
// 👇 GET /api/users → 读取所有用户
app.get('/api/users', async (req, res) => {
try {
const users = await readUsers();
res.json(users);
} catch (error) {
res.status(500).json({ error: 'Failed to read users' });
}
});
// 👇 POST /api/users → 创建新用户
app.post('/api/users', async (req, res) => {
const { name, email, age } = req.body;
if (!name || !email) {
return res.status(400).json({ error: 'Name and email are required' });
}
try {
const newUser = await writeUser({ name, email, age });
res.status(201).json(newUser);
} catch (error) {
res.status(500).json({ error: 'Failed to write user' });
}
});
app.listen(PORT, () => {
console.log(`✅ Backend running at http://localhost:${PORT}`);
});
启动后端:
cd backend
pnpm dev
✅ 后端 API 已就绪:
GET http://localhost:3001/api/users→ 获取用户列表POST http://localhost:3001/api/users→ 创建用户(JSON 格式)
✅ 第二部分:搭建 Vue 3 前端
步骤1:创建 frontend 项目(使用 Vite)
在项目根目录:
cd ..
pnpm create vite frontend --template vue
cd frontend
安装依赖:
pnpm install
步骤2:配置代理(解决开发环境跨域)
创建 frontend/vite.config.js:
// frontend/vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
server: {
proxy: {
'/api': {
target: 'http://localhost:3001', // 后端地址
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '/api')
}
}
}
});
✅ 这样前端
http://localhost:5173/api/users会自动代理到http://localhost:3001/api/users
步骤3:创建组件
📄 frontend/src/components/UserList.vue
<!-- frontend/src/components/UserList.vue -->
<template>
<div>
<h2>👥 用户列表</h2>
<div v-if="loading">加载中...</div>
<div v-else-if="error" class="error">{{ error }}</div>
<table v-else-if="users.length > 0">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>邮箱</th>
<th>年龄</th>
<th>创建时间</th>
</tr>
</thead>
<tbody>
<tr v-for="user in users" :key="user.id">
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
<td>{{ user.age || '—' }}</td>
<td>{{ new Date(user.created_at).toLocaleString() }}</td>
</tr>
</tbody>
</table>
<p v-else>暂无用户</p>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
const users = ref([]);
const loading = ref(false);
const error = ref('');
const fetchUsers = async () => {
loading.value = true;
error.value = '';
try {
const res = await fetch('/api/users');
if (!res.ok) throw new Error('Failed to fetch');
users.value = await res.json();
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
};
onMounted(fetchUsers);
</script>
<style scoped>
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th, td {
padding: 10px;
border: 1px solid #ddd;
text-align: left;
}
th {
background-color: #f5f5f5;
}
.error {
color: red;
}
</style>
📄 frontend/src/components/UserForm.vue
<!-- frontend/src/components/UserForm.vue -->
<template>
<div>
<h2>➕ 添加新用户</h2>
<form @submit.prevent="handleSubmit" class="form">
<div>
<label>姓名:</label>
<input v-model="formData.name" required />
</div>
<div>
<label>邮箱:</label>
<input v-model="formData.email" type="email" required />
</div>
<div>
<label>年龄:</label>
<input v-model.number="formData.age" type="number" min="1" max="150" />
</div>
<button type="submit" :disabled="loading">
{{ loading ? '提交中...' : '✅ 提交' }}
</button>
<div v-if="error" class="error">{{ error }}</div>
<div v-if="success" class="success">🎉 用户添加成功!</div>
</form>
</div>
</template>
<script setup>
import { ref } from 'vue';
const formData = ref({
name: '',
email: '',
age: null
});
const loading = ref(false);
const error = ref('');
const success = ref(false);
const handleSubmit = async () => {
loading.value = true;
error.value = '';
success.value = false;
try {
const res = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData.value)
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.error || '提交失败');
}
const newUser = await res.json();
console.log('新用户:', newUser);
success.value = true;
formData.value = { name: '', email: '', age: null }; // 重置表单
// 可选:3秒后自动刷新列表(或 emit 事件让父组件刷新)
setTimeout(() => {
success.value = false;
}, 3000);
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
};
</script>
<style scoped>
.form {
max-width: 500px;
margin: 20px 0;
padding: 20px;
background: #f9f9f9;
border-radius: 8px;
}
.form div {
margin-bottom: 15px;
}
.form label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form input {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
padding: 10px 20px;
background: #4a90e2;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
background: #ccc;
}
.error {
color: red;
margin-top: 10px;
}
.success {
color: green;
margin-top: 10px;
}
</style>
步骤4:修改 frontend/src/App.vue
<!-- frontend/src/App.vue -->
<template>
<div class="container">
<h1>Vue3 + Express + CSV 用户管理系统</h1>
<UserForm @user-added="refreshUsers" />
<UserList />
</div>
</template>
<script setup>
import UserList from './components/UserList.vue';
import UserForm from './components/UserForm.vue';
const refreshUsers = () => {
// 简单刷新:重新加载页面(或通过 provide/inject 优化)
window.location.reload();
};
</script>
<style>
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
}
</style>
步骤5:运行前端
cd frontend
pnpm dev
✅ 最终效果
- 后端 Express 在
http://localhost:3001运行,读写backend/data/users.csv - 前端 Vue 3 在
http://localhost:5173运行,通过/api/users代理访问后端 - 页面显示用户列表(从 CSV 读取)
- 表单提交 → POST → 写入 CSV → 页面刷新显示新数据
📂 最终项目结构
my-vue-express-csv-app/
├── backend/
│ ├── index.js
│ ├── csvUtils.js
│ ├── package.json
│ └── data/users.csv
│
└── frontend/
├── package.json
├── vite.config.js
└── src/
├── main.js
├── App.vue
└── components/
├── UserList.vue
└── UserForm.vue
🎉 恭喜你!
你已成功构建:
✅ Vue 3 前端(现代 Composition API)
✅ Express + ESM + pnpm 后端
✅ CSV 文件持久化存储
✅ 前后端分离架构
✅ 无数据库、无编译依赖、100% 兼容 Windows