147
社区成员
发帖
与我相关
我的任务
分享| 本次作业所属课程 | Web 全栈开发实战 |
|---|---|
| 作业要求 | 实现前后端分离架构的通讯录系统,包含添加、修改、删除核心功能,支持扩展功能;前后端代码分别提交至 GitHub 不同仓库,撰写符合规范的博客并提交 |
| 本次作业目标 | 掌握前后端分离开发模式;熟练使用 Git 进行代码管理;学会项目部署与文档编写;提升架构设计与编码实现能力 |
| 其他参考资料 | Spring Boot 官方文档、Vue.js 官方文档、GitHub 官方指南、Google Java Style Guide、Vue Code Style |
GitHub仓库地址:https://github.com/zcs667/contacts-project
| 任务模块 | 计划时间(min) | 实际时间(min) |
|---|---|---|
| 需求分析与技术选型 | 60 | 50 |
| 架构设计与目录规划 | 40 | 45 |
| 前端页面开发(含样式) | 180 | 200 |
| 后端接口开发 | 150 | 160 |
| 前后端联调 | 90 | 100 |
| 扩展功能开发 | 80 | 75 |
| 博客编写与素材整理 | 120 | 130 |
| 总计 | 720 | 760 |





前端:Vue 3 + Vite + Axios + Element Plus(UI 组件库)
后端:Spring Boot 3 + MyBatis-Plus(数据访问)
数据库:MySQL 8.0
版本控制:Git + GitHub
采用前后端分离架构,核心分为三层:
前端层:负责页面展示、用户交互、数据校验,通过 Axios 调用后端接口
后端层:提供 RESTful API,处理业务逻辑、数据持久化,返回 JSON 格式数据
数据层:MySQL 数据库存储联系人信息,表结构包含 id(主键)、name、phone、email、remark、create_time 字段
前后端分离通讯录系统
├── 核心功能
│ ├── 添加联系人(表单提交→接口调用→数据库存储)
│ ├── 修改联系人(读取数据库→表单填充→提交更新→数据同步)
│ └── 删除联系人(确认操作→接口请求→数据库删除)
└── 扩展功能
└── 联系人搜索(关键词输入→接口查询→结果展示)
添加联系人:前端表单校验→Axios POST 请求→后端接口接收参数→Service 层处理→Mapper 层插入数据库→返回成功结果→前端刷新列表
修改联系人:前端请求详情接口→加载数据库数据→填充表单→提交修改请求→后端更新数据库→返回结果→前端更新视图
删除联系人:前端发起删除请求→后端接收 id 参数→查询数据库确认存在→执行删除操作→返回结果→前端移除列表项
// 添加联系人
router.post('/', (req, res) => {
const { name, phone } = req.body;
if (!name || !phone) {
return res.status(400).json({ message: '姓名和电话不能为空' });
}
const newContact = {
id: nextId++,
name,
phone
};
contacts.push(newContact);
res.status(201).json(newContact);
});
// 更新联系人
router.put('/:id', (req, res) => {
const { name, phone } = req.body;
const id = parseInt(req.params.id);
const contactIndex = contacts.findIndex(c => c.id === id);
if (contactIndex === -1) {
return res.status(404).json({ message: '联系人不存在' });
}
if (!name || !phone) {
return res.status(400).json({ message: '姓名和电话不能为空' });
}
contacts[contactIndex] = {
...contacts[contactIndex],
name,
phone
};
res.json(contacts[contactIndex]);
});
// 删除联系人
router.delete('/:id', (req, res) => {
const id = parseInt(req.params.id);
const contactIndex = contacts.findIndex(c => c.id === id);
if (contactIndex === -1) {
return res.status(404).json({ message: '联系人不存在' });
}
contacts.splice(contactIndex, 1);
res.json({ message: '联系人删除成功' });
});
const [contacts, setContacts] = useState([])
const [name, setName] = useState('')
const [phone, setPhone] = useState('')
const [editingId, setEditingId] = useState(null)
const [showModal, setShowModal] = useState(false)
// 获取所有联系人
const fetchContacts = async () => {
try {
const response = await axios.get('http://localhost:3000/api/contacts')
setContacts(response.data)
} catch (error) {
console.error('获取联系人失败:', error)
// 使用模拟数据
setContacts([
{ id: 1, name: '张三', phone: '13800138001' },
{ id: 2, name: '李四', phone: '13800138002' }
])
}
}
// 组件加载时获取联系人
effect(() => {
fetchContacts()
}, [])
<table className="contacts-table">
<thead>
<tr>
<th>姓名</th>
<th>电话</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{contacts.map(contact => (
<tr key={contact.id}>
<td>{contact.name}</td>
<td>{contact.phone}</td>
<td>
<button onClick={() => editContact(contact)} className="edit-btn">编辑</button>
<button onClick={() => deleteContact(contact.id)} className="delete-btn">删除</button>
</td>
</tr>
))}
</tbody>
</table>
{showModal && (
<div className="modal-overlay" onClick={resetForm}>
<div className="modal" onClick={(e) => e.stopPropagation()}>
<h2>{editingId ? '编辑联系人' : '添加联系人'}</h2>
<form onSubmit={handleSubmit}>
<div className="form-group">
<label>姓名:</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</div>
<div className="form-group">
<label>电话:</label>
<input
type="tel"
value={phone}
onChange={(e) => setPhone(e.target.value)}
required
/>
</div>
<div className="form-actions">
<button type="submit" className="submit-btn">{editingId ? '更新' : '添加'}</button>
<button type="button" onClick={resetForm} className="cancel-btn">取消</button>
</div>
</form>
</div>
</div>
)}
// 添加或更新联系人
const handleSubmit = async (e) => {
e.preventDefault()
try {
if (editingId) {
// 更新联系人
await axios.put(`http://localhost:3000/api/contacts/${editingId}`, { name, phone })
setContacts(contacts.map(contact =>
contact.id === editingId ? { ...contact, name, phone } : contact
))
} else {
// 添加联系人
const response = await axios.post('http://localhost:3000/api/contacts', { name, phone })
setContacts([...contacts, response.data])
}
resetForm()
} catch (error) {
console.error('操作失败:', error)
// 模拟操作成功的降级处理...
}
}
熟练掌握了前后端分离架构的开发流程,理解了前端与后端通过 API 协作的核心逻辑,学会了使用 Axios 进行跨域请求处理。
加深了对 Git 的使用理解,成功实现前后端代码分离提交至不同 GitHub 仓库,掌握了仓库关联、代码推送、分支管理等操作。
跨域问题:前端调用后端接口时出现跨域报错,通过在后端 Controller 添加 @CrossOrigin 注解,允许所有来源的请求,成功解决。
数据同步问题:修改联系人后前端视图未及时更新,排查发现是未重新调用查询接口,修改后在提交成功后重新请求数据,确保视图与数据库一致。
Git 仓库提交问题:首次提交后端代码时提示 “remote origin already exists”,通过执行 “git remote rm origin” 删除旧关联,重新关联仓库后提交成功。
本次作业让我深刻体会到前后端分离开发的优势,前端专注于用户交互,后端专注于业务逻辑,分工明确且易于维护。通过编写 codestyle.md 文件,养成了遵循代码规范的良好习惯,提高了代码的可读性和可维护性。
扩展功能的开发让我学会了根据需求灵活扩展业务逻辑,同时也认识到技术选型的重要性 —— 选择合适的框架和工具能大幅提高开发效率。未来我会继续深入学习前后端分离相关技术,如 Vuex 状态管理、Spring Security 权限控制等,进一步提升自己的全栈开发能力。