2025/12/27 11:42:57
网站建设
项目流程
泉州晋江网站建设费用,网站开通银行支付接口,html网页编程软件,网店代运营一年的费用是多少SchoolDash Alpha冲刺随笔4 - Day 7 课程与作业信息
所属课程#xff1a;软件工程实践 作业要求来源#xff1a;第五次作业——Alpha冲刺
项目燃尽图#xff08;Burn-up Chart#xff09;
当前冲刺总Story Point#xff1a;50 SP#xff08;已完成46 SP#xff0c;剩…SchoolDash Alpha冲刺随笔4 - Day 7课程与作业信息所属课程软件工程实践作业要求来源第五次作业——Alpha冲刺项目燃尽图Burn-up Chart当前冲刺总Story Point50 SP已完成46 SP剩余4 SP本日冲刺整体进展完成购物车管理与订单创建。前端添加/更新/删除购物车项后端同步订单表。项目最新运行效果视频演示SchoolDash商品添加购物车及结算演示视频购物车页面图片成员今日工作成果后端开发购物车const express require(express); const router express.Router(); const Cart require(../models/Cart); const Goods require(../models/Goods); const Category require(../models/Category); const { auth } require(../middleware/auth); // 定义模型关联确保关联已设置 Category.hasMany(Goods, { foreignKey: categoryId }); Goods.belongsTo(Category, { foreignKey: categoryId }); Cart.belongsTo(Goods, { foreignKey: goodsId }); // 获取购物车列表 router.get(/list, auth, async (req, res) { try { console.log(获取购物车列表用户ID:, req.user.id); // 先测试直接查询商品 const testGoods await Goods.findByPk(1); console.log(测试查询商品ID 1:, testGoods ? testGoods.name : 未找到); // 测试直接查询购物车 const testCart await Cart.findByPk(3); console.log(测试查询购物车ID 3:, testCart ? 商品ID: ${testCart.goodsId} : 未找到); // 测试关联查询 const cartItems await Cart.findAll({ where: { userId: req.user.id }, include: [{ model: Goods, as: Good, include: [{ model: Category, attributes: [name] }] }], order: [[createdAt, DESC]] }); console.log(找到购物车商品数量:, cartItems.length); console.log(购物车原始数据:, JSON.stringify(cartItems, null, 2)); // 确保商品价格为数字类型并适配前端字段名 const formattedCartItems cartItems.map(item { // 调试检查原始数据中的商品信息 console.log(处理购物车项 ID: ${item.id}, 商品ID: ${item.goodsId}); console.log(原始商品数据:, JSON.stringify(item.Goods || item.Good, null, 2)); // 使用正确的属性名访问商品信息 const goodsInfo item.Good; return { ...item.toJSON(), // 前端期望的字段名 goods_id: item.goodsId, num: item.quantity, goodsName: goodsInfo ? goodsInfo.name : 未知商品, price: goodsInfo ? parseFloat(goodsInfo.price) || 0 : 0, checked: item.selected ! false, // 默认选中 Good: goodsInfo ? { ...goodsInfo.toJSON ? goodsInfo.toJSON() : goodsInfo, price: parseFloat(goodsInfo.price) || 0 } : null }; }); console.log(格式化后的购物车数据:, JSON.stringify(formattedCartItems, null, 2)); res.json({ code: 200, msg: 获取购物车成功, data: formattedCartItems }); } catch (error) { console.error(获取购物车失败, error); res.status(500).json({ code: 500, msg: 服务器错误 }); } }); // 添加商品到购物车 router.post(/add, auth, async (req, res) { try { console.log(添加商品到购物车用户ID:, req.user.id); console.log(请求体数据:, req.body); const { goods_id, goodsId, num 1 } req.body; // 兼容前端两种参数名 const finalGoodsId goods_id || goodsId; console.log(最终商品ID:, finalGoodsId, 数量:, num); if (!finalGoodsId) { return res.status(400).json({ code: 400, msg: 商品ID不能为空 }); } // 检查商品是否存在 const goods await Goods.findByPk(finalGoodsId); console.log(查询到的商品:, goods ? goods.name : 不存在); if (!goods) { return res.status(404).json({ code: 404, msg: 商品不存在 }); } // 检查库存 if (goods.stock num) { return res.status(400).json({ code: 400, msg: 库存不足 }); } // 查找是否已存在 const existingCart await Cart.findOne({ where: { userId: req.user.id, goodsId: finalGoodsId } }); console.log(是否已存在于购物车:, !!existingCart); if (existingCart) { // 更新数量 const newQuantity existingCart.quantity num; if (goods.stock newQuantity) { return res.status(400).json({ code: 400, msg: 库存不足 }); } await existingCart.update({ quantity: newQuantity }); console.log(更新购物车数量为:, newQuantity); } else { // 创建新记录 await Cart.create({ userId: req.user.id, goodsId: finalGoodsId, quantity: num }); console.log(创建新购物车记录); } res.json({ code: 200, msg: 添加成功 }); } catch (error) { console.error(添加购物车失败, error); res.status(500).json({ code: 500, msg: 服务器错误 }); } }); // 更新购物车商品数量 router.put(/update, auth, async (req, res) { try { const { id, quantity, num } req.body; // 兼容前端两种参数名 const finalQuantity quantity ! undefined ? quantity : num; if (!id || finalQuantity undefined) { return res.status(400).json({ code: 400, msg: 参数不完整 }); } if (finalQuantity 0) { return res.status(400).json({ code: 400, msg: 数量必须大于0 }); } const cartItem await Cart.findOne({ where: { id, userId: req.user.id }, include: [{ model: Goods, as: Good }] }); if (!cartItem) { return res.status(404).json({ code: 404, msg: 购物车商品不存在 }); } // 检查库存 if (cartItem.Good.stock finalQuantity) { return res.status(400).json({ code: 400, msg: 库存不足 }); } await cartItem.update({ quantity: finalQuantity }); res.json({ code: 200, msg: 更新成功 }); } catch (error) { console.error(更新购物车失败, error); res.status(500).json({ code: 500, msg: 服务器错误 }); } }); // 删除购物车商品 router.delete(/delete, auth, async (req, res) { try { const { id } req.query; if (!id) { return res.status(400).json({ code: 400, msg: 购物车ID不能为空 }); } const deleted await Cart.destroy({ where: { id, userId: req.user.id } }); if (deleted 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: 服务器错误 }); } }); // 兼容前端POST请求删除购物车商品 router.post(/delete, auth, async (req, res) { try { const { id } req.body; if (!id) { return res.status(400).json({ code: 400, msg: 购物车ID不能为空 }); } const deleted await Cart.destroy({ where: { id, userId: req.user.id } }); if (deleted 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: 服务器错误 }); } }); module.exports router;订单路由const express require(express); const router express.Router(); const Order require(../models/Order); const OrderItem require(../models/OrderItem); const Cart require(../models/Cart); const Address require(../models/Address); const Goods require(../models/Goods); const { auth } require(../middleware/auth); // 生成订单编号 function generateOrderNumber() { const date new Date(); const year date.getFullYear(); const month String(date.getMonth() 1).padStart(2, 0); const day String(date.getDate()).padStart(2, 0); const random Math.floor(Math.random() * 10000).toString().padStart(4, 0); return ORD${year}${month}${day}${random}; } // 获取用户订单列表 router.get(/orders, auth, async (req, res) { try { console.log(订单列表API被调用用户:, req.user.username); const { status } req.query; console.log(请求参数:, { status, userId: req.user.id }); let whereCondition { userId: req.user.id }; if (status status ! ) { whereCondition.status status; } console.log(查询条件:, whereCondition); const orders await Order.findAll({ where: whereCondition, include: [ { model: Address, as: address }, { model: require(../models/User), as: rider, attributes: [username] }, { model: OrderItem, include: [{ model: Goods, as: Good, attributes: [id, name, price] }] } ], order: [[createdAt, DESC]] }); console.log(查询到订单数量:, orders.length); // 格式化订单数据确保价格是数字类型 const formattedOrders orders.map(order { const orderData { ...order.toJSON(), totalAmount: parseFloat(order.totalAmount) || 0, // 前端期望的字段名 orderNo: order.orderNumber, totalPrice: parseFloat(order.totalAmount) || 0, goodsList: order.OrderItems.map(item ({ goodsId: item.goodsId, name: item.Good ? item.Good.name : 未知商品, price: parseFloat(item.price) || 0, count: item.quantity })) }; // 删除不需要的字段 delete orderData.OrderItems; return orderData; }); console.log(返回订单数据成功); res.json({ code: 200, msg: 获取订单列表成功, data: formattedOrders }); } catch (error) { console.error(获取订单列表失败, error); console.error(错误详情:, error.message); console.error(错误堆栈:, error.stack); res.status(500).json({ code: 500, msg: 服务器错误, error: process.env.NODE_ENV development ? error.message : undefined }); } }); // 获取选中的购物车商品 router.get(/cart/selected, auth, async (req, res) { try { console.log(获取选中购物车商品API被调用); console.log(用户ID:, req.user.id); // 支持两种方式1. 查询参数中的cartIds 2. 获取所有选中的商品 const { cartIds } req.query; let whereCondition { userId: req.user.id }; if (cartIds) { // 如果提供了cartIds只获取这些ID的商品 // 处理cartIds可能是数组或字符串的情况 let cartIdArray; if (Array.isArray(cartIds)) { cartIdArray cartIds; } else { cartIdArray cartIds.split(,).map(id parseInt(id.trim())); } whereCondition.id cartIdArray; console.log(使用cartIds查询:, cartIdArray); } else { // 否则获取所有选中的商品 whereCondition.selected true; console.log(查询所有选中的商品); } console.log(查询条件:, whereCondition); const cartItems await Cart.findAll({ where: whereCondition, include: [{ model: Goods, as: Good }] }); console.log(查询到的购物车项数量:, cartItems.length); // 计算总金额并格式化数据 let totalAmount 0; const formattedItems cartItems.map(item { // 检查商品信息是否存在 if (!item.Good) { console.error(购物车项 ID ${item.id} 缺少关联的商品信息); return null; } const price parseFloat(item.Good.price) || 0; const quantity item.quantity || 1; const itemTotal price * quantity; totalAmount itemTotal; return { id: item.id, goods_id: item.goodsId, name: item.Good.name, price: price, count: quantity, stock: item.Good.stock }; }).filter(item item ! null); // 过滤掉null项 res.json({ code: 200, msg: 获取选中商品成功, data: formattedItems, totalAmount: totalAmount.toFixed(2) }); } catch (error) { console.error(获取选中商品失败, error); res.status(500).json({ code: 500, msg: 服务器错误 }); } }); // 创建订单 router.post(/order, auth, async (req, res) { const transaction await require(../config/db).transaction(); try { const { addressId, notes, cartIds } req.body; // 获取选中的购物车商品 let whereCondition { userId: req.user.id }; if (cartIds) { // 如果提供了cartIds只获取这些ID的商品 // 处理cartIds可能是数组或字符串的情况 let cartIdArray; if (Array.isArray(cartIds)) { cartIdArray cartIds; } else { cartIdArray cartIds.split(,).map(id parseInt(id.trim())); } whereCondition.id cartIdArray; } else { // 否则获取所有选中的商品 whereCondition.selected true; } const cartItems await Cart.findAll({ where: whereCondition, include: [{ model: Goods, as: Good }], transaction }); if (cartItems.length 0) { await transaction.rollback(); return res.status(400).json({ code: 400, msg: 没有选中商品 }); } // 检查地址是否存在 const address await Address.findOne({ where: { id: addressId, userId: req.user.id }, transaction }); if (!address) { await transaction.rollback(); return res.status(400).json({ code: 400, msg: 收货地址不存在 }); } // 检查库存并计算总金额 let totalAmount 0; for (const item of cartItems) { if (item.Good.stock item.quantity) { await transaction.rollback(); return res.status(400).json({ code: 400, msg: 商品 ${item.Good.name} 库存不足 }); } totalAmount parseFloat(item.Good.price) * item.quantity; } // 创建订单 const order await Order.create({ orderNumber: generateOrderNumber(), userId: req.user.id, addressId: addressId, totalAmount: totalAmount.toFixed(2), status: paid, // 创建后直接设为已支付状态 paymentStatus: paid, paymentMethod: online, notes: notes || }, { transaction }); // 创建订单项 for (const item of cartItems) { await OrderItem.create({ orderId: order.id, goodsId: item.goodsId, quantity: item.quantity, price: item.Good.price, totalPrice: (parseFloat(item.Good.price) * item.quantity).toFixed(2) }, { transaction }); // 减少库存 await Goods.update({ stock: item.Good.stock - item.quantity }, { where: { id: item.goodsId }, transaction }); } // 清空已购买的购物车商品 await Cart.destroy({ where: whereCondition, transaction }); await transaction.commit(); res.json({ code: 200, msg: 订单创建成功, data: { orderId: order.id, orderNumber: order.orderNumber, totalAmount: order.totalAmount } }); } catch (error) { await transaction.rollback(); console.error(创建订单失败, error); res.status(500).json({ code: 500, msg: 服务器错误 }); } }); // 获取订单详情 router.get(/orders/:id, auth, async (req, res) { try { const orderId req.params.id; const order await Order.findOne({ where: { id: orderId, userId: req.user.id // 确保用户只能查看自己的订单 }, include: [ { model: Address, as: address }, { model: require(../models/User), as: rider, attributes: [username, phone] }, { 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); console.error(错误详情:, error.message); console.error(错误堆栈:, error.stack); res.status(500).json({ code: 500, msg: 服务器错误, error: process.env.NODE_ENV development ? error.message : undefined }); } }); // 更新订单状态 router.put(/orders/:id/status, auth, async (req, res) { try { const orderId req.params.id; const { status } req.body; // 验证订单是否存在且属于当前用户 const order await Order.findOne({ where: { id: orderId, userId: req.user.id } }); if (!order) { return res.status(404).json({ code: 404, msg: 订单不存在 }); } // 验证状态转换是否合法 const validTransitions { pending: [paid, cancelled], // 待支付 - 已支付/已取消 paid: [received], // 已支付 - 已收货 received: [], // 已收货是最终状态 cancelled: [] // 已取消是最终状态 }; if (validTransitions[order.status] !validTransitions[order.status].includes(status)) { return res.status(400).json({ code: 400, msg: 无法从 ${order.status} 状态转换到 ${status} 状态 }); } // 更新订单状态 const [updated] await Order.update( { status }, { where: { id: orderId } } ); if (updated 0) { return res.status(404).json({ code: 404, msg: 订单不存在 }); } res.json({ code: 200, msg: 订单状态更新成功 }); } catch (error) { console.error(更新订单状态失败, error); console.error(错误详情:, error.message); console.error(错误堆栈:, error.stack); res.status(500).json({ code: 500, msg: 服务器错误, error: process.env.NODE_ENV development ? error.message : undefined }); } }); // 批量删除购物车商品 router.post(/cart/batch-delete, auth, async (req, res) { try { const { ids } req.body; if (!ids || !Array.isArray(ids) || ids.length 0) { return res.status(400).json({ code: 400, msg: 商品ID列表不能为空 }); } const deleted await Cart.destroy({ where: { id: ids, userId: req.user.id } }); res.json({ code: 200, msg: 成功删除 ${deleted} 个商品, data: { deletedCount: deleted } }); } catch (error) { console.error(批量删除失败, error); res.status(500).json({ code: 500, msg: 服务器错误 }); } }); module.exports router;前端开发购物车页面与下单流程template div classcart-page div classcart-container !-- 标题 -- div classpage-title我的购物车/div !-- 购物车列表 -- div classcart-list v-ifcartList.length div classcart-item v-foritem in cartList :keyitem.id div classitem-checkbox el-checkbox v-modelitem.checked changecalcTotalPrice/el-checkbox /div div classitem-info div classgoods-name{{ item.goodsName }}/div div classgoods-price¥{{ item.price.toFixed(2) }}/div /div div classitem-count button classcount-btn clickhandleMinus(item) :disableditem.num 1-/button span classcount-num{{ item.num }}/span button classcount-btn clickhandlePlus(item)/button /div div classitem-actions span clickhandleDelete(item.id)删除/span /div /div /div !-- 空状态 -- div classempty-state v-else div classempty-icon/div div classempty-text购物车空空如也快去逛逛吧/div button classgo-shop-btn click$router.push(/user)去首页购物/button /div !-- 底部结算栏 -- div classcart-footer v-ifcartList.length div classfooter-left el-checkbox v-modelallChecked changehandleAllCheck全选/el-checkbox /div div classfooter-middle span classtotal-text合计/span span classtotal-price¥{{ totalPrice.toFixed(2) }}/span /div div classfooter-right button classcheckout-btn clickhandleCheckout结算/button /div /div /div /div /template script setup import { ref, computed, onMounted } from vue; import { useRouter } from vue-router; import { ElMessage, ElMessageBox, ElCheckbox } from element-plus; import request from ../../utils/request; const router useRouter(); const cartList ref([]); const totalPrice ref(0); const token localStorage.getItem(token); // 全选状态 const allChecked computed({ get() { return cartList.value.every(item item.checked) cartList.value.length 0; }, set(val) { cartList.value.forEach(item { item.checked val; }); calcTotalPrice(); } }); // 获取购物车列表后端接口/cart/list const getCartList async () { try { const res await request({ url: /cart/list, method: GET }); if (res res.code 200) { // 后端已经格式化数据直接使用 cartList.value res.data; calcTotalPrice(); } } catch (error) { ElMessage.error(获取购物车失败); } }; onMounted(() { if (!token) { ElMessage.warning(请先登录); router.push(/user/login); return; } getCartList(); }); // 计算总价 const calcTotalPrice () { totalPrice.value cartList.value .filter(item item.checked) .reduce((sum, item) sum item.price * item.num, 0); }; // 数量减后端接口/cart/update const handleMinus async (item) { try { const res await request({ url: /cart/update, method: PUT, data: { id: item.id, quantity: item.num - 1 } }); if (res res.code 200) { item.num - 1; calcTotalPrice(); } } catch (error) { ElMessage.error(修改数量失败); } }; // 数量加后端接口/cart/update const handlePlus async (item) { try { const res await request({ url: /cart/update, method: PUT, data: { id: item.id, quantity: item.num 1 } }); if (res res.code 200) { item.num 1; calcTotalPrice(); } } catch (error) { ElMessage.error(修改数量失败); } }; // 删除购物车商品后端接口/cart/delete const handleDelete async (id) { try { await ElMessageBox.confirm(确定要删除该商品吗, 提示, { type: warning }); const res await request({ url: /cart/delete, method: POST, data: { id } }); if (res res.code 200) { ElMessage.success(删除成功); getCartList(); // 刷新列表 } } catch (error) { if (error ! cancel) { ElMessage.error(删除失败); } } }; // 全选/取消全选 const handleAllCheck () { calcTotalPrice(); }; // 结算 const handleCheckout () { const checkedGoods cartList.value.filter(item item.checked); if (!checkedGoods.length) { return ElMessage.warning(请选择要结算的商品); } // 跳转到结算页携带选中商品ID用逗号分隔 router.push({ path: /user/confirm-order, query: { cartIds: checkedGoods.map(item item.id).join(,) } }); }; /script style scoped .cart-page { min-height: 100vh; background-color: #f5f5f5; display: flex; align-items: flex-start; justify-content: center; padding: 20px; padding-top: 40px; box-sizing: border-box; } .cart-container { width: 100%; max-width: 800px; background-color: #ffffff; padding: 32px; border-radius: 12px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); box-sizing: border-box; display: flex; flex-direction: column; min-height: 600px; } .page-title { font-size: 20px; font-weight: 600; color: #333333; margin-bottom: 24px; text-align: center; } .cart-list { flex: 1; } .cart-item { display: flex; align-items: center; padding: 16px; border-bottom: 1px solid #e5e7eb; } .item-checkbox { width: 50px; text-align: center; } .item-info { flex: 1; } .goods-name { font-size: 14px; color: #333; margin-bottom: 8px; line-height: 1.4; } .goods-price { font-size: 14px; color: #e53e3e; font-weight: 500; } .item-count { display: flex; align-items: center; width: 100px; } .count-btn { width: 28px; height: 28px; border: 1px solid #e5e7eb; background-color: #f5f5f5; color: #333; border-radius: 4px; cursor: pointer; display: flex; align-items: center; justify-content: center; } .count-btn:disabled { background-color: #eee; color: #999; cursor: not-allowed; } .count-num { width: 40px; text-align: center; font-size: 14px; } .item-actions { width: 80px; text-align: center; } .item-actions span { font-size: 14px; color: #4299e1; cursor: pointer; } .item-actions span:hover { text-decoration: underline; } .empty-state { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 40px 0; } .empty-icon { font-size: 64px; margin-bottom: 16px; } .empty-text { font-size: 16px; color: #999; margin-bottom: 24px; } .go-shop-btn { padding: 12px 24px; background-color: #4299e1; color: #fff; border: none; border-radius: 6px; font-size: 14px; cursor: pointer; transition: background-color 0.2s; } .go-shop-btn:hover { background-color: #3a86cf; } .cart-footer { display: flex; align-items: center; justify-content: space-between; padding: 16px 0; border-top: 1px solid #e5e7eb; margin-top: 20px; } .footer-left { padding-left: 16px; } .footer-middle { text-align: right; } .total-text { font-size: 14px; color: #666; } .total-price { font-size: 18px; color: #e53e3e; font-weight: 600; margin-left: 8px; } .footer-right { padding-right: 16px; } .checkout-btn { padding: 10px 24px; background-color: #4299e1; color: #fff; border: none; border-radius: 6px; font-size: 14px; cursor: pointer; transition: background-color 0.2s; } .checkout-btn:hover { background-color: #3a86cf; } /style测试任务订单流程测试成果端到端测试通过缓存问题已解决本日小结与明日计划今日总结下单流程通畅明日计划骑手与管理端功能快接近尾声了