Compare commits
	
		
			16 Commits
		
	
	
		
			5d50c42e3e
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| bca4b412c9 | |||
| 2217ccf30c | |||
| be8b182e82 | |||
| 27a3f564ed | |||
| 4774262178 | |||
| e535e8a9c5 | |||
| f9a757046c | |||
| 69a445cebd | |||
| 486d3179d3 | |||
| b512c6479d | |||
| 3abcd5e46a | |||
| 2a16a7fd97 | |||
| 62630ef750 | |||
| 69d2719eeb | |||
| 077686a0fc | |||
| 305ac5dde6 | 
							
								
								
									
										132
									
								
								routes/cart.js
									
									
									
									
									
								
							
							
						
						
									
										132
									
								
								routes/cart.js
									
									
									
									
									
								
							| @@ -473,71 +473,6 @@ router.put('/:id', auth, async (req, res) => { | |||||||
|   } |   } | ||||||
| }); | }); | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @swagger |  | ||||||
|  * /api/cart/{id}: |  | ||||||
|  *   delete: |  | ||||||
|  *     summary: 删除购物车商品 |  | ||||||
|  *     tags: [Cart] |  | ||||||
|  *     security: |  | ||||||
|  *       - bearerAuth: [] |  | ||||||
|  *     parameters: |  | ||||||
|  *       - in: path |  | ||||||
|  *         name: id |  | ||||||
|  *         required: true |  | ||||||
|  *         schema: |  | ||||||
|  *           type: integer |  | ||||||
|  *         description: 购物车项ID |  | ||||||
|  *     responses: |  | ||||||
|  *       200: |  | ||||||
|  *         description: 删除购物车商品成功 |  | ||||||
|  *         content: |  | ||||||
|  *           application/json: |  | ||||||
|  *             schema: |  | ||||||
|  *               type: object |  | ||||||
|  *               properties: |  | ||||||
|  *                 success: |  | ||||||
|  *                   type: boolean |  | ||||||
|  *                 message: |  | ||||||
|  *                   type: string |  | ||||||
|  *       401: |  | ||||||
|  *         description: 未授权 |  | ||||||
|  *       404: |  | ||||||
|  *         description: 购物车项不存在 |  | ||||||
|  *       500: |  | ||||||
|  *         description: 服务器错误 |  | ||||||
|  */ |  | ||||||
| router.delete('/:id', auth, async (req, res) => { |  | ||||||
|   try { |  | ||||||
|     const cartItemId = req.params.id; |  | ||||||
|     const userId = req.user.id; |  | ||||||
|      |  | ||||||
|     // 检查购物车项是否存在且属于当前用户 |  | ||||||
|     const [cartItems] = await getDB().execute( |  | ||||||
|       'SELECT id FROM cart_items WHERE id = ? AND user_id = ?', |  | ||||||
|       [cartItemId, userId] |  | ||||||
|     ); |  | ||||||
|      |  | ||||||
|     if (cartItems.length === 0) { |  | ||||||
|       return res.status(404).json({ success: false, message: '购物车项不存在' }); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // 删除购物车项 |  | ||||||
|     await getDB().execute( |  | ||||||
|       'DELETE FROM cart_items WHERE id = ?', |  | ||||||
|       [cartItemId] |  | ||||||
|     ); |  | ||||||
|      |  | ||||||
|     res.json({ |  | ||||||
|       success: true, |  | ||||||
|       message: '删除购物车商品成功' |  | ||||||
|     }); |  | ||||||
|   } catch (error) { |  | ||||||
|     console.error('删除购物车商品失败:', error); |  | ||||||
|     res.status(500).json({ success: false, message: '删除购物车商品失败' }); |  | ||||||
|   } |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @swagger |  * @swagger | ||||||
|  * /api/cart/batch: |  * /api/cart/batch: | ||||||
| @@ -654,6 +589,7 @@ router.delete('/clear', auth, async (req, res) => { | |||||||
|       success: true, |       success: true, | ||||||
|       message: '清空购物车成功' |       message: '清空购物车成功' | ||||||
|     }); |     }); | ||||||
|  |     console.log(11111111111111) | ||||||
|   } catch (error) { |   } catch (error) { | ||||||
|     console.error('清空购物车失败:', error); |     console.error('清空购物车失败:', error); | ||||||
|     res.status(500).json({ success: false, message: '清空购物车失败' }); |     res.status(500).json({ success: false, message: '清空购物车失败' }); | ||||||
| @@ -932,4 +868,70 @@ router.post('/checkout', auth, async (req, res) => { | |||||||
|   } |   } | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @swagger | ||||||
|  |  * /api/cart/{id}: | ||||||
|  |  *   delete: | ||||||
|  |  *     summary: 删除购物车商品 | ||||||
|  |  *     tags: [Cart] | ||||||
|  |  *     security: | ||||||
|  |  *       - bearerAuth: [] | ||||||
|  |  *     parameters: | ||||||
|  |  *       - in: path | ||||||
|  |  *         name: id | ||||||
|  |  *         required: true | ||||||
|  |  *         schema: | ||||||
|  |  *           type: integer | ||||||
|  |  *         description: 购物车项ID | ||||||
|  |  *     responses: | ||||||
|  |  *       200: | ||||||
|  |  *         description: 删除购物车商品成功 | ||||||
|  |  *         content: | ||||||
|  |  *           application/json: | ||||||
|  |  *             schema: | ||||||
|  |  *               type: object | ||||||
|  |  *               properties: | ||||||
|  |  *                 success: | ||||||
|  |  *                   type: boolean | ||||||
|  |  *                 message: | ||||||
|  |  *                   type: string | ||||||
|  |  *       401: | ||||||
|  |  *         description: 未授权 | ||||||
|  |  *       404: | ||||||
|  |  *         description: 购物车项不存在 | ||||||
|  |  *       500: | ||||||
|  |  *         description: 服务器错误 | ||||||
|  |  */ | ||||||
|  | router.delete('/:id', auth, async (req, res) => { | ||||||
|  |   try { | ||||||
|  |     console.log(111111111) | ||||||
|  |     const cartItemId = req.params.id; | ||||||
|  |     const userId = req.user.id; | ||||||
|  |      | ||||||
|  |     // 检查购物车项是否存在且属于当前用户 | ||||||
|  |     const [cartItems] = await getDB().execute( | ||||||
|  |       'SELECT id FROM cart_items WHERE id = ? AND user_id = ?', | ||||||
|  |       [cartItemId, userId] | ||||||
|  |     ); | ||||||
|  |      | ||||||
|  |     if (cartItems.length === 0) { | ||||||
|  |       return res.status(404).json({ success: false, message: '购物车项不存在' }); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // 删除购物车项 | ||||||
|  |     await getDB().execute( | ||||||
|  |       'DELETE FROM cart_items WHERE id = ?', | ||||||
|  |       [cartItemId] | ||||||
|  |     ); | ||||||
|  |      | ||||||
|  |     res.json({ | ||||||
|  |       success: true, | ||||||
|  |       message: '删除购物车商品成功' | ||||||
|  |     }); | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('删除购物车商品失败:', error); | ||||||
|  |     res.status(500).json({ success: false, message: '删除购物车商品失败' }); | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  |  | ||||||
| module.exports = router; | module.exports = router; | ||||||
							
								
								
									
										68
									
								
								routes/category.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								routes/category.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | |||||||
|  | const express = require('express'); | ||||||
|  | const { getDB } = require('../database'); | ||||||
|  | const router = express.Router(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @swagger | ||||||
|  |  * /api/category: | ||||||
|  |  *   get: | ||||||
|  |  *     summary: 获取一级分类及其二级分类 | ||||||
|  |  *     description: 返回所有一级分类,每个一级分类包含其下的二级分类 | ||||||
|  |  *     tags: [category] | ||||||
|  |  *     responses: | ||||||
|  |  *       200: | ||||||
|  |  *         description: 成功返回一级分类及其二级分类 | ||||||
|  |  *         content: | ||||||
|  |  *           application/json: | ||||||
|  |  *             schema: | ||||||
|  |  *               type: object | ||||||
|  |  *               properties: | ||||||
|  |  *                 success: | ||||||
|  |  *                   type: boolean | ||||||
|  |  *                   example: true | ||||||
|  |  *                 data: | ||||||
|  |  *                   type: array | ||||||
|  |  *                   items: | ||||||
|  |  *                     type: object | ||||||
|  |  *                     properties: | ||||||
|  |  *                       name: | ||||||
|  |  *                         type: string | ||||||
|  |  *                         example: "一级分类1" | ||||||
|  |  *                       relative: | ||||||
|  |  *                         type: array | ||||||
|  |  *                         items: | ||||||
|  |  *                           type: object | ||||||
|  |  *                           properties: | ||||||
|  |  *                             name: | ||||||
|  |  *                               type: string | ||||||
|  |  *                               example: "二级分类1-1" | ||||||
|  |  *                             img: | ||||||
|  |  *                               type: string | ||||||
|  |  *                               example: "https://example.com/image.jpg" | ||||||
|  |  */ | ||||||
|  | router.get('/', async (req, res) => { | ||||||
|  |   try { | ||||||
|  |     const db = await getDB(); | ||||||
|  |     const [firstCategory] = await db.query('SELECT * FROM category WHERE level = 1'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     for (const category of firstCategory) { | ||||||
|  |       const [secondCategories] = await db.query('SELECT * FROM category WHERE parent_id = ?', [category.id]); | ||||||
|  |       category.relative = secondCategories; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     res.json({ success: true, data: firstCategory.map(category => ({ | ||||||
|  |       name: category.category_name, | ||||||
|  |       relative: category.relative.map(secondCategory => ({ | ||||||
|  |         name: secondCategory.category_name, | ||||||
|  |         img: secondCategory.image, | ||||||
|  |       })) | ||||||
|  |     }))}); | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('Error fetching categories:', error); | ||||||
|  |     res.status(500).json({ success: false, error: 'Internal server error' }); | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | module.exports = router; | ||||||
							
								
								
									
										248
									
								
								routes/coupon.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								routes/coupon.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,248 @@ | |||||||
|  | const express = require('express'); | ||||||
|  | const router = express.Router(); | ||||||
|  | const { getDB } = require('../database'); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @swagger | ||||||
|  |  * /api/coupon: | ||||||
|  |  *   get: | ||||||
|  |  *     summary: 获取用户优惠券 | ||||||
|  |  *     description: 返回用户所有优惠券,包含是否已领取状态 | ||||||
|  |  *     tags: [coupon] | ||||||
|  |  *     parameters: | ||||||
|  |  *       - in: query | ||||||
|  |  *         name: user_id | ||||||
|  |  *         schema: | ||||||
|  |  *           type: string | ||||||
|  |  *         required: true | ||||||
|  |  *         description: 用户ID | ||||||
|  |  *     responses: | ||||||
|  |  *       200: | ||||||
|  |  *         description: 成功返回用户优惠券 | ||||||
|  |  *         content: | ||||||
|  |  *           application/json: | ||||||
|  |  *             schema: | ||||||
|  |  *               type: object | ||||||
|  |  *               properties: | ||||||
|  |  *                 success: | ||||||
|  |  *                   type: boolean | ||||||
|  |  *                   example: true | ||||||
|  |  *                 data: | ||||||
|  |  *                   type: array | ||||||
|  |  *                   items: | ||||||
|  |  *                     type: object | ||||||
|  |  *                     properties: | ||||||
|  |  *                       id: | ||||||
|  |  *                         type: string | ||||||
|  |  *                         example: "1" | ||||||
|  |  *                       name: | ||||||
|  |  *                         type: string | ||||||
|  |  *                         example: "满减优惠券" | ||||||
|  |  *                       discount: | ||||||
|  |  *                         type: number | ||||||
|  |  *                         example: 100 | ||||||
|  |  *                       remain: | ||||||
|  |  *                         type: number | ||||||
|  |  *                         example: 10 | ||||||
|  |  *                       got: | ||||||
|  |  *                         type: boolean | ||||||
|  |  *                         example: false | ||||||
|  |  *                         description: 是否已领取 | ||||||
|  |  *  | ||||||
|  |  *       400: | ||||||
|  |  *         description: 缺少用户ID参数 | ||||||
|  |  *       500: | ||||||
|  |  *         description: 服务器内部错误 | ||||||
|  |  */ | ||||||
|  | router.get('/', async (req, res) => { | ||||||
|  |   try { | ||||||
|  |     const useId = req.query.user_id; | ||||||
|  |     const db = getDB(); | ||||||
|  |     const query = ` | ||||||
|  |       SELECT * FROM coupon_products | ||||||
|  |     ` | ||||||
|  |     const [coupon] = await db.query(query); | ||||||
|  |  | ||||||
|  |     if (coupon.length === 0) { | ||||||
|  |       res.json({ message: '暂无优惠券' }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     for(const item of coupon) { | ||||||
|  |       const query = ` | ||||||
|  |         SELECT * FROM coupon_use WHERE user_id = ? AND coupon_id = ? | ||||||
|  |       ` | ||||||
|  |       const [couponExist] = await db.query(query, [useId, item.id]); | ||||||
|  |       if (couponExist.length === 0) { | ||||||
|  |         item.got = false; | ||||||
|  |       } else { | ||||||
|  |         item.got = true; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     res.json({coupon, success: true}); | ||||||
|  |   } catch (error) { | ||||||
|  |     res.status(500).json({ error: '获取优惠券失败' }); | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @swagger | ||||||
|  |  * /api/coupon/{id}: | ||||||
|  |  *   get: | ||||||
|  |  *     summary: 用户领取优惠券 | ||||||
|  |  *     description: 用户通过优惠券ID领取优惠券,优惠券数量减一 | ||||||
|  |  *     tags: [coupon] | ||||||
|  |  *     parameters: | ||||||
|  |  *       - in: path | ||||||
|  |  *         name: id | ||||||
|  |  *         schema: | ||||||
|  |  *           type: string | ||||||
|  |  *         required: true | ||||||
|  |  *         description: 用户ID | ||||||
|  |  *       - in: query | ||||||
|  |  *         name: coupon_id | ||||||
|  |  *         schema: | ||||||
|  |  *           type: string | ||||||
|  |  *         required: true | ||||||
|  |  *         description: 优惠券ID | ||||||
|  |  *     responses: | ||||||
|  |  *       200: | ||||||
|  |  *         description: 成功领取优惠券 | ||||||
|  |  *         content: | ||||||
|  |  *           application/json: | ||||||
|  |  *             schema: | ||||||
|  |  *               type: object | ||||||
|  |  *               properties: | ||||||
|  |  *                 success: | ||||||
|  |  *                   type: boolean | ||||||
|  |  *                   example: true | ||||||
|  |  *       400: | ||||||
|  |  *         description: 缺少用户ID或优惠券ID参数 | ||||||
|  |  *       500: | ||||||
|  |  *         description: 服务器内部错误 | ||||||
|  |  */ | ||||||
|  | router.get('/:id', async (req, res) => { | ||||||
|  |   try { | ||||||
|  |     const db = getDB(); | ||||||
|  |  | ||||||
|  |     const userId = req.params.id; | ||||||
|  |     const receiveCouponId = req.query.coupon_id; | ||||||
|  |  | ||||||
|  |     const query = ` | ||||||
|  |       SELECT * FROM coupon_use WHERE user_id = ? AND coupon_id = ? | ||||||
|  |     ` | ||||||
|  |     const [couponExist] = await db.query(query, [userId, receiveCouponId]); | ||||||
|  |  | ||||||
|  |     if (couponExist.length === 0) { | ||||||
|  |  | ||||||
|  |       const insertQuery = ` | ||||||
|  |         INSERT INTO coupon_use (user_id, coupon_id, get_time) VALUES (?, ?, now()) | ||||||
|  |       ` | ||||||
|  |       await db.query(insertQuery, [userId, receiveCouponId]); | ||||||
|  |  | ||||||
|  |       const updateQuery = ` | ||||||
|  |         UPDATE coupon_products SET remain = remain - 1 WHERE id = ? | ||||||
|  |       ` | ||||||
|  |       await db.query(updateQuery, [receiveCouponId]); | ||||||
|  |  | ||||||
|  |     } else { | ||||||
|  |       return res.status(500).json({ error: '已有该优惠券' }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     res.json({success: true}); | ||||||
|  |   } catch (error) { | ||||||
|  |     console.log(error); | ||||||
|  |     res.status(500).json({ error: '获取优惠券失败' }); | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @swagger | ||||||
|  |  * /api/coupon/user/{id}: | ||||||
|  |  *   get: | ||||||
|  |  *     summary: 获取用户优惠券 | ||||||
|  |  *     description: 返回用户领取的优惠券,包括优惠券信息和商品信息 | ||||||
|  |  *     tags: [coupon] | ||||||
|  |  *     parameters: | ||||||
|  |  *       - in: path | ||||||
|  |  *         name: id | ||||||
|  |  *         schema: | ||||||
|  |  *           type: string | ||||||
|  |  *         required: true | ||||||
|  |  *         description: 用户ID | ||||||
|  |  *     responses: | ||||||
|  |  *       200: | ||||||
|  |  *         description: 成功返回用户优惠券 | ||||||
|  |  *         content: | ||||||
|  |  *           application/json: | ||||||
|  |  *             schema: | ||||||
|  |  *               type: object | ||||||
|  |  *               properties: | ||||||
|  |  *                 success: | ||||||
|  |  *                   type: boolean | ||||||
|  |  *                   example: true | ||||||
|  |  *                 data: | ||||||
|  |  *                   type: array | ||||||
|  |  *                   items: | ||||||
|  |  *                     type: object | ||||||
|  |  *                     properties: | ||||||
|  |  *                       id: | ||||||
|  |  *                         type: string | ||||||
|  |  *                         example: "1" | ||||||
|  |  *                       name: | ||||||
|  |  *                         type: string | ||||||
|  |  *                         example: "满减优惠券" | ||||||
|  |  *                       discount: | ||||||
|  |  *                         type: number | ||||||
|  |  *                         example: 100 | ||||||
|  |  *                       products_id: | ||||||
|  |  *                         type: array | ||||||
|  |  *                         items: | ||||||
|  |  *                           type: string | ||||||
|  |  *                           example: "1" | ||||||
|  |  *                       remain: | ||||||
|  |  *                         type: number | ||||||
|  |  *                         example: 100 | ||||||
|  |  *                       get_time: | ||||||
|  |  *                         type: string | ||||||
|  |  *                         format: date-time | ||||||
|  |  *                         example: "2023-01-01T00:00:00.000Z" | ||||||
|  |  *       400: | ||||||
|  |  *         description: 缺少用户ID参数 | ||||||
|  |  *       500: | ||||||
|  |  *         description: 服务器内部错误 | ||||||
|  |  */ | ||||||
|  | router.get('/user/:id', async (req, res) => { | ||||||
|  |   try { | ||||||
|  |     const db = getDB(); | ||||||
|  |     const userId = req.params.id; | ||||||
|  |  | ||||||
|  |     const query = ` | ||||||
|  |       SELECT * FROM coupon_use WHERE user_id = ? | ||||||
|  |     ` | ||||||
|  |     const [coupon] = await db.query(query, [userId]); | ||||||
|  |  | ||||||
|  |     for (const item of coupon) { | ||||||
|  |       const query = ` | ||||||
|  |         SELECT * FROM coupon_products WHERE id = ? | ||||||
|  |       ` | ||||||
|  |       const [couponInfo] = await db.query(query, [item.coupon_id]); | ||||||
|  |       item.couponInfo = couponInfo[0]; | ||||||
|  |       item.couponInfo.products = []; | ||||||
|  |       for (const product of item.couponInfo.products_id) { | ||||||
|  |         const query = ` | ||||||
|  |           SELECT name FROM products WHERE id = ? | ||||||
|  |         ` | ||||||
|  |         const [productInfo] = await db.query(query, [product]); | ||||||
|  |         item.couponInfo.products.push(productInfo[0]); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     res.json({success: true, coupon}); | ||||||
|  |   } catch (error) { | ||||||
|  |     console.log(error); | ||||||
|  |     res.status(500).json({ error: '加载优惠券失败' }); | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | module.exports = router; | ||||||
							
								
								
									
										407
									
								
								routes/orders.js
									
									
									
									
									
								
							
							
						
						
									
										407
									
								
								routes/orders.js
									
									
									
									
									
								
							| @@ -77,7 +77,7 @@ router.get('/', auth, async (req, res) => { | |||||||
|       SELECT  |       SELECT  | ||||||
|         o.id, o.order_no, o.user_id, o.total_amount, o.total_points,  |         o.id, o.order_no, o.user_id, o.total_amount, o.total_points,  | ||||||
|         o.status, o.address, o.created_at, o.updated_at,o.total_rongdou, |         o.status, o.address, o.created_at, o.updated_at,o.total_rongdou, | ||||||
|         u.username |         u.username, o.real_rongdou, o.real_points | ||||||
|       FROM orders o |       FROM orders o | ||||||
|       LEFT JOIN users u ON o.user_id = u.id |       LEFT JOIN users u ON o.user_id = u.id | ||||||
|       ${whereClause} |       ${whereClause} | ||||||
| @@ -114,7 +114,7 @@ router.get('/', auth, async (req, res) => { | |||||||
|       } |       } | ||||||
|  |  | ||||||
|       // 处理地址信息 |       // 处理地址信息 | ||||||
|       console.log(order.address,'order.address'); |       // console.log(order.address,'order.address'); | ||||||
|        |        | ||||||
|       if (order.address) { |       if (order.address) { | ||||||
|         try { |         try { | ||||||
| @@ -344,7 +344,8 @@ router.get('/:id', auth, async (req, res) => { | |||||||
|     const query = ` |     const query = ` | ||||||
|       SELECT  |       SELECT  | ||||||
|         o.id, o.order_no, o.user_id, o.total_amount, o.total_points,  |         o.id, o.order_no, o.user_id, o.total_amount, o.total_points,  | ||||||
|         o.status, o.address, o.created_at, o.updated_at, |         o.status, o.address, o.created_at, o.updated_at, o.total_rongdou, | ||||||
|  |         o.real_rongdou, o.real_points, | ||||||
|         u.username, u.phone |         u.username, u.phone | ||||||
|       FROM orders o |       FROM orders o | ||||||
|       LEFT JOIN users u ON o.user_id = u.id |       LEFT JOIN users u ON o.user_id = u.id | ||||||
| @@ -385,7 +386,7 @@ router.get('/:id', auth, async (req, res) => { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     // 处理地址信息 |     // 处理地址信息 | ||||||
|     console.log(order.address,'order.address'); |     // console.log(order.address,'order.address'); | ||||||
|      |      | ||||||
|     if (order.address) { |     if (order.address) { | ||||||
|       try { |       try { | ||||||
| @@ -442,6 +443,27 @@ router.post('/create-from-cart', auth, async (req, res) => { | |||||||
|       return res.status(400).json({ success: false, message: '购物车商品不存在' }); |       return res.status(400).json({ success: false, message: '购物车商品不存在' }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     for (const item of cartItems) { | ||||||
|  |  | ||||||
|  |       const [stock] = await getDB().execute( | ||||||
|  |         `SELECT stock FROM product_spec_combinations WHERE id = ?`, | ||||||
|  |         [item.specification_id] | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       if (stock[0].stock < item.quantity) { | ||||||
|  |         await db.query('ROLLBACK'); | ||||||
|  |         return res.status(400).json({ success: false, message: `商品 ${item.name} 库存不足` }); | ||||||
|  |       } | ||||||
|  |       await db.execute( | ||||||
|  |         `UPDATE product_spec_combinations SET stock = stock - ? WHERE id = ?`, | ||||||
|  |         [item.quantity, item.specification_id] | ||||||
|  |       ); | ||||||
|  |       await db.execute( | ||||||
|  |         `UPDATE products SET stock = stock - ? WHERE id = ?`, | ||||||
|  |         [item.quantity, item.product_id] | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // 验证商品状态和库存,计算总价和支付方式 |     // 验证商品状态和库存,计算总价和支付方式 | ||||||
|     let totalAmount = 0; |     let totalAmount = 0; | ||||||
|     let totalPoints = 0; |     let totalPoints = 0; | ||||||
| @@ -479,20 +501,23 @@ router.post('/create-from-cart', auth, async (req, res) => { | |||||||
|       totalAmount += finalPrice * item.quantity; |       totalAmount += finalPrice * item.quantity; | ||||||
|        |        | ||||||
|       // 根据支付方式计算积分和融豆需求 |       // 根据支付方式计算积分和融豆需求 | ||||||
|       const hasPoints = productPaymentMethods.includes('points') || productPaymentMethods.includes('points_rongdou'); |       // const hasPoints = productPaymentMethods.includes('points') || productPaymentMethods.includes('points_rongdou'); | ||||||
|       const hasRongdou = productPaymentMethods.includes('rongdou') || productPaymentMethods.includes('points_rongdou'); |       // const hasRongdou = productPaymentMethods.includes('rongdou') || productPaymentMethods.includes('points_rongdou'); | ||||||
|        |        | ||||||
|       if (hasPoints && !hasRongdou) { |       // if (hasPoints && !hasRongdou) { | ||||||
|         // 仅积分支付:按10000积分=1融豆计算 |       //   // 仅积分支付:按10000积分=1融豆计算 | ||||||
|         totalPoints += finalRongdouPrice * item.quantity * 10000; |       //   totalPoints += finalRongdouPrice * item.quantity * 10000; | ||||||
|         totalRongdou += finalRongdouPrice * item.quantity; |       //   totalRongdou += finalRongdouPrice * item.quantity; | ||||||
|       } else if (!hasPoints && hasRongdou) { |       // } else if (!hasPoints && hasRongdou) { | ||||||
|         // 仅融豆支付 |       //   // 仅融豆支付 | ||||||
|         totalRongdou += finalRongdouPrice * item.quantity; |       //   totalRongdou += finalRongdouPrice * item.quantity; | ||||||
|       } else { |       // } else { | ||||||
|         // 组合支付或默认:记录融豆价格,前端可选择支付方式 |       //   // 组合支付或默认:记录融豆价格,前端可选择支付方式 | ||||||
|         totalRongdou += finalRongdouPrice * item.quantity; |       //   totalRongdou += finalRongdouPrice * item.quantity; | ||||||
|       } |       // } | ||||||
|  |       totalPoints += item.points_price * item.quantity; | ||||||
|  |       totalRongdou += item.rongdou_price * item.quantity; | ||||||
|  |       // console.log(1111,item) | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     // 去重支付方式 |     // 去重支付方式 | ||||||
| @@ -612,9 +637,9 @@ router.put('/:id/cancel', auth, async (req, res) => { | |||||||
|  |  | ||||||
|     const order = orders[0]; |     const order = orders[0]; | ||||||
|  |  | ||||||
|     if (order.status !== 'pending') { |     if (order.status !== 'pending' && order.status !== 'pre_order') { | ||||||
|       await db.query('ROLLBACK'); |       await db.query('ROLLBACK'); | ||||||
|       return res.status(400).json({ success: false, message: '只能取消待处理的订单' }); |       return res.status(400).json({ success: false, message: '只能取消待处理或待支付的订单' }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // 退还用户积分 |     // 退还用户积分 | ||||||
| @@ -630,6 +655,24 @@ router.put('/:id/cancel', auth, async (req, res) => { | |||||||
|       [userId, order.total_points] |       [userId, order.total_points] | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  |     console.log(12345,order.id); | ||||||
|  |     const [orderDetails] = await db.execute( | ||||||
|  |       'SELECT * FROM order_items WHERE order_id = ?', | ||||||
|  |       [order.id] | ||||||
|  |     ); | ||||||
|  |     console.log(12345,orderDetails); | ||||||
|  |     // 返还库存 | ||||||
|  |     await db.execute( | ||||||
|  |       'UPDATE product_spec_combinations SET stock = stock + ? WHERE id = ?', | ||||||
|  |       [orderDetails[0].quantity, orderDetails[0].spec_combination_id] | ||||||
|  |     ); | ||||||
|  |     // 返还销售量 | ||||||
|  |     await db.execute( | ||||||
|  |       'UPDATE products SET stock = stock + ? WHERE id = ?', | ||||||
|  |       [orderDetails[0].quantity, orderDetails[0].product_id] | ||||||
|  |     ); | ||||||
|  |      | ||||||
|  |  | ||||||
|     // 更新订单状态 |     // 更新订单状态 | ||||||
|     await db.execute( |     await db.execute( | ||||||
|       'UPDATE orders SET status = "cancelled", updated_at = NOW() WHERE id = ?', |       'UPDATE orders SET status = "cancelled", updated_at = NOW() WHERE id = ?', | ||||||
| @@ -689,7 +732,7 @@ router.put('/:id/confirm', auth, async (req, res) => { | |||||||
|  |  | ||||||
|     // 检查订单是否存在且属于当前用户 |     // 检查订单是否存在且属于当前用户 | ||||||
|     const [orders] = await getDB().execute( |     const [orders] = await getDB().execute( | ||||||
|       'SELECT id, status FROM orders WHERE id = ? AND user_id = ?', |       'SELECT * FROM orders WHERE id = ? AND user_id = ?', | ||||||
|       [orderId, userId] |       [orderId, userId] | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
| @@ -703,6 +746,28 @@ router.put('/:id/confirm', auth, async (req, res) => { | |||||||
|       return res.status(400).json({ success: false, message: '只能确认已发货的订单' }); |       return res.status(400).json({ success: false, message: '只能确认已发货的订单' }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // 佣金分配 | ||||||
|  |     const [products] = await getDB().execute( | ||||||
|  |       'SELECT * FROM order_items WHERE order_id = ?', | ||||||
|  |       [orderId] | ||||||
|  |     ); | ||||||
|  |     for (const product of products) { | ||||||
|  |       const [producersResult] = await getDB().execute( | ||||||
|  |         'SELECT * FROM products WHERE id = ?', | ||||||
|  |         [product.product_id] | ||||||
|  |       ); | ||||||
|  |       await getDB().execute( | ||||||
|  |         'UPDATE products SET sales = sales + ? WHERE id = ?', | ||||||
|  |         [product.quantity, product.product_id] | ||||||
|  |       ); | ||||||
|  |       if (producersResult[0].shop_name) { | ||||||
|  |         await getDB().execute( | ||||||
|  |           'UPDATE users SET income = income + ? WHERE id = ?', | ||||||
|  |           [producersResult[0].price * product.quantity, parseInt(producersResult[0].shop_name)] | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // 更新订单状态 |     // 更新订单状态 | ||||||
|     await getDB().execute( |     await getDB().execute( | ||||||
|       'UPDATE orders SET status = "completed", updated_at = NOW() WHERE id = ?', |       'UPDATE orders SET status = "completed", updated_at = NOW() WHERE id = ?', | ||||||
| @@ -937,7 +1002,7 @@ router.get('/pending-payment/:id', auth, async (req, res) => { | |||||||
|       `SELECT  |       `SELECT  | ||||||
|         oi.id, oi.product_id, oi.quantity, oi.price, oi.points_price, oi.rongdou_price, |         oi.id, oi.product_id, oi.quantity, oi.price, oi.points_price, oi.rongdou_price, | ||||||
|         oi.spec_combination_id, |         oi.spec_combination_id, | ||||||
|         p.name as product_name, p.image_url, p.description, |         p.name as product_name, p.image_url, p.description, p.payment_methods, | ||||||
|         psc.spec_values as spec_info |         psc.spec_values as spec_info | ||||||
|       FROM order_items oi |       FROM order_items oi | ||||||
|       LEFT JOIN products p ON oi.product_id = p.id |       LEFT JOIN products p ON oi.product_id = p.id | ||||||
| @@ -961,7 +1026,10 @@ router.get('/pending-payment/:id', auth, async (req, res) => { | |||||||
|       success: true, |       success: true, | ||||||
|       data: { |       data: { | ||||||
|         ...order, |         ...order, | ||||||
|         items: orderItems |         items: orderItems.map(item => ({ | ||||||
|  |           ...item, | ||||||
|  |           payment_methods: JSON.parse(item.payment_methods) | ||||||
|  |         })) | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|   } catch (error) { |   } catch (error) { | ||||||
| @@ -1061,7 +1129,7 @@ router.post('/confirm-payment', auth, async (req, res) => { | |||||||
|   try { |   try { | ||||||
|     await connection.beginTransaction(); |     await connection.beginTransaction(); | ||||||
|  |  | ||||||
|     const { orderId: order_id, addressId: address_id } = req.body; |     const { orderId: order_id, addressId: address_id, couponRecordId, paymentMethod, beansAmount, pointsAmount } = req.body; | ||||||
|     const userId = req.user.id; |     const userId = req.user.id; | ||||||
|  |  | ||||||
|     // 验证必填字段 |     // 验证必填字段 | ||||||
| @@ -1089,29 +1157,29 @@ router.post('/confirm-payment', auth, async (req, res) => { | |||||||
|     const order = orders[0]; |     const order = orders[0]; | ||||||
|      |      | ||||||
|     // 解析支付方式 |     // 解析支付方式 | ||||||
|     let allPaymentMethods = []; |     // let allPaymentMethods = []; | ||||||
|     console.log(typeof order.payment_methods_list); |     // // console.log(typeof order.payment_methods_list); | ||||||
|      |      | ||||||
|     if (order.payment_methods_list) { |     // if (order.payment_methods_list) { | ||||||
|       try { |     //   try { | ||||||
|         // 数据库中存储的是序列化的JSON字符串,直接解析 |     //     // 数据库中存储的是序列化的JSON字符串,直接解析 | ||||||
|         |         | ||||||
|         allPaymentMethods = JSON.parse(JSON.parse(order.payment_methods_list)); |     //     allPaymentMethods = JSON.parse(JSON.parse(order.payment_methods_list)); | ||||||
|       } catch (e) { |     //   } catch (e) { | ||||||
|         console.error('解析支付方式失败:', e, 'raw data:', order.payment_methods_list); |     //     console.error('解析支付方式失败:', e, 'raw data:', order.payment_methods_list); | ||||||
|         allPaymentMethods = []; |     //     allPaymentMethods = []; | ||||||
|       } |     //   } | ||||||
|     } |     // } | ||||||
|      |      | ||||||
|     // 去重支付方式 |     // // 去重支付方式 | ||||||
|     allPaymentMethods = [...new Set(allPaymentMethods)]; |     // allPaymentMethods = [...new Set(allPaymentMethods)]; | ||||||
|      |      | ||||||
|     // 判断支付方式类型 |     // // 判断支付方式类型 | ||||||
|     const hasPoints = allPaymentMethods.includes('points') || allPaymentMethods.includes('points_rongdou'); |     // const hasPoints = allPaymentMethods.includes('points') || allPaymentMethods.includes('points_rongdou'); | ||||||
|     const hasRongdou = allPaymentMethods.includes('rongdou') || allPaymentMethods.includes('points_rongdou'); |     // const hasRongdou = allPaymentMethods.includes('rongdou') || allPaymentMethods.includes('points_rongdou'); | ||||||
|     const isComboPayment = allPaymentMethods.includes('points_rongdou'); |     // const isComboPayment = allPaymentMethods.includes('points_rongdou'); | ||||||
|      |      | ||||||
|     console.log('订单支付方式:', allPaymentMethods, { hasPoints, hasRongdou, isComboPayment }); |     // console.log('订单支付方式:', allPaymentMethods, { hasPoints, hasRongdou, isComboPayment }); | ||||||
|  |  | ||||||
|     // 获取收货地址信息 |     // 获取收货地址信息 | ||||||
|     const [addresses] = await connection.execute( |     const [addresses] = await connection.execute( | ||||||
| @@ -1143,82 +1211,209 @@ router.post('/confirm-payment', auth, async (req, res) => { | |||||||
|     } |     } | ||||||
|     user.balance = Math.abs(user.balance); |     user.balance = Math.abs(user.balance); | ||||||
|  |  | ||||||
|     // 根据支付方式处理扣费逻辑 |     console.log(123456,paymentMethod) | ||||||
|     let totalRongdouNeeded = order.total_rongdou; // 需要的融豆总数 |  | ||||||
|     let pointsToDeduct = 0; // 需要扣除的积分 |  | ||||||
|     let rongdouToDeduct = 0; // 需要扣除的融豆 |  | ||||||
|  |  | ||||||
|     if (!hasRongdou && !hasPoints) { |     // 开始扣钱 | ||||||
|       await connection.rollback(); |     switch (paymentMethod) { | ||||||
|       return res.status(400).json({ success: false, message: '商品支付方式配置错误' }); |       case 'points': | ||||||
|     } |         // 积分支付逻辑 | ||||||
|      |         if (user.points < order.total_points) { | ||||||
|     if (hasPoints && !hasRongdou) { |  | ||||||
|       // 只支持积分支付,按10000积分=1融豆转换 |  | ||||||
|       const pointsNeeded = totalRongdouNeeded * 10000; |  | ||||||
|       if (user.points < pointsNeeded) { |  | ||||||
|         await connection.rollback(); |  | ||||||
|         return res.status(400).json({ success: false, message: '积分不足' }); |  | ||||||
|       } |  | ||||||
|       pointsToDeduct = pointsNeeded; |  | ||||||
|       rongdouToDeduct = 0; |  | ||||||
|     } else if (!hasPoints && hasRongdou) { |  | ||||||
|       // 只支持融豆支付 |  | ||||||
|       if (user.balance < totalRongdouNeeded) { |  | ||||||
|         await connection.rollback(); |  | ||||||
|         return res.status(400).json({ success: false, message: '融豆不足' }); |  | ||||||
|       } |  | ||||||
|       pointsToDeduct = 0; |  | ||||||
|       rongdouToDeduct = totalRongdouNeeded; |  | ||||||
|     } else if (hasPoints && hasRongdou) { |  | ||||||
|       // 组合支付:先扣积分,不足部分用融豆 |  | ||||||
|       const availablePointsInRongdou = Math.floor(user.points / 10000); // 积分可转换的融豆数 |  | ||||||
|        |  | ||||||
|       if (availablePointsInRongdou >= totalRongdouNeeded) { |  | ||||||
|         // 积分足够支付全部 |  | ||||||
|         pointsToDeduct = totalRongdouNeeded * 10000; |  | ||||||
|         rongdouToDeduct = 0; |  | ||||||
|       } else { |  | ||||||
|         // 积分不够,需要组合支付 |  | ||||||
|         pointsToDeduct = availablePointsInRongdou * 10000; |  | ||||||
|         rongdouToDeduct = totalRongdouNeeded - availablePointsInRongdou; |  | ||||||
|          |  | ||||||
|         if (user.balance < rongdouToDeduct) { |  | ||||||
|           await connection.rollback(); |           await connection.rollback(); | ||||||
|           return res.status(400).json({ success: false, message: '积分和融豆余额不足' }); |           return res.status(400).json({ success: false, message: '积分不足' }); | ||||||
|         } |         } | ||||||
|       } |         console.log(1234567,paymentMethod,userId,order.total_points) | ||||||
|  |         await connection.execute( | ||||||
|  |           'UPDATE users SET points = points - ? WHERE id = ?', | ||||||
|  |           [order.total_points, userId] | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         // 记录积分变动历史 | ||||||
|  |         await connection.execute( | ||||||
|  |           `INSERT INTO points_history (user_id, type, amount, description, order_id)  | ||||||
|  |           VALUES (?, 'spend', ?, ?, ?)`, | ||||||
|  |           [userId, order.total_points, `订单支付 - ${order.order_no}`, order_id] | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         // 供应商分佣 | ||||||
|  |         // await connection.execute( | ||||||
|  |         //   'UPDATE users SET points = points + ? WHERE id = ?', | ||||||
|  |         //   [order.total_points * 0.1, order.shop_id] | ||||||
|  |         // ); | ||||||
|  |         // console.log(123,order) | ||||||
|  |  | ||||||
|  |         break; | ||||||
|  |       case 'beans': | ||||||
|  |         // 融豆支付逻辑 | ||||||
|  |         if (user.balance < order.total_rongdou) { | ||||||
|  |           await connection.rollback(); | ||||||
|  |           return res.status(400).json({ success: false, message: '融豆不足' }); | ||||||
|  |         } | ||||||
|  |         await connection.execute( | ||||||
|  |           'UPDATE users SET balance = balance + ? WHERE id = ?', | ||||||
|  |           [order.total_rongdou, userId] | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         // 记录融豆变动历史 | ||||||
|  |         await connection.execute( | ||||||
|  |           `INSERT INTO points_history (user_id, type, amount, description, order_id)  | ||||||
|  |           VALUES (?, 'spend', ?, ?, ?)`, | ||||||
|  |           [userId, order.total_rongdou, `订单支付 - ${order.order_no}`, order_id] | ||||||
|  |         ); | ||||||
|  |         break; | ||||||
|  |       case 'mixed': | ||||||
|  |         // 积分和融豆组合支付逻辑 | ||||||
|  |  | ||||||
|  |         if(user.points < order.total_points) { | ||||||
|  |           const needPoints = (user.points/10000).floor(0) * 10000; | ||||||
|  |           const needBeans = order.total_rongdou - needPoints/10000; | ||||||
|  |           if(user.balance < needBeans) { | ||||||
|  |             await connection.rollback(); | ||||||
|  |             return res.status(400).json({ success: false, message: '融豆不足' }); | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |           await connection.execute( | ||||||
|  |             'UPDATE users SET points = points - ? WHERE id = ?', | ||||||
|  |             [needPoints, userId] | ||||||
|  |           ); | ||||||
|  |  | ||||||
|  |           // 记录积分变动历史 | ||||||
|  |           await connection.execute( | ||||||
|  |             `INSERT INTO points_history (user_id, type, amount, description, order_id)  | ||||||
|  |             VALUES (?, 'spend', ?, ?, ?)`, | ||||||
|  |             [userId, needPoints, `订单支付 - ${order.order_no}`, order_id] | ||||||
|  |           ); | ||||||
|  |  | ||||||
|  |           await connection.execute( | ||||||
|  |             'UPDATE users SET balance = balance + ? WHERE id = ?', | ||||||
|  |             [needBeans, userId] | ||||||
|  |           ); | ||||||
|  |  | ||||||
|  |           // 记录融豆变动历史 | ||||||
|  |           await connection.execute( | ||||||
|  |             `INSERT INTO points_history (user_id, type, amount, description, order_id)  | ||||||
|  |             VALUES (?, 'spend', ?, ?, ?)`, | ||||||
|  |             [userId, needBeans, `订单支付 - ${order.order_no}`, order_id] | ||||||
|  |           ); | ||||||
|  |         } else { | ||||||
|  |           await connection.execute( | ||||||
|  |             'UPDATE users SET points = points - ? WHERE id = ?', | ||||||
|  |             [order.total_points, userId] | ||||||
|  |           ); | ||||||
|  |           // 记录积分变动历史 | ||||||
|  |           await connection.execute( | ||||||
|  |             `INSERT INTO points_history (user_id, type, amount, description, order_id)  | ||||||
|  |             VALUES (?, 'spend', ?, ?, ?)`, | ||||||
|  |             [userId, order.total_points, `订单支付 - ${order.order_no}`, order_id] | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         break; | ||||||
|  |       case 'alipay_wap': | ||||||
|  |         console.log(123465) | ||||||
|  |         break; | ||||||
|  |       default: | ||||||
|  |         await connection.rollback(); | ||||||
|  |         return res.status(400).json({ success: false, message: '支付方式配置错误' }); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     console.log('扣费计算:', { totalRongdouNeeded, pointsToDeduct, rongdouToDeduct, userPoints: user.points, userBalance: user.balance }); |     // // 根据支付方式处理扣费逻辑 | ||||||
|  |     // let totalRongdouNeeded = order.total_rongdou; // 需要的融豆总数 | ||||||
|  |     // let pointsToDeduct = 0; // 需要扣除的积分 | ||||||
|  |     // let rongdouToDeduct = 0; // 需要扣除的融豆 | ||||||
|  |      | ||||||
|  |     // if (!hasRongdou && !hasPoints) { | ||||||
|  |     //   await connection.rollback(); | ||||||
|  |     //   return res.status(400).json({ success: false, message: '商品支付方式配置错误' }); | ||||||
|  |     // } | ||||||
|  |      | ||||||
|  |     // if (hasPoints && !hasRongdou) { | ||||||
|  |     //   // 只支持积分支付,按10000积分=1融豆转换 | ||||||
|  |     //   const pointsNeeded = totalRongdouNeeded * 10000; | ||||||
|  |     //   if (user.points < pointsNeeded) { | ||||||
|  |     //     await connection.rollback(); | ||||||
|  |     //     return res.status(400).json({ success: false, message: '积分不足' }); | ||||||
|  |     //   } | ||||||
|  |     //   pointsToDeduct = pointsNeeded; | ||||||
|  |     //   rongdouToDeduct = 0; | ||||||
|  |     // } else if (!hasPoints && hasRongdou) { | ||||||
|  |     //   // 只支持融豆支付 | ||||||
|  |     //   if (user.balance < totalRongdouNeeded) { | ||||||
|  |     //     await connection.rollback(); | ||||||
|  |     //     return res.status(400).json({ success: false, message: '融豆不足' }); | ||||||
|  |     //   } | ||||||
|  |     //   pointsToDeduct = 0; | ||||||
|  |     //   rongdouToDeduct = totalRongdouNeeded; | ||||||
|  |     // } else if (hasPoints && hasRongdou) { | ||||||
|  |     //   // 组合支付:先扣积分,不足部分用融豆 | ||||||
|  |     //   const availablePointsInRongdou = Math.floor(user.points / 10000); // 积分可转换的融豆数 | ||||||
|  |        | ||||||
|  |     //   if (availablePointsInRongdou >= totalRongdouNeeded) { | ||||||
|  |     //     // 积分足够支付全部 | ||||||
|  |     //     pointsToDeduct = totalRongdouNeeded * 10000; | ||||||
|  |     //     rongdouToDeduct = 0; | ||||||
|  |     //   } else { | ||||||
|  |     //     // 积分不够,需要组合支付 | ||||||
|  |     //     pointsToDeduct = availablePointsInRongdou * 10000; | ||||||
|  |     //     rongdouToDeduct = totalRongdouNeeded - availablePointsInRongdou; | ||||||
|  |          | ||||||
|  |     //     if (user.balance < rongdouToDeduct) { | ||||||
|  |     //       await connection.rollback(); | ||||||
|  |     //       return res.status(400).json({ success: false, message: '积分和融豆余额不足' }); | ||||||
|  |     //     } | ||||||
|  |     //   } | ||||||
|  |     // } | ||||||
|  |      | ||||||
|  |     // console.log('扣费计算:', { totalRongdouNeeded, pointsToDeduct, rongdouToDeduct, userPoints: user.points, userBalance: user.balance }); | ||||||
|  |  | ||||||
|     // 扣除积分 |     // 扣除积分 | ||||||
|     if (pointsToDeduct > 0) { |     // if (pointsToDeduct > 0) { | ||||||
|       await connection.execute( |     //   await connection.execute( | ||||||
|         'UPDATE users SET points = points - ? WHERE id = ?', |     //     'UPDATE users SET points = points - ? WHERE id = ?', | ||||||
|         [pointsToDeduct, userId] |     //     [pointsToDeduct, userId] | ||||||
|       ); |     //   ); | ||||||
|  |  | ||||||
|       // 记录积分变动历史 |     //   // 记录积分变动历史 | ||||||
|  |     //   await connection.execute( | ||||||
|  |     //     `INSERT INTO points_history (user_id, type, amount, description, order_id)  | ||||||
|  |     //      VALUES (?, 'spend', ?, ?, ?)`, | ||||||
|  |     //     [userId, pointsToDeduct, `订单支付 - ${order.order_no}`, order_id] | ||||||
|  |     //   ); | ||||||
|  |     // } | ||||||
|  |  | ||||||
|  |     // // 扣除融豆 | ||||||
|  |     // if (rongdouToDeduct > 0) { | ||||||
|  |     //   await connection.execute( | ||||||
|  |     //     'UPDATE users SET balance = balance + ? WHERE id = ?', | ||||||
|  |     //     [rongdouToDeduct, userId] | ||||||
|  |     //   ); | ||||||
|  |  | ||||||
|  |     //   // 记录融豆变动历史 | ||||||
|  |     //   await connection.execute( | ||||||
|  |     //     `INSERT INTO rongdou_history (user_id, type, amount, description, order_id)  | ||||||
|  |     //      VALUES (?, 'spend', ?, ?, ?)`, | ||||||
|  |     //     [userId, rongdouToDeduct, `订单支付 - ${order.order_no}`, order_id] | ||||||
|  |     //   ); | ||||||
|  |     // } | ||||||
|  |  | ||||||
|  |     // 更新优惠券记录 | ||||||
|  |     if (couponRecordId) { | ||||||
|       await connection.execute( |       await connection.execute( | ||||||
|         `INSERT INTO points_history (user_id, type, amount, description, order_id)  |         'UPDATE coupon_use SET use_time = NOW(), order_id = ? WHERE id = ?', | ||||||
|          VALUES (?, 'spend', ?, ?, ?)`, |         [order_id, couponRecordId] | ||||||
|         [userId, pointsToDeduct, `订单支付 - ${order.order_no}`, order_id] |  | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // 扣除融豆 |     if (beansAmount > 0) { | ||||||
|     if (rongdouToDeduct > 0) { |  | ||||||
|       await connection.execute( |       await connection.execute( | ||||||
|         'UPDATE users SET balance = balance + ? WHERE id = ?', |         'UPDATE orders SET real_rongdou = ? WHERE id = ?', | ||||||
|         [rongdouToDeduct, userId] |         [beansAmount, order_id] | ||||||
|       ); |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|       // 记录融豆变动历史 |     if (pointsAmount > 0) { | ||||||
|       await connection.execute( |       await connection.execute( | ||||||
|         `INSERT INTO rongdou_history (user_id, type, amount, description, order_id)  |         'UPDATE orders SET real_points = ? WHERE id = ?', | ||||||
|          VALUES (?, 'spend', ?, ?, ?)`, |         [pointsAmount, order_id] | ||||||
|         [userId, rongdouToDeduct, `订单支付 - ${order.order_no}`, order_id] |  | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -1233,11 +1428,17 @@ router.post('/confirm-payment', auth, async (req, res) => { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     await connection.execute( |     await connection.execute( | ||||||
|       `UPDATE orders SET status = 'pending', address = ?, updated_at = NOW()  |       `UPDATE orders SET status = 'pending', address = ?, updated_at = NOW(), coupon_record_id = ?, payment_method = ? | ||||||
|        WHERE id = ?`, |        WHERE id = ?`, | ||||||
|       [addressStr, order_id] |       [addressStr, couponRecordId === undefined ? null : couponRecordId, paymentMethod, order_id] | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  |     // 减组合库存 | ||||||
|  |     // await connection.execute( | ||||||
|  |     //   'UPDATE product_spec_combinations SET stock = stock - 1 WHERE id = ?', | ||||||
|  |     //   [order.product_combination_id] | ||||||
|  |     // ); | ||||||
|  |  | ||||||
|     await connection.commit(); |     await connection.commit(); | ||||||
|  |  | ||||||
|     res.json({ |     res.json({ | ||||||
|   | |||||||
| @@ -187,6 +187,10 @@ router.get('/query-status/:outTradeNo', paymentAuth, async (req, res) => { | |||||||
|   } |   } | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | router.get('/pay-product/test', async (req, res) => { | ||||||
|  |   console.log(123) | ||||||
|  | }) | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 获取用户支付记录 |  * 获取用户支付记录 | ||||||
|  * GET /api/payment/orders |  * GET /api/payment/orders | ||||||
|   | |||||||
| @@ -4,15 +4,113 @@ const { auth, adminAuth } = require('../middleware/auth'); | |||||||
|  |  | ||||||
| const router = express.Router(); | const router = express.Router(); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @swagger | ||||||
|  |  * /api/products: | ||||||
|  |  *   get: | ||||||
|  |  *     summary: 获取商品列表 | ||||||
|  |  *     description: 返回商品列表,支持分页、搜索、分类、状态过滤 | ||||||
|  |  *     tags: [products] | ||||||
|  |  *     parameters: | ||||||
|  |  *       - in: query | ||||||
|  |  *         name: page | ||||||
|  |  *         schema: | ||||||
|  |  *           type: integer | ||||||
|  |  *         description: 页码,默认1 | ||||||
|  |  *       - in: query | ||||||
|  |  *         name: limit | ||||||
|  |  *         schema: | ||||||
|  |  *           type: integer | ||||||
|  |  *         description: 每页数量,默认10,最大100 | ||||||
|  |  *       - in: query | ||||||
|  |  *         name: search | ||||||
|  |  *         schema: | ||||||
|  |  *           type: string | ||||||
|  |  *         description: 搜索商品名称 | ||||||
|  |  *       - in: query | ||||||
|  |  *         name: category | ||||||
|  |  *         schema: | ||||||
|  |  *           type: string | ||||||
|  |  *         description: 分类名称 | ||||||
|  |  *       - in: query | ||||||
|  |  *         name: status | ||||||
|  |  *         schema: | ||||||
|  |  *           type: string | ||||||
|  |  *         description: 商品状态(active/inactive) | ||||||
|  |  *     responses: | ||||||
|  |  *       200: | ||||||
|  |  *         description: 成功返回商品列表 | ||||||
|  |  *         content: | ||||||
|  |  *           application/json: | ||||||
|  |  *             schema: | ||||||
|  |  *               type: object | ||||||
|  |  *               properties: | ||||||
|  |  *                 success: | ||||||
|  |  *                   type: boolean | ||||||
|  |  *                   example: true | ||||||
|  |  *                 data: | ||||||
|  |  *                   type: array | ||||||
|  |  *                   items: | ||||||
|  |  *                     type: object | ||||||
|  |  *                     properties: | ||||||
|  |  *                       id: | ||||||
|  |  *                         type: string | ||||||
|  |  *                         example: "1" | ||||||
|  |  *                       name: | ||||||
|  |  *                         type: string | ||||||
|  |  *                         example: "商品A" | ||||||
|  |  *                       rongdou_price: | ||||||
|  |  *                         type: number | ||||||
|  |  *                         example: 100 | ||||||
|  |  *                       points_price: | ||||||
|  |  *                         type: number | ||||||
|  |  *                         example: 1000 | ||||||
|  |  *                       stock: | ||||||
|  |  *                         type: integer | ||||||
|  |  *                         example: 100 | ||||||
|  |  *                       image: | ||||||
|  |  *                         type: string | ||||||
|  |  *                         example: "https://example.com/image.jpg" | ||||||
|  |  *                       description: | ||||||
|  |  *                         type: string | ||||||
|  |  *                         example: "这是一个商品" | ||||||
|  |  *                       status: | ||||||
|  |  *                         type: string | ||||||
|  |  *                         example: "active" | ||||||
|  |  *                       category_id: | ||||||
|  |  *                         type: string | ||||||
|  |  *                         example: "1" | ||||||
|  |  *                       created_at: | ||||||
|  |  *                         type: string | ||||||
|  |  *                         format: date-time | ||||||
|  |  *                         example: "2023-01-01T00:00:00Z" | ||||||
|  |  *                       updated_at: | ||||||
|  |  *                         type: string | ||||||
|  |  *                         format: date-time | ||||||
|  |  *                         example: "2023-01-01T00:00:00Z" | ||||||
|  |  *                       sales: | ||||||
|  |  *                         type: integer | ||||||
|  |  *                         example: 100 | ||||||
|  |  *                       images: | ||||||
|  |  *                         type: array | ||||||
|  |  *                         items: | ||||||
|  |  *                           type: string | ||||||
|  |  *                           example: "https://example.com/image.jpg" | ||||||
|  |  *     400: | ||||||
|  |  *       description: 无效的分页参数 | ||||||
|  |  *     500: | ||||||
|  |  *       description: 服务器内部错误 | ||||||
|  |  */ | ||||||
| // 商品管理路由 | // 商品管理路由 | ||||||
| router.get('/', async (req, res) => { | router.get('/', async (req, res) => { | ||||||
|   try { |   try { | ||||||
|     const { page = 1, limit = 10, search = '', category = '', status = '' } = req.query; |     const { page = 1, limit = 10, search = '', category = '', status = '', sort } = req.query; | ||||||
|      |      | ||||||
|     // 确保参数为有效数字 |     // 确保参数为有效数字 | ||||||
|     const pageNum = Math.max(1, parseInt(page) || 1); |     const pageNum = Math.max(1, parseInt(page) || 1); | ||||||
|     const limitNum = Math.max(1, Math.min(100, parseInt(limit) || 10)); // 限制最大100条 |     const limitNum = Math.max(1, Math.min(100, parseInt(limit) || 10)); // 限制最大100条 | ||||||
|     const offset = Math.max(0, (pageNum - 1) * limitNum); |     const offset = Math.max(0, (pageNum - 1) * limitNum); | ||||||
|  |     let filteredProducts = [] | ||||||
|      |      | ||||||
|     console.log('分页参数:', { pageNum, limitNum, offset, search, category, status }); |     console.log('分页参数:', { pageNum, limitNum, offset, search, category, status }); | ||||||
|      |      | ||||||
| @@ -24,11 +122,6 @@ router.get('/', async (req, res) => { | |||||||
|       params.push(`%${search}%`); |       params.push(`%${search}%`); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     if (category) { |  | ||||||
|       whereClause += ' AND category = ?'; |  | ||||||
|       params.push(category); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     if (status) { |     if (status) { | ||||||
|       whereClause += ' AND status = ?'; |       whereClause += ' AND status = ?'; | ||||||
|       params.push(status); |       params.push(status); | ||||||
| @@ -36,6 +129,21 @@ router.get('/', async (req, res) => { | |||||||
|       whereClause += ' AND status = "active"'; |       whereClause += ' AND status = "active"'; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     switch (sort) { | ||||||
|  |       case 'price_desc': | ||||||
|  |         whereClause += ' ORDER BY sale_price DESC' | ||||||
|  |         break; | ||||||
|  |       case 'price_asc': | ||||||
|  |         whereClause += ' ORDER BY sale_price ASC' | ||||||
|  |         break; | ||||||
|  |       case 'sales_desc': | ||||||
|  |         whereClause += ' ORDER BY sales DESC' | ||||||
|  |         break; | ||||||
|  |       default: | ||||||
|  |         whereClause += ' ORDER BY created_at DESC' | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |      | ||||||
|     // 获取总数 |     // 获取总数 | ||||||
|     const countQuery = `SELECT COUNT(*) as total FROM products ${whereClause}`; |     const countQuery = `SELECT COUNT(*) as total FROM products ${whereClause}`; | ||||||
|     const [countResult] = await getDB().execute(countQuery, params); |     const [countResult] = await getDB().execute(countQuery, params); | ||||||
| @@ -43,24 +151,53 @@ router.get('/', async (req, res) => { | |||||||
|      |      | ||||||
|     // 获取商品列表 |     // 获取商品列表 | ||||||
|     const query = ` |     const query = ` | ||||||
|       SELECT id, name, rongdou_price, category, points_price, stock, image_url as image, description, status, payment_methods, created_at, updated_at |       SELECT id, name, rongdou_price, points_price, stock, image_url as image, description, status, payment_methods, created_at, updated_at, sales, images | ||||||
|       FROM products  |       FROM products  | ||||||
|       ${whereClause} |       ${whereClause} | ||||||
|       ORDER BY created_at DESC |  | ||||||
|       LIMIT ${limitNum} OFFSET ${offset} |       LIMIT ${limitNum} OFFSET ${offset} | ||||||
|     `; |     `; | ||||||
|      |      | ||||||
|     // 确保参数数组正确传递 |     // 确保参数数组正确传递 | ||||||
|     const queryParams = [...params]; |     const queryParams = [...params]; | ||||||
|     console.log('Query params:', queryParams, 'Query:', query); |     // console.log('Query params:', queryParams, 'Query:', query); | ||||||
|     const [products] = await getDB().execute(query, queryParams); |     const [products] = await getDB().execute(query, queryParams); | ||||||
|  |  | ||||||
|     products.forEach(item=>{ |     products.forEach(item=>{ | ||||||
|       item.payment_methods = JSON.parse(item.payment_methods) |       item.payment_methods = JSON.parse(item.payment_methods) | ||||||
|  |       item.images = JSON.parse(item.images) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     if (category) { | ||||||
|  |       // 先根据分类名称获取分类ID | ||||||
|  |       const query = `SELECT * FROM category WHERE category_name = ?` | ||||||
|  |       const [getCategory] = await getDB().execute(query, [category]) | ||||||
|  |       const [getSecondCategory] = await getDB().execute('SELECT * FROM category WHERE parent_id = ?', [getCategory[0].id]) | ||||||
|  |  | ||||||
|  |       const sumCategory = getCategory.concat(getSecondCategory).map(item=>item.id) | ||||||
|  |  | ||||||
|  |       // 再根据分类ID获取商品ID | ||||||
|  |       const getProductCategory = [] | ||||||
|  |       for (const item of sumCategory) { | ||||||
|  |         const [getProductCategoryItem] = await getDB().execute('SELECT * FROM products_category WHERE category_id = ?', [item]) | ||||||
|  |         getProductCategory.push(...getProductCategoryItem) | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       const productIds = [] | ||||||
|  |       for (const item of getProductCategory) { | ||||||
|  |         productIds.push(item.product_id) | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       filteredProducts = products.filter(item=>productIds.includes(item.id)) | ||||||
|  |     } else { | ||||||
|  |       filteredProducts = products | ||||||
|  |     } | ||||||
|  |  | ||||||
|     res.json({ |     res.json({ | ||||||
|       success: true, |       success: true, | ||||||
|       data: { |       data: { | ||||||
|         products, |         products: filteredProducts, | ||||||
|  |         // products: products, | ||||||
|         pagination: { |         pagination: { | ||||||
|           page: pageNum, |           page: pageNum, | ||||||
|           limit: limitNum, |           limit: limitNum, | ||||||
| @@ -75,36 +212,36 @@ router.get('/', async (req, res) => { | |||||||
|   } |   } | ||||||
| }); | }); | ||||||
|  |  | ||||||
| // 获取商品分类列表 | // // 获取商品分类列表 | ||||||
| router.get('/categories', async (req, res) => { | // router.get('/categories', async (req, res) => { | ||||||
|   try { | //   try { | ||||||
|     const [categories] = await getDB().execute( | //     const [categories] = await getDB().execute( | ||||||
|       'SELECT DISTINCT category FROM products WHERE status = "active" AND category IS NOT NULL' | //       'SELECT DISTINCT category FROM products WHERE status = "active" AND category IS NOT NULL' | ||||||
|     ); | //     ); | ||||||
|      |      | ||||||
|     res.json({ | //     res.json({ | ||||||
|       success: true, | //       success: true, | ||||||
|       data: { | //       data: { | ||||||
|         categories: categories.map(item => item.category) | //         categories: categories.map(item => item.category) | ||||||
|       } | //       } | ||||||
|     }); | //     }); | ||||||
|   } catch (error) { | //   } catch (error) { | ||||||
|     console.error('获取商品分类失败:', error); | //     console.error('获取商品分类失败:', error); | ||||||
|     res.status(500).json({ success: false, message: '获取商品分类失败' }); | //     res.status(500).json({ success: false, message: '获取商品分类失败' }); | ||||||
|   } | //   } | ||||||
| }); | // }); | ||||||
|  |  | ||||||
| // 获取热销商品 | // 获取热销商品 | ||||||
| router.get('/hot', async (req, res) => { | router.get('/hot', async (req, res) => { | ||||||
|   try { |   try { | ||||||
|     // 从活跃商品中随机获取2个商品 |     // 从活跃商品中随机获取2个商品 | ||||||
|     const [products] = await getDB().execute( |     const [products] = await getDB().execute( | ||||||
|       `SELECT id, name, category, price, points_price, rongdou_price, stock,  |       `SELECT id, name, price, points_price, rongdou_price, stock,  | ||||||
|              image_url, images, description, shop_name, shop_avatar,  |              image_url, images, description, shop_name, shop_avatar,  | ||||||
|              payment_methods, sales, rating, status, created_at, updated_at |              payment_methods, sales, rating, status, created_at, updated_at | ||||||
|        FROM products  |        FROM products  | ||||||
|        WHERE status = 'active' AND stock > 0 |        WHERE status = 'active' AND stock > 0 | ||||||
|        ORDER BY RAND()  |        ORDER BY sales DESC | ||||||
|        LIMIT 2` |        LIMIT 2` | ||||||
|     ); |     ); | ||||||
|      |      | ||||||
| @@ -156,36 +293,42 @@ router.get('/hot', async (req, res) => { | |||||||
|  */ |  */ | ||||||
| router.get('/cheap', async (req, res) => { | router.get('/cheap', async (req, res) => { | ||||||
|   try { |   try { | ||||||
|     // 从活跃商品中随机获取2个商品作为秒杀商品 |  | ||||||
|     const [products] = await getDB().execute( |     const [products] = await getDB().execute( | ||||||
|       `SELECT id, name, category, price, points_price, rongdou_price, stock,  |       `SELECT id, start_time, end_time, flash_stock, flash_price, products_id | ||||||
|              image_url, images, description, shop_name, shop_avatar,  |        FROM flash_product | ||||||
|              payment_methods, sales, rating, status, created_at, updated_at |        WHERE end_time > NOW() AND flash_stock > 0 | ||||||
|        FROM products  |  | ||||||
|        WHERE status = 'active' AND stock > 0 |  | ||||||
|        ORDER BY RAND()  |        ORDER BY RAND()  | ||||||
|        LIMIT 2` |        LIMIT 2` | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     // 格式化商品数据,为秒杀商品添加特殊标识 |     const tempProducts = await Promise.all(products.map(async item=>{ | ||||||
|     const formattedProducts = products.map(product => ({ |  | ||||||
|       ...product, |       const [product] = await getDB().execute( | ||||||
|       images: product.images ? JSON.parse(product.images) : (product.image_url ? [product.image_url] : []), |         `SELECT id, name, price, points_price, rongdou_price, stock,  | ||||||
|       payment_methods: product.payment_methods ? JSON.parse(product.payment_methods) : ['points'], |                image_url, images, description, shop_name, shop_avatar,  | ||||||
|       // 秒杀商品特殊处理:价格打8折 |                payment_methods, sales, rating, status, created_at, updated_at | ||||||
|       flash_sale_price: Math.floor(product.price * 0.8), |          FROM products  | ||||||
|       flash_sale_points: Math.floor(product.points_price * 0.8), |          WHERE id = ?`, | ||||||
|       flash_sale_rongdou: Math.floor(product.rongdou_price * 0.8), |         [item.products_id] | ||||||
|       is_flash_sale: true, |       ) | ||||||
|       // 保持向后兼容 |  | ||||||
|       points: product.points_price, |       item = { | ||||||
|       image: product.image_url |         ...product[0], | ||||||
|     })); |         images: product.images ? JSON.parse(product.images) : (product.image_url ? [product.image_url] : []), | ||||||
|  |         payment_methods: product.payment_methods ? JSON.parse(product.payment_methods) : ['points'],  | ||||||
|  |         ...item, | ||||||
|  |         points: product.points_price, | ||||||
|  |         image: product.image_url, | ||||||
|  |         id: product[0].id, | ||||||
|  |       } | ||||||
|  |       return item | ||||||
|  |     })) | ||||||
|      |      | ||||||
|     res.json({ |     res.json({ | ||||||
|       success: true, |       success: true, | ||||||
|       data: { |       data: { | ||||||
|         products: formattedProducts |         products: tempProducts | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|   } catch (error) { |   } catch (error) { | ||||||
| @@ -336,7 +479,7 @@ router.get('/:id', async (req, res) => { | |||||||
|     const userId = req.user?.id; // 可选的用户ID,用于检查收藏状态 |     const userId = req.user?.id; // 可选的用户ID,用于检查收藏状态 | ||||||
|      |      | ||||||
|     const query = ` |     const query = ` | ||||||
|       SELECT id, name, category, price, points_price, rongdou_price, stock,  |       SELECT id, name, price, points_price, rongdou_price, stock,  | ||||||
|              image_url, images, videos, description, details, shop_name, shop_avatar, |              image_url, images, videos, description, details, shop_name, shop_avatar, | ||||||
|              payment_methods, sales, rating, status, created_at, updated_at |              payment_methods, sales, rating, status, created_at, updated_at | ||||||
|       FROM products  |       FROM products  | ||||||
| @@ -365,6 +508,8 @@ router.get('/:id', async (req, res) => { | |||||||
|       [id] |       [id] | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  |     // console.log(123,specCombinations); | ||||||
|  |      | ||||||
|     // 为每个规格组合获取详细的规格值信息 |     // 为每个规格组合获取详细的规格值信息 | ||||||
|     const enhancedSpecifications = []; |     const enhancedSpecifications = []; | ||||||
|     for (const combination of specCombinations) { |     for (const combination of specCombinations) { | ||||||
| @@ -437,6 +582,8 @@ router.get('/:id', async (req, res) => { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // console.log(123,enhancedSpecifications); | ||||||
|  |      | ||||||
|     // 获取商品属性 |     // 获取商品属性 | ||||||
|     const [attributes] = await getDB().execute( |     const [attributes] = await getDB().execute( | ||||||
|       'SELECT * FROM product_attributes WHERE product_id = ? ORDER BY sort_order, id', |       'SELECT * FROM product_attributes WHERE product_id = ? ORDER BY sort_order, id', | ||||||
| @@ -525,7 +672,7 @@ router.get('/:id', async (req, res) => { | |||||||
|       // 保持向后兼容 |       // 保持向后兼容 | ||||||
|       points: product.points_price, |       points: product.points_price, | ||||||
|       image: product.image_url, |       image: product.image_url, | ||||||
|       tags: product.category ? [product.category] : [] |       // tags: product.category ? [product.category] : [] | ||||||
|     }; |     }; | ||||||
|      |      | ||||||
|     res.json({ |     res.json({ | ||||||
| @@ -888,39 +1035,123 @@ router.get('/:id/reviews', async (req, res) => { | |||||||
| // 获取推荐商品 | // 获取推荐商品 | ||||||
| router.get('/:id/recommended', async (req, res) => { | router.get('/:id/recommended', async (req, res) => { | ||||||
|   try { |   try { | ||||||
|  |     // 获取商品id | ||||||
|     const id = parseInt(req.params.id); |     const id = parseInt(req.params.id); | ||||||
|  |  | ||||||
|     // 获取同类别的其他商品作为推荐 |     // 通过商品id获取该商品的分类id | ||||||
|     const query = ` |     const categoryQuery = ` | ||||||
|       SELECT p2.id, p2.name, p2.category, p2.price, p2.points_price as points,  |       SELECT category_id FROM products_category WHERE product_id = ? | ||||||
|              p2.stock, p2.image_url as image, p2.description |  | ||||||
|       FROM products p1 |  | ||||||
|       JOIN products p2 ON p1.category = p2.category |  | ||||||
|       WHERE p1.id = ? AND p2.id != ? AND p2.status = 'active' |  | ||||||
|       ORDER BY RAND() |  | ||||||
|       LIMIT 6 |  | ||||||
|     `; |     `; | ||||||
|  |     const [currentCategory] = await getDB().execute(categoryQuery, [id]); | ||||||
|      |      | ||||||
|     const [recommendedProducts] = await getDB().execute(query, [id, id]); |     let parentId = null; | ||||||
|  |     let categoryIds = currentCategory.map(item => item.category_id); | ||||||
|  |      | ||||||
|  |     if(currentCategory.length > 0 && currentCategory[0] !== null) { | ||||||
|  |       // 获取该商品的一级分类id | ||||||
|  |       const item = currentCategory[0]; | ||||||
|  |       const levelQuery = ` | ||||||
|  |         SELECT * FROM category WHERE id = ? AND level = 1 | ||||||
|  |       `; | ||||||
|  |       const [fatherCategory] = await getDB().execute(levelQuery, [item.category_id]); | ||||||
|  |        | ||||||
|  |       if(fatherCategory.length > 0) { | ||||||
|  |         // 该商品为一级分类商品,不存在二级分类 | ||||||
|  |         parentId = fatherCategory[0].id; | ||||||
|  |         categoryIds.push(parentId); | ||||||
|  |       } else { | ||||||
|  |         // 获取该商品的二级分类id | ||||||
|  |         const secondlevelQuery = ` | ||||||
|  |           SELECT * FROM category WHERE id = ? AND level = 2 | ||||||
|  |         `; | ||||||
|  |         const [secondlevelCategory] = await getDB().execute(secondlevelQuery, [item.category_id]); | ||||||
|  |         if(secondlevelCategory.length > 0) { | ||||||
|  |           // 通过二级分类获取一级分类id | ||||||
|  |           parentId = secondlevelCategory[0].parent_id; | ||||||
|  |           categoryIds.push(parentId); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // categoryIds目前存储该商品的一级分类id及其目录下所有二级分类的id | ||||||
|  |     const allSecondCategoryIdsQuery = ` | ||||||
|  |       SELECT id FROM category WHERE parent_id = ? AND level = 2 | ||||||
|  |     `; | ||||||
|  |     const [secondCategoryIds] = await getDB().execute(allSecondCategoryIdsQuery, [parentId]); | ||||||
|  |     categoryIds.push(...secondCategoryIds.map(item => item.id)); | ||||||
|  |  | ||||||
|  |     let recommendedProducts = []; | ||||||
|  |     let filteredRecommendProductIds = []; | ||||||
|  |  | ||||||
|  |     // 如果有分类ID,先获取同类商品 | ||||||
|  |     if (categoryIds.length > 0) { | ||||||
|  |       const recommendId = ` | ||||||
|  |         SELECT * FROM products_category WHERE category_id IN (${categoryIds.map(() => '?').join(',')}) | ||||||
|  |       `; | ||||||
|  |       const [recommendProductIds] = await getDB().execute(recommendId, categoryIds); | ||||||
|  |  | ||||||
|  |       filteredRecommendProductIds = [...new Set(recommendProductIds.map(item => item.product_id))]; | ||||||
|  |  | ||||||
|  |       // 获取同类别的其他商品作为推荐 | ||||||
|  |       if (filteredRecommendProductIds.length > 0) { | ||||||
|  |         const query = ` | ||||||
|  |           SELECT id, name, price, points_price as points,  | ||||||
|  |                  stock, image_url as image, description | ||||||
|  |           FROM products  | ||||||
|  |           WHERE id IN (${filteredRecommendProductIds.map(() => '?').join(',')}) AND id != ? | ||||||
|  |         `; | ||||||
|  |          | ||||||
|  |         const [categoryProducts] = await getDB().execute(query, [...filteredRecommendProductIds, id]); | ||||||
|  |         recommendedProducts = categoryProducts; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // 如果同类别商品不足,补充其他热门商品 |     // 如果同类别商品不足,补充其他热门商品 | ||||||
|     if (recommendedProducts.length < 6) { |     if (recommendedProducts.length < 6) { | ||||||
|       const remainingCount = 6 - recommendedProducts.length; |  | ||||||
|       if (remainingCount > 0) { |       let recommendQuery = ` | ||||||
|         const additionalQuery = ` |         SELECT products_id FROM recommend_product | ||||||
|           SELECT id, name, category, price, points_price as points,  |         WHERE 1 = 1 | ||||||
|  |       `; | ||||||
|  |  | ||||||
|  |       if (filteredRecommendProductIds.length > 0) { | ||||||
|  |         recommendQuery += ` AND products_id NOT IN (${filteredRecommendProductIds.map(() => '?').join(',')})`; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       recommendQuery += ` ORDER BY RAND() LIMIT ${6 - recommendedProducts.length}`; | ||||||
|  |  | ||||||
|  |       // 根据是否有排除ID来传递参数 | ||||||
|  |       const queryParams = filteredRecommendProductIds.length > 0  | ||||||
|  |         ? [...filteredRecommendProductIds]  | ||||||
|  |         : []; | ||||||
|  |  | ||||||
|  |       const [recommendProductIds] = await getDB().execute(recommendQuery, queryParams); | ||||||
|  |       filteredRecommendProductIds.push(...recommendProductIds.map(item => item.products_id)); | ||||||
|  |  | ||||||
|  |       for (const item of recommendProductIds) { | ||||||
|  |         const recommendQuery = ` | ||||||
|  |           SELECT id, name, price, points_price as points,  | ||||||
|                  stock, image_url as image, description |                  stock, image_url as image, description | ||||||
|           FROM products  |           FROM products  | ||||||
|           WHERE id != ? AND status = 'active' |           WHERE id = ? | ||||||
|           ORDER BY RAND() |         `; | ||||||
|           LIMIT ${remainingCount} |         const [recommendProduct] = await getDB().execute(recommendQuery, [item.products_id]); | ||||||
|  |         recommendedProducts.push(recommendProduct[0]); | ||||||
|  |       } | ||||||
|  |       if (recommendProductIds.length < 6) { | ||||||
|  |         // 补充其他热门商品 | ||||||
|  |         const additionalQuery = ` | ||||||
|  |           SELECT id, name, price, points_price as points,  | ||||||
|  |                  stock, image_url as image, description | ||||||
|  |           FROM products | ||||||
|  |           WHERE id NOT IN (${filteredRecommendProductIds.map(() => '?').join(',')}) | ||||||
|  |           ORDER BY RAND() | ||||||
|  |           LIMIT ${6 - recommendedProducts.length} | ||||||
|         `; |         `; | ||||||
|          |  | ||||||
|         const [additionalProducts] = await getDB().execute( |         const [additionalProducts] = await getDB().execute( | ||||||
|           additionalQuery, |           additionalQuery, | ||||||
|           [id] |           filteredRecommendProductIds | ||||||
|         ); |         ); | ||||||
|          |  | ||||||
|         recommendedProducts.push(...additionalProducts); |         recommendedProducts.push(...additionalProducts); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -163,7 +163,7 @@ router.get('/provinces', async (req, res) => { | |||||||
|             }); |             }); | ||||||
|             global.provinces = regionsByLevel[1]; |             global.provinces = regionsByLevel[1]; | ||||||
|         }else { |         }else { | ||||||
|             console.log('1111') |             // console.log('1111') | ||||||
|             regionsByLevel[1] = global.provinces; |             regionsByLevel[1] = global.provinces; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										96
									
								
								server.js
									
									
									
									
									
								
							
							
						
						
									
										96
									
								
								server.js
									
									
									
									
									
								
							| @@ -88,6 +88,95 @@ const limiter = rateLimit({ | |||||||
| }); | }); | ||||||
| app.use('/api', limiter); | app.use('/api', limiter); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | // ============ 添加 Swagger 文档路由 ============ | ||||||
|  | const swaggerUi = require('swagger-ui-express'); | ||||||
|  | const specs = require('./swagger'); | ||||||
|  |  | ||||||
|  | // 创建自定义的 HTML 页面来解决兼容性问题 | ||||||
|  | const swaggerHtml = ` | ||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="zh-CN"> | ||||||
|  | <head> | ||||||
|  |     <meta charset="UTF-8"> | ||||||
|  |     <title>融豆商城 API文档</title> | ||||||
|  |     <link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@4.15.5/swagger-ui.css" /> | ||||||
|  |     <style> | ||||||
|  |         html { | ||||||
|  |             box-sizing: border-box; | ||||||
|  |             overflow: -moz-scrollbars-vertical; | ||||||
|  |             overflow-y: scroll; | ||||||
|  |         } | ||||||
|  |         *, | ||||||
|  |         *:before, | ||||||
|  |         *:after { | ||||||
|  |             box-sizing: inherit; | ||||||
|  |         } | ||||||
|  |         body { | ||||||
|  |             margin: 0; | ||||||
|  |             background: #fafafa; | ||||||
|  |         } | ||||||
|  |         .swagger-ui .topbar { | ||||||
|  |             display: none; | ||||||
|  |         } | ||||||
|  |         .swagger-ui .info .title { | ||||||
|  |             color: #3b4151; | ||||||
|  |             font-family: sans-serif; | ||||||
|  |             font-size: 36px; | ||||||
|  |             margin: 0; | ||||||
|  |         } | ||||||
|  |     </style> | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  |     <div id="swagger-ui"></div> | ||||||
|  |     <script src="https://unpkg.com/swagger-ui-dist@4.15.5/swagger-ui-bundle.js"></script> | ||||||
|  |     <script src="https://unpkg.com/swagger-ui-dist@4.15.5/swagger-ui-standalone-preset.js"></script> | ||||||
|  |     <script> | ||||||
|  |         // 兼容性处理 | ||||||
|  |         if (!Object.hasOwn) { | ||||||
|  |             Object.hasOwn = function(obj, prop) { | ||||||
|  |                 return Object.prototype.hasOwnProperty.call(obj, prop); | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         window.onload = function() { | ||||||
|  |             const ui = SwaggerUIBundle({ | ||||||
|  |                 url: '/api-docs.json', | ||||||
|  |                 dom_id: '#swagger-ui', | ||||||
|  |                 deepLinking: true, | ||||||
|  |                 presets: [ | ||||||
|  |                     SwaggerUIBundle.presets.apis, | ||||||
|  |                     SwaggerUIStandalonePreset | ||||||
|  |                 ], | ||||||
|  |                 plugins: [ | ||||||
|  |                     SwaggerUIBundle.plugins.DownloadUrl | ||||||
|  |                 ], | ||||||
|  |                 layout: "StandaloneLayout", | ||||||
|  |                 persistAuthorization: true | ||||||
|  |             }); | ||||||
|  |              | ||||||
|  |             window.ui = ui; | ||||||
|  |         } | ||||||
|  |     </script> | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
|  | `; | ||||||
|  |  | ||||||
|  | // 自定义 Swagger 路由 | ||||||
|  | app.get('/api-docs', (req, res) => { | ||||||
|  |     res.send(swaggerHtml); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | // 提供 JSON 格式的文档 | ||||||
|  | app.get('/api-docs.json', (req, res) => { | ||||||
|  |     res.setHeader('Content-Type', 'application/json'); | ||||||
|  |     res.send(specs); | ||||||
|  | }); | ||||||
|  | // ============ Swagger 配置结束 ============ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| // 静态文件服务 - 必须在API路由之前 | // 静态文件服务 - 必须在API路由之前 | ||||||
| // 为uploads路径配置CORS和静态文件服务 | // 为uploads路径配置CORS和静态文件服务 | ||||||
| app.use('/uploads', express.static(path.join(__dirname, 'uploads'), { | app.use('/uploads', express.static(path.join(__dirname, 'uploads'), { | ||||||
| @@ -249,6 +338,13 @@ app.use('/api/announcements', require('./routes/announcements')); // 通知公 | |||||||
| app.use('/api/wechat-pay', require('./routes/wechatPay')); // 只保留微信支付 | app.use('/api/wechat-pay', require('./routes/wechatPay')); // 只保留微信支付 | ||||||
| app.use('/api/payment', require('./routes/payment')); | app.use('/api/payment', require('./routes/payment')); | ||||||
|  |  | ||||||
|  | // 优惠券路由 | ||||||
|  | app.use('/api/coupon', require('./routes/coupon')); | ||||||
|  |  | ||||||
|  | // 分类路由 | ||||||
|  | app.use('/api/category', require('./routes/category')); | ||||||
|  |  | ||||||
|  |  | ||||||
| // 前端路由 - 必须在最后,作为fallback | // 前端路由 - 必须在最后,作为fallback | ||||||
| app.get('/', (req, res) => { | app.get('/', (req, res) => { | ||||||
|   res.removeHeader('Origin-Agent-Cluster'); |   res.removeHeader('Origin-Agent-Cluster'); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user