SchoolDash Alpha冲刺随笔5 - Day 9
课程与作业信息
所属课程:软件工程实践
作业要求来源:第五次作业——Alpha冲刺
- 项目燃尽图(Burn-up Chart)
当前冲刺总Story Point:50 SP(已完50 SP,剩余0 SP)

- 本日冲刺整体进展
完成骑手订单接收、管理端订单/用户管理。
系统三大端基本功能就绪。
- 项目最新运行效果
骑手端演示视频
SchoolDash骑手端演示视频
管理员端演示视频
SchoolDash管理员端演示视频
骑手端页面




管理员端页面



- 今日工作成果
(后端开发)骑手与管理员路由
骑手路由
const express = require('express');
const router = express.Router();
const { auth, checkRole } = require('../middleware/auth');
const Order = require('../models/Order');
const OrderItem = require('../models/OrderItem');
const Goods = require('../models/Goods');
// 骑手仪表板统计
router.get('/dashboard/stats', auth, checkRole(['rider']), async (req, res) => {
try {
const riderId = req.user.id;
const assigned = await Order.count({ where: { status: 'assigned', riderId: riderId } });
const delivering = await Order.count({ where: { riderId, status: 'delivering' } });
const completed = await Order.count({ where: { riderId, status: 'completed' } });
res.json({
code: 200,
msg: '获取骑手统计成功',
data: {
assignedOrders: assigned,
deliveringOrders: delivering,
completedOrders: completed
}
});
} catch (error) {
console.error('获取骑手统计失败:', error);
res.status(500).json({ code: 500, msg: '服务器错误' });
}
});
// 获取待接单订单(status = 'paid',尚未分配骑手)
router.get('/orders/pending', auth, checkRole(['rider']), async (req, res) => {
try {
const orders = await Order.findAll({
where: { status: 'paid', riderId: null },
include: [
{ model: require('../models/User'), as: 'user', attributes: ['username', 'phone'] },
{ model: require('../models/Address'), as: 'address' },
{
model: OrderItem,
include: [{
model: Goods,
as: 'Good',
attributes: ['id', 'name', 'price']
}]
}
],
order: [['createdAt', 'ASC']]
});
// 格式化订单数据,确保价格是数字类型
const formattedOrders = orders.map(order => ({
...order.toJSON(),
totalAmount: parseFloat(order.totalAmount) || 0,
OrderItems: order.OrderItems ? order.OrderItems.map(item => ({
...item.toJSON(),
price: parseFloat(item.price) || 0,
totalPrice: parseFloat(item.totalPrice) || 0,
Good: item.Good ? {
...item.Good.toJSON(),
price: parseFloat(item.Good.price) || 0
} : null
})) : []
}));
res.json({ code: 200, msg: '获取待接单订单成功', data: formattedOrders });
} catch (error) {
console.error('获取待接单订单失败:', error);
res.status(500).json({ code: 500, msg: '服务器错误' });
}
});
// 骑手接单(将订单状态从 'paid' 改为 'assigned',并设置骑手ID)
router.post('/orders/:id/accept', auth, checkRole(['rider']), async (req, res) => {
try {
const riderId = req.user.id;
const orderId = req.params.id;
// 验证订单是否存在且状态为'paid'且未分配骑手
const order = await Order.findOne({
where: {
id: orderId,
status: 'paid',
riderId: null
}
});
if (!order) {
return res.status(400).json({ code: 400, msg: '订单不存在或已被接单' });
}
// 更新订单状态为已分配并设置骑手ID
await order.update({ status: 'assigned', riderId: riderId });
// 获取更新后的订单详情
const updatedOrder = await Order.findByPk(orderId, {
include: [
{ model: require('../models/User'), as: 'user', attributes: ['username', 'phone'] },
{ model: require('../models/Address'), as: 'address' },
{
model: OrderItem,
include: [{
model: Goods,
as: 'Good',
attributes: ['id', 'name', 'price']
}]
}
]
});
res.json({ code: 200, msg: '接单成功', data: updatedOrder });
} catch (error) {
console.error('接单失败:', error);
res.status(500).json({ code: 500, msg: '服务器错误' });
}
});
// 获取已分配订单(status = 'assigned',已分配给当前骑手)
router.get('/orders/assigned', auth, checkRole(['rider']), async (req, res) => {
try {
const riderId = req.user.id;
const orders = await Order.findAll({
where: { status: 'assigned', riderId: riderId },
include: [
{ model: require('../models/User'), as: 'user', attributes: ['username', 'phone'] },
{ model: require('../models/Address'), as: 'address' },
{
model: OrderItem,
include: [{
model: Goods,
as: 'Good',
attributes: ['id', 'name', 'price']
}]
}
],
order: [['createdAt', 'ASC']]
});
// 格式化订单数据,确保价格是数字类型
const formattedOrders = orders.map(order => ({
...order.toJSON(),
totalAmount: parseFloat(order.totalAmount) || 0,
OrderItems: order.OrderItems ? order.OrderItems.map(item => ({
...item.toJSON(),
price: parseFloat(item.price) || 0,
totalPrice: parseFloat(item.totalPrice) || 0,
Good: item.Good ? {
...item.Good.toJSON(),
price: parseFloat(item.Good.price) || 0
} : null
})) : []
}));
res.json({ code: 200, msg: '获取已分配订单成功', data: formattedOrders });
} catch (error) {
console.error('获取已分配订单失败:', error);
res.status(500).json({ code: 500, msg: '服务器错误' });
}
});
// 骑手确认配送(将订单状态从 'assigned' 改为 'delivering')
router.post('/orders/:id/confirm', auth, checkRole(['rider']), async (req, res) => {
try {
const riderId = req.user.id;
const orderId = req.params.id;
// 验证订单是否分配给当前骑手
const order = await Order.findOne({
where: {
id: orderId,
riderId: riderId,
status: 'assigned'
}
});
if (!order) {
return res.status(400).json({ code: 400, msg: '订单不存在或状态不正确' });
}
// 更新订单状态为配送中
await order.update({ status: 'delivering' });
// 获取更新后的订单详情
const updatedOrder = await Order.findByPk(orderId, {
include: [
{ model: require('../models/User'), as: 'user', attributes: ['username', 'phone'] },
{ model: require('../models/Address'), as: 'address' },
{
model: OrderItem,
include: [{
model: Goods,
as: 'Good',
attributes: ['id', 'name', 'price']
}]
}
]
});
res.json({ code: 200, msg: '确认配送成功', data: updatedOrder });
} catch (error) {
console.error('确认配送失败:', error);
res.status(500).json({ code: 500, msg: '服务器错误' });
}
});
// 骑手查看自己的配送中订单
router.get('/orders/delivering', auth, checkRole(['rider']), async (req, res) => {
try {
const riderId = req.user.id;
const orders = await Order.findAll({
where: { riderId, status: 'delivering' },
include: [
{ model: require('../models/User'), as: 'user', attributes: ['username', 'phone'] },
{ model: require('../models/Address'), as: 'address' },
{
model: OrderItem,
include: [{
model: Goods,
as: 'Good',
attributes: ['id', 'name', 'price']
}]
}
],
order: [['updatedAt', 'ASC']]
});
// 格式化订单数据,确保价格是数字类型
const formattedOrders = orders.map(order => ({
...order.toJSON(),
totalAmount: parseFloat(order.totalAmount) || 0,
OrderItems: order.OrderItems ? order.OrderItems.map(item => ({
...item.toJSON(),
price: parseFloat(item.price) || 0,
totalPrice: parseFloat(item.totalPrice) || 0,
Good: item.Good ? {
...item.Good.toJSON(),
price: parseFloat(item.Good.price) || 0
} : null
})) : []
}));
res.json({ code: 200, msg: '获取配送中订单成功', data: formattedOrders });
} catch (error) {
console.error('获取配送中订单失败:', error);
res.status(500).json({ code: 500, msg: '服务器错误' });
}
});
// 骑手完成订单
router.post('/orders/:id/complete', auth, checkRole(['rider']), async (req, res) => {
try {
const riderId = req.user.id;
const orderId = req.params.id;
const [updated] = await Order.update({
status: 'completed'
}, {
where: {
id: orderId,
riderId: riderId,
status: 'delivering'
}
});
if (updated === 0) {
return res.status(404).json({ code: 404, msg: '订单不存在或无权限操作' });
}
res.json({ code: 200, msg: '订单完成' });
} catch (error) {
console.error('完成订单失败:', error);
res.status(500).json({ code: 500, msg: '服务器错误' });
}
});
// 骑手查看已完成订单
router.get('/orders/completed', auth, checkRole(['rider']), async (req, res) => {
try {
console.log('骑手已完成订单API被调用,用户:', req.user ? req.user.username : '未认证');
console.log('请求参数:', req.query);
const riderId = req.user.id;
const { dateFilter, orderNo } = req.query;
// 构建查询条件
let whereCondition = { riderId, status: 'completed' };
// 如果有订单号筛选条件
if (orderNo) {
whereCondition.orderNumber = { [require('sequelize').Op.like]: `%${orderNo}%` };
}
// 如果有时间筛选条件
if (dateFilter && dateFilter !== 'all') {
const today = new Date();
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
let startDate, endDate;
switch (dateFilter) {
case 'today':
startDate = today;
endDate = tomorrow;
break;
case 'yesterday':
startDate = new Date(today);
startDate.setDate(startDate.getDate() - 1);
endDate = today;
break;
case '7days':
startDate = new Date(today);
startDate.setDate(startDate.getDate() - 7);
endDate = tomorrow;
break;
case '30days':
startDate = new Date(today);
startDate.setDate(startDate.getDate() - 30);
endDate = tomorrow;
break;
}
if (startDate && endDate) {
whereCondition.updatedAt = {
[require('sequelize').Op.between]: [startDate, endDate]
};
}
}
console.log('查询条件:', whereCondition);
const orders = await Order.findAll({
where: whereCondition,
include: [
{ model: require('../models/User'), as: 'user', attributes: ['username', 'phone'] },
{ model: require('../models/Address'), as: 'address' },
{
model: OrderItem,
include: [{
model: Goods,
as: 'Good',
attributes: ['id', 'name', 'price']
}]
}
],
order: [['updatedAt', 'DESC']]
});
console.log('查询到订单数量:', orders.length);
// 格式化订单数据,确保价格是数字类型
const formattedOrders = orders.map(order => ({
...order.toJSON(),
totalAmount: parseFloat(order.totalAmount) || 0,
OrderItems: order.OrderItems ? order.OrderItems.map(item => ({
...item.toJSON(),
price: parseFloat(item.price) || 0,
totalPrice: parseFloat(item.totalPrice) || 0,
Good: item.Good ? {
...item.Good.toJSON(),
price: parseFloat(item.Good.price) || 0
} : null
})) : []
}));
console.log('返回骑手已完成订单数据成功');
res.json({ code: 200, msg: '获取已完成订单成功', data: formattedOrders });
} catch (error) {
console.error('获取已完成订单失败:', error);
res.status(500).json({ code: 500, msg: '服务器错误' });
}
});
// 骑手获取订单详情(包括待接单订单和自己的订单)
router.get('/order-detail/:id', auth, checkRole(['rider']), async (req, res) => {
try {
const riderId = req.user.id;
const orderId = req.params.id;
// 骑手可以查看所有待接单订单和已分配给自己的订单
const order = await Order.findOne({
where: {
id: orderId,
[require('sequelize').Op.or]: [
{ status: 'paid', riderId: null }, // 待接单订单
{ riderId: riderId } // 已分配给自己的订单
]
},
include: [
{ model: require('../models/User'), as: 'user', attributes: ['username', 'phone'] },
{ model: require('../models/Address'), as: 'address' },
{
model: OrderItem,
include: [{
model: Goods,
as: 'Good',
attributes: ['id', 'name', 'price']
}]
}
]
});
if (!order) {
return res.status(404).json({ code: 404, msg: '订单不存在或无权限查看' });
}
// 格式化订单数据,确保价格是数字类型
const formattedOrder = {
...order.toJSON(),
totalAmount: parseFloat(order.totalAmount) || 0,
OrderItems: order.OrderItems ? order.OrderItems.map(item => ({
...item.toJSON(),
price: parseFloat(item.price) || 0,
totalPrice: parseFloat(item.totalPrice) || 0,
Good: item.Good ? {
...item.Good.toJSON(),
price: parseFloat(item.Good.price) || 0
} : null
})) : []
};
res.json({ code: 200, msg: '获取订单详情成功', data: formattedOrder });
} catch (error) {
console.error('获取订单详情失败:', error);
res.status(500).json({ code: 500, msg: '服务器错误' });
}
});
// 骑手获取订单详情
router.get('/orders/:id', auth, checkRole(['rider']), async (req, res) => {
try {
const riderId = req.user.id;
const orderId = req.params.id;
const order = await Order.findOne({
where: {
id: orderId,
riderId: riderId
},
include: [
{ model: require('../models/User'), as: 'user', attributes: ['username', 'phone'] },
{ model: require('../models/Address'), as: 'address' },
{
model: OrderItem,
include: [{
model: Goods,
as: 'Good',
attributes: ['id', 'name', 'price']
}]
}
]
});
if (!order) {
return res.status(404).json({ code: 404, msg: '订单不存在或无权限查看' });
}
// 格式化订单数据,确保价格是数字类型
const formattedOrder = {
...order.toJSON(),
totalAmount: parseFloat(order.totalAmount) || 0,
OrderItems: order.OrderItems ? order.OrderItems.map(item => ({
...item.toJSON(),
price: parseFloat(item.price) || 0,
totalPrice: parseFloat(item.totalPrice) || 0,
Good: item.Good ? {
...item.Good.toJSON(),
price: parseFloat(item.Good.price) || 0
} : null
})) : []
};
res.json({ code: 200, msg: '获取订单详情成功', data: formattedOrder });
} catch (error) {
console.error('获取订单详情失败:', error);
res.status(500).json({ code: 500, msg: '服务器错误' });
}
});
module.exports = router;
管理员路由
const express = require('express');
const router = express.Router();
const { auth, checkRole } = require('../../middleware/auth');
// 挂载子路由(商品/分类/用户/订单)
router.use('/goods', require('./goodsRoutes'));
router.use('/category', require('./categoryRoutes'));
router.use('/users', require('./userRoutes'));
router.use('/order', require('./orderRoutes')); // Changed from /orders to /order to match frontend
// 管理员仪表板统计
router.get('/dashboard/stats', auth, checkRole(['admin']), async (req, res) => {
try {
const Sequelize = require('sequelize');
const { Op } = require('sequelize');
const User = require('../../models/User');
const Goods = require('../../models/Goods');
const Category = require('../../models/Category');
const Order = require('../../models/Order');
// 获取商品、订单等统计数据
const [goodsCount] = await Goods.findAll({
attributes: [[Sequelize.fn('COUNT', Sequelize.col('id')), 'count']]
});
// 获取订单总数
const [orderCount] = await Order.findAll({
attributes: [[Sequelize.fn('COUNT', Sequelize.col('id')), 'count']]
});
// 获取今日订单数
const today = new Date();
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
const [todayOrderCount] = await Order.findAll({
where: {
createdAt: {
[Op.between]: [today, tomorrow]
}
},
attributes: [[Sequelize.fn('COUNT', Sequelize.col('id')), 'count']]
});
// 获取总销售额(已支付订单)
const [totalSalesResult] = await Order.findAll({
where: {
status: 'paid'
},
attributes: [[Sequelize.fn('SUM', Sequelize.col('totalAmount')), 'total']]
});
const totalSales = totalSalesResult.dataValues.total || 0;
// 获取各状态订单数量
const statusCounts = await Order.findAll({
attributes: [
'status',
[Sequelize.fn('COUNT', Sequelize.col('id')), 'count']
],
group: ['status'],
raw: true
});
// 构建订单状态分布数据
const statusMap = {
'pending': { label: '待支付', count: 0 },
'paid': { label: '已支付', count: 0 },
'assigned': { label: '已派单', count: 0 },
'delivering': { label: '配送中', count: 0 },
'completed': { label: '已完成', count: 0 },
'cancelled': { label: '已取消', count: 0 }
};
statusCounts.forEach(item => {
if (statusMap[item.status]) {
statusMap[item.status].count = parseInt(item.count);
}
});
// 计算百分比
const total = Object.values(statusMap).reduce((sum, item) => sum + item.count, 0);
const orderStatusData = Object.entries(statusMap).map(([status, data]) => ({
status,
label: data.label,
count: data.count,
rate: total > 0 ? Math.round((data.count / total) * 100) : 0
}));
res.json({
code: 200,
msg: '获取统计数据成功',
data: {
stats: {
goodsCount: goodsCount.dataValues.count,
orderCount: orderCount.dataValues.count,
todayOrderCount: todayOrderCount.dataValues.count,
totalSales: parseFloat(totalSales)
},
orderStatus: orderStatusData
}
});
(前端开发)骑手与管理端页面布局
骑手端页面布局
<template>
<div class="rider-layout">
<!-- 侧边栏 -->
<div class="sidebar">
<div class="sidebar-header">
<div class="logo-text">School Dash 骑手端</div>
</div>
<div class="sidebar-menu">
<div
class="menu-item"
:class="{ active: $route.path === '/rider/dashboard' }"
@click="$router.push('/rider/dashboard')"
>
📊 订单概览
</div>
<div
class="menu-item"
:class="{ active: $route.path === '/rider/order-list' }"
@click="$router.push('/rider/order-list')"
>
📦 待接单
</div>
<div
class="menu-item"
:class="{ active: $route.path === '/rider/accepted-orders' }"
@click="$router.push('/rider/accepted-orders')"
>
🚴 配送中
</div>
<div
class="menu-item"
:class="{ active: $route.path === '/rider/completed-orders' }"
@click="$router.push('/rider/completed-orders')"
>
✅ 已完成
</div>
<div class="menu-item" @click="handleLogout">
🚪 退出登录
</div>
</div>
</div>
<!-- 主内容区 -->
<div class="main-content">
<!-- 顶部导航 -->
<div class="content-header">
<div class="header-title">{{ pageTitle }}</div>
<div class="rider-info">
骑手:{{ riderName }}
</div>
</div>
<!-- 页面内容 -->
<div class="content-body">
<router-view />
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { ElMessage } from 'element-plus';
import request from '../utils/request';
const router = useRouter();
const route = useRoute();
const riderName = ref(localStorage.getItem('riderName') || '骑手');
const pageTitle = ref('');
// 页面标题映射
const titleMap = {
'/rider/dashboard': '订单概览',
'/rider/order-list': '待接单',
'/rider/accepted-orders': '配送中',
'/rider/completed-orders': '已完成'
};
// 初始化
onMounted(() => {
// 校验登录状态
if (!localStorage.getItem('riderToken')) {
router.push('/rider/login');
return;
}
// 更新页面标题
updatePageTitle();
// 监听路由变化
router.afterEach(() => updatePageTitle());
});
// 更新页面标题
const updatePageTitle = () => {
pageTitle.value = titleMap[route.path] || '骑手端';
};
// 退出登录
const handleLogout = () => {
localStorage.removeItem('riderToken');
localStorage.removeItem('riderName');
ElMessage.success('退出登录成功');
router.push('/rider/login');
};
</script>
<style scoped>
.rider-layout {
display: flex;
height: 100vh;
background-color: #f5f5f5;
}
/* 侧边栏 */
.sidebar {
width: 220px;
background-color: #4299e1;
color: #fff;
display: flex;
flex-direction: column;
}
.sidebar-header {
padding: 20px;
border-bottom: 1px solid #3a86cf;
}
.logo-text {
font-size: 16px;
font-weight: 600;
text-align: center;
}
.sidebar-menu {
padding-top: 20px;
flex: 1;
}
.menu-item {
padding: 14px 20px;
font-size: 14px;
cursor: pointer;
transition: background-color 0.2s;
}
.menu-item:hover {
background-color: #3a86cf;
}
.menu-item.active {
background-color: #3182ce;
border-left: 4px solid #fff;
}
/* 主内容区 */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: auto;
}
.content-header {
padding: 0 24px;
height: 60px;
background-color: #fff;
box-shadow: 0 1px 2px rgba(0,0,0,0.08);
display: flex;
align-items: center;
justify-content: space-between;
}
.header-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
.rider-info {
font-size: 14px;
color: #666;
}
.content-body {
flex: 1;
padding: 24px;
}
/* 响应式适配 */
@media (max-width: 767px) {
.sidebar {
width: 60px;
}
.logo-text, .menu-item span {
display: none;
}
.menu-item {
display: flex;
align-items: center;
justify-content: center;
padding: 14px 0;
}
.content-header {
padding: 0 16px;
}
.content-body {
padding: 16px;
}
}
</style>
管理员端页面布局
'/admin/dashboard': '数据概览',
'/admin/goods-manage': '商品管理',
'/admin/order-manage': '订单管理'
};
// 初始化
onMounted(() => {
// 校验登录状态
if (!localStorage.getItem('adminToken')) {
router.push('/admin/login');
return;
}
// 更新页面标题
updatePageTitle();
// 监听路由变化
router.afterEach(() => updatePageTitle());
});
// 更新页面标题
const updatePageTitle = () => {
pageTitle.value = titleMap[route.path] || '管理后台';
};
// 退出登录
const handleLogout = () => {
localStorage.removeItem('adminToken');
localStorage.removeItem('adminName');
ElMessage.success('退出登录成功');
router.push('/admin/login');
};
</script>
<style scoped>
.admin-layout {
display: flex;
height: 100vh;
background-color: #f5f5f5;
}
/* 侧边栏 */
.sidebar {
width: 220px;
background-color: #4299e1;
color: #fff;
display: flex;
flex-direction: column;
}
.sidebar-header {
padding: 20px;
border-bottom: 1px solid #3a86cf;
}
.logo-text {
font-size: 16px;
font-weight: 600;
text-align: center;
}
.sidebar-menu {
padding-top: 20px;
flex: 1;
}
.menu-item {
padding: 14px 20px;
font-size: 14px;
cursor: pointer;
transition: background-color 0.2s;
}
.menu-item:hover {
background-color: #3a86cf;
}
.menu-item.active {
background-color: #3182ce;
border-left: 4px solid #fff;
}
/* 主内容区 */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: auto;
}
.content-header {
padding: 0 24px;
height: 60px;
background-color: #fff;
box-shadow: 0 1px 2px rgba(0,0,0,0.08);
display: flex;
align-items: center;
justify-content: space-between;
}
.header-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
.user-info {
font-size: 14px;
color: #666;
}
.content-body {
flex: 1;
padding: 24px;
}
</style>
(测试)
任务:全系统集成测试
成果:覆盖主要流程,发现并修复2个小bug
- 本日小结与明日计划
今日总结:Alpha版本基本完成
存在问题:少量优化点
明日计划:收尾与总结
SchoolDash Alpha冲刺圆满完成!

被折叠的 条评论
为什么被折叠?



