后台交互 - 实际例子:管理后台
通过一个完整的"用户管理后台"例子,展示常见的后台交互模式,包括列表查询、分页、搜索、新增、编辑、删除等操作。
接口设计 (RESTful API)
| 方法 | 路径 | 说明 |
|---|---|---|
GET |
/api/users | 获取用户列表(支持分页、搜索) |
GET |
/api/users/:id | 获取单个用户详情 |
POST |
/api/users | 创建新用户 |
PUT |
/api/users/:id | 更新用户信息 |
DELETE |
/api/users/:id | 删除用户 |
1. 用户列表页 (HTML/Vue)
<div id="app">
<div class="toolbar">
<input v-model="searchKeyword" placeholder="搜索用户名或邮箱" @input="handleSearch"/>
<button @click="openModal()">新增用户</button>
</div>
<table>
<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>
<span :class="['status', user.active ? 'active' : 'inactive']">
{{ user.active ? '启用' : '禁用' }}
</span>
</td>
<td>
<button @click="editUser(user)">编辑</button>
<button @click="deleteUser(user.id)">删除</button>
</td>
</tr>
</tbody>
</table>
<div class="pagination">
<button :disabled="page === 1" @click="changePage(-1)">上一页</button>
<span>第 {{ page }} / {{ totalPages }} 页</span>
<button :disabled="page === totalPages" @click="changePage(1)">下一页</button>
</div>
</div>
2. 页面逻辑 (JavaScript)
const app = new Vue({
el: '#app',
data: {
users: [],
searchKeyword: '',
page: 1,
pageSize: 10,
total: 0,
loading: false
},
computed: {
totalPages() {
return Math.ceil(this.total / this.pageSize);
}
},
mounted() {
this.fetchUsers();
},
methods: {
// 获取用户列表(带分页和搜索)
async fetchUsers() {
this.loading = true;
try {
const params = new URLSearchParams({
page: this.page,
pageSize: this.pageSize,
keyword: this.searchKeyword
});
const res = await fetch(`/api/users?${params}`);
const data = await res.json();
this.users = data.list;
this.total = data.total;
} catch(e) {
console.error('获取失败', e);
} finally {
this.loading = false;
}
},
// 搜索(防抖)
handleSearch: debounce(function() {
this.page = 1;
this.fetchUsers();
}, 300),
// 分页
changePage(delta) {
this.page += delta;
this.fetchUsers();
},
// 删除用户
async deleteUser(id) {
if(!confirm('确定删除该用户?')) return;
await fetch(`/api/users/${id}`, { method: 'DELETE' });
this.fetchUsers();
},
// 编辑用户
editUser(user) {
// 打开弹窗并填充数据...
},
// 打开新增弹窗
openModal() {
// 打开弹窗...
}
}
});
// 防抖函数
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
3. 新增/编辑弹窗
<!-- 新增编辑弹窗 -->
<div v-if="showModal" class="modal-overlay" @click="closeModal">
<div class="modal" @click.stop>
<h3>{{ isEdit ? '编辑用户' : '新增用户' }}</h3>
<form @submit.prevent="submitForm">
<div class="form-group">
<label>用户名</label>
<input v-model="formData.name" required/>
</div>
<div class="form-group">
<label>邮箱</label>
<input type="email" v-model="formData.email" required/>
</div>
<div class="form-group">
<label>
<input type="checkbox" v-model="formData.active"/>
启用账户
</label>
</div>
<div class="form-actions">
<button type="button" @click="closeModal">取消</button>
<button type="submit">提交</button>
</div>
</form>
</div>
</div>
4. 表单提交逻辑
data: {
showModal: false,
isEdit: false,
formData: {
id: null,
name: '',
email: '',
active: true
}
},
methods: {
openModal() {
this.isEdit = false;
this.formData = { id: null, name: '', email: '', active: true };
this.showModal = true;
},
editUser(user) {
this.isEdit = true;
this.formData = { ...user };
this.showModal = true;
},
closeModal() {
this.showModal = false;
},
async submitForm() {
const url = this.isEdit
? `/api/users/${this.formData.id}`
: '/api/users';
const method = this.isEdit ? 'PUT' : 'POST';
const res = await fetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.formData)
});
if (res.ok) {
alert(this.isEdit ? '修改成功' : '创建成功');
this.closeModal();
this.fetchUsers();
}
}
}
5. 后端接口示例 (Node.js/Express)
// GET /api/users - 获取用户列表
app.get('/api/users', async (req, res) => {
const { page = 1, pageSize = 10, keyword = '' } = req.query;
let query = {};
if (keyword) {
query.$or = [
{ name: { $regex: keyword } },
{ email: { $regex: keyword } }
];
}
const total = await User.countDocuments(query);
const list = await User.find(query)
.skip((page - 1) * pageSize)
.limit(Number(pageSize));
res.json({ list, total });
});
// POST /api/users - 创建用户
app.post('/api/users', async (req, res) => {
const user = new User(req.body);
await user.save();
res.json(user);
});
// PUT /api/users/:id - 更新用户
app.put('/api/users/:id', async (req, res) => {
const user = await User.findByIdAndUpdate(req.params.id, req.body, { new: true });
res.json(user);
});
// DELETE /api/users/:id - 删除用户
app.delete('/api/users/:id', async (req, res) => {
await User.findByIdAndDelete(req.params.id);
res.json({ success: true });
});