7.16 小商场UI(拼团的UI复用并修改)与接口对接

This commit is contained in:
zhangsan 2025-07-16 17:12:35 +08:00
parent d8643194ee
commit b235462189
20 changed files with 815 additions and 241 deletions

View File

@ -11,7 +11,7 @@
Target Server Version : 80042
File Encoding : 65001
Date: 16/07/2025 13:59:35
Date: 16/07/2025 17:08:44
*/
SET NAMES utf8mb4;
@ -40,13 +40,16 @@ CREATE TABLE `pay_order` (
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uq_order_id`(`order_id` ASC) USING BTREE,
INDEX `idx_user_id_product_id`(`user_id` ASC, `product_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;
) ENGINE = InnoDB AUTO_INCREMENT = 35 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of pay_order
-- ----------------------------
INSERT INTO `pay_order` VALUES (31, 'smile01', '9890001', 'MyBatisBook', '376456387082', '2025-07-16 13:47:35', 100.00, 'DEAL_DONE', '<form name=\"punchout_form\" method=\"post\" action=\"https://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=utf-8&method=alipay.trade.page.pay&sign=QiS6Ss9WVG45w4G85WPEw8D7TLbahtqfJQ8M%2BtPGI4FGP0njFMLDamSWT%2B5H67mZlEJjO%2F03a0kZ%2Fs4eg2bsX2ZAn8hzFyUPNHIbAfNikjnlWPxVBT1ageB3zmc95xP9r%2BtGRnuPqryxSs1FzKQjqbQJJRWXxdQE%2FEHjq5bc6zTODbiDMk%2B8hhy104FlM%2BPiT4SKKOfAgtHI2rFruW%2B6rlIHdxsI5roHvq650uDQ0Ir%2BqTZ46FP%2Fv2RpNfmOp1pR8%2BJ4NAZlti8wng0AzWVkl7xUdsgBQ%2BD9Bja57Lt8pZC1SI0%2BrV0HOU0H%2F8AkuVvdCzAPgwUrzaPkYexrsH6a2g%3D%3D&return_url=https%3A%2F%2Fblog.bitday.top&notify_url=https%3A%2F%2Fpay.bitday.top%2Fapi%2Fv1%2Falipay%2Falipay_notify_url&version=1.0&app_id=9021000150645052&sign_type=RSA2&timestamp=2025-07-16+13%3A47%3A35&alipay_sdk=alipay-sdk-java-4.38.157.ALL&format=json\">\n<input type=\"hidden\" name=\"biz_content\" value=\"{&quot;out_trade_no&quot;:&quot;376456387082&quot;,&quot;total_amount&quot;:80.00,&quot;subject&quot;:&quot;MyBatisBook&quot;,&quot;product_code&quot;:&quot;FAST_INSTANT_TRADE_PAY&quot;}\">\n<input type=\"submit\" value=\"立即支付\" style=\"display:none\" >\n</form>\n<script>document.forms[0].submit();</script>', '2025-07-16 13:50:29', 1, 20.00, 80.00, '2025-07-16 13:47:35', '2025-07-16 13:58:39');
INSERT INTO `pay_order` VALUES (32, 'smile02', '9890001', 'MyBatisBook', '503529040337', '2025-07-16 13:55:51', 100.00, 'DEAL_DONE', '<form name=\"punchout_form\" method=\"post\" action=\"https://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=utf-8&method=alipay.trade.page.pay&sign=l1%2FB5WhTnlHDBZGCmFD62mNUQZSGPISW0ny%2F%2Btxju2Mx0m8Ju%2B80cy43YPNGb5Law80EaByVO9O90S5yJ5qIa4%2FMWNHdcrpoVpdaoiuSwa8FvHAZYA65KM6nB7fe1ESy4Q3xOVYi9TS7TFhg5feSbrCe%2F48jfs0A7GxFzZuioQPXb8fFlTpQhm0w4bsUwYf5YfNKxUUNQn%2BK%2FMT2QMBsEceCT0%2FJn5OM2Yl1IufMvdkMsZVQLdN5%2BxsDP%2BabPTxmV%2BGB0gshWT9mHlfRONqxEbqkug99CmqxQKy9GEnKDZFCdkSknAI3ukS14cflpJB84Vq4Pb1m5ZA8AyCbcUUlYQ%3D%3D&return_url=https%3A%2F%2Fblog.bitday.top&notify_url=https%3A%2F%2Fpay.bitday.top%2Fapi%2Fv1%2Falipay%2Falipay_notify_url&version=1.0&app_id=9021000150645052&sign_type=RSA2&timestamp=2025-07-16+13%3A55%3A51&alipay_sdk=alipay-sdk-java-4.38.157.ALL&format=json\">\n<input type=\"hidden\" name=\"biz_content\" value=\"{&quot;out_trade_no&quot;:&quot;503529040337&quot;,&quot;total_amount&quot;:80.00,&quot;subject&quot;:&quot;MyBatisBook&quot;,&quot;product_code&quot;:&quot;FAST_INSTANT_TRADE_PAY&quot;}\">\n<input type=\"submit\" value=\"立即支付\" style=\"display:none\" >\n</form>\n<script>document.forms[0].submit();</script>', '2025-07-16 13:56:47', 1, 20.00, 80.00, '2025-07-16 13:55:50', '2025-07-16 13:58:39');
INSERT INTO `pay_order` VALUES (33, 'smile03', '9890001', 'MyBatisBook', '274640446882', '2025-07-16 13:57:47', 100.00, 'DEAL_DONE', '<form name=\"punchout_form\" method=\"post\" action=\"https://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=utf-8&method=alipay.trade.page.pay&sign=aeJrx00JaoiPo%2Bd%2BelwPekikVqqcT0UjTQluUAWGtxia0Zp9BuzIljU%2BOrQtBj9vhV9cfh1qFxh4%2BGqpTyJN0ZTTPoQa1U4tT1UE2be5JKcV%2BKFtXfpSL%2BFHx7oPLoqXUnhksF5Q8h82n680LVt%2FFGfwo4mBKuFzJ%2FX1libjCzFwdcIHvosESbClSHb5hmNSyWSmyh4UZra9wvaofUjZvrE2%2FhEYIK01Sl9cTXFvmnCHaGLdKgBPUYdx5zVQryKuV%2BfZgDw9s7kEjPommlx3s0XK0tFb8dmitYc%2FdFvVDjDTJ8YE4PuFDedG2qtmOgJ%2BteXfrUHjWwfsMLTuor9elA%3D%3D&return_url=https%3A%2F%2Fblog.bitday.top&notify_url=https%3A%2F%2Fpay.bitday.top%2Fapi%2Fv1%2Falipay%2Falipay_notify_url&version=1.0&app_id=9021000150645052&sign_type=RSA2&timestamp=2025-07-16+13%3A57%3A47&alipay_sdk=alipay-sdk-java-4.38.157.ALL&format=json\">\n<input type=\"hidden\" name=\"biz_content\" value=\"{&quot;out_trade_no&quot;:&quot;274640446882&quot;,&quot;total_amount&quot;:80.00,&quot;subject&quot;:&quot;MyBatisBook&quot;,&quot;product_code&quot;:&quot;FAST_INSTANT_TRADE_PAY&quot;}\">\n<input type=\"submit\" value=\"立即支付\" style=\"display:none\" >\n</form>\n<script>document.forms[0].submit();</script>', '2025-07-16 13:58:39', 1, 20.00, 80.00, '2025-07-16 13:57:47', '2025-07-16 13:58:40');
INSERT INTO `pay_order` VALUES (36, 'opEtBvq6go0co-HQC5DSHkKfkdds', '9890001', 'MyBatisBook', '498873906342', '2025-07-16 15:53:11', 100.00, 'DEAL_DONE', '<form name=\"punchout_form\" method=\"post\" action=\"https://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=utf-8&method=alipay.trade.page.pay&sign=YHURFlD5GRayv1bHAezkCE0W%2BJbS5l4pxwR2eH9k6SLfes2DozLEVvgo3H28mMNTRw1EK6lc%2FN56HUvBOTD4DNtpQ72d3S4v51v%2FqV1%2FxPHIC9HDAQD%2FTTTln6ZjRzLSkWQxRn7aQOBiVhTplu7B%2BBkPKIpIEwWsKtlI0GSCM7%2BE9dQdKrv9BYLD9eAGIJNhL%2FRC3Fu1CkbvhyMPmCxkjkH1vrbx5tmeeWXfOaMkXmR3vePVE9f4xcZgKHiYcBPdaSO%2Brs4uzskw9idRwUzL1xX%2BZElA2hZGCtgXCYz40MVsWiPiwT0voUQcbFE48ZfobOAzKtmpfgILFF2p3qLoFQ%3D%3D&return_url=https%3A%2F%2Fblog.bitday.top&notify_url=https%3A%2F%2Fpay.bitday.top%2Fapi%2Fv1%2Falipay%2Falipay_notify_url&version=1.0&app_id=9021000150645052&sign_type=RSA2&timestamp=2025-07-16+15%3A53%3A11&alipay_sdk=alipay-sdk-java-4.38.157.ALL&format=json\">\n<input type=\"hidden\" name=\"biz_content\" value=\"{&quot;out_trade_no&quot;:&quot;498873906342&quot;,&quot;total_amount&quot;:80.00,&quot;subject&quot;:&quot;MyBatisBook&quot;,&quot;product_code&quot;:&quot;FAST_INSTANT_TRADE_PAY&quot;}\">\n<input type=\"submit\" value=\"立即支付\" style=\"display:none\" >\n</form>\n<script>document.forms[0].submit();</script>', '2025-07-16 15:54:03', 1, 20.00, 80.00, '2025-07-16 15:53:11', '2025-07-16 16:44:47');
INSERT INTO `pay_order` VALUES (39, 'smile01', '9890001', 'MyBatisBook', '369511405849', '2025-07-16 16:28:58', 100.00, 'PAY_SUCCESS', '<form name=\"punchout_form\" method=\"post\" action=\"https://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=utf-8&method=alipay.trade.page.pay&sign=O11ikCcaT503aUwewmNj71Y6PZ5IsqAQ3vqrJK3bCahFJe9KmWbAYMkO91%2FGrCmkmekZ%2Bl2wL85OS4IPNlAMRKmtOhRC22VyqHFjhFaLCr5zzQjvoWgFWMOiAt33x8cdeLvi1RW7nQ66ll1SEi33syInH154TO3MrqTMmLGIPdMvXDmz%2FyfmSQ5h6ZQdN9aaSdFSTR8q%2BFgcvGMSVJSTAog%2BJbMHwsRxZXqlFI8LX8%2Fsh7fRoM87l8Ws1jAKRm6BS6sRgbfmEOdtJRO95Uc2LYLVT817%2B3Fvll2B8KwkSa66Ar2Oo6s6emCQJl3IG67TcA5%2FdPhtyYFtFsqSbpAqQw%3D%3D&return_url=https%3A%2F%2Fblog.bitday.top&notify_url=https%3A%2F%2Fpay.bitday.top%2Fapi%2Fv1%2Falipay%2Falipay_notify_url&version=1.0&app_id=9021000150645052&sign_type=RSA2&timestamp=2025-07-16+16%3A28%3A59&alipay_sdk=alipay-sdk-java-4.38.157.ALL&format=json\">\n<input type=\"hidden\" name=\"biz_content\" value=\"{&quot;out_trade_no&quot;:&quot;369511405849&quot;,&quot;total_amount&quot;:80.00,&quot;subject&quot;:&quot;MyBatisBook&quot;,&quot;product_code&quot;:&quot;FAST_INSTANT_TRADE_PAY&quot;}\">\n<input type=\"submit\" value=\"立即支付\" style=\"display:none\" >\n</form>\n<script>document.forms[0].submit();</script>', '2025-07-16 16:30:33', 1, 20.00, 80.00, '2025-07-16 16:28:57', '2025-07-16 16:30:33');
INSERT INTO `pay_order` VALUES (40, 'smile01', '9890001', 'MyBatisBook', '698481154046', '2025-07-16 16:34:25', 100.00, 'DEAL_DONE', '<form name=\"punchout_form\" method=\"post\" action=\"https://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=utf-8&method=alipay.trade.page.pay&sign=nCszW8W%2F%2Fyhu7x9uPcJJmKv8gYoayqFmqLojw8Q%2BDt2bI5OqBKyoZhT61rdexukp%2BLtvZcWOxJ8sDN%2Bf4Zq8FUvb7uxCsWaD7HWTlgm5KXdSc%2BEVawudxr8aWd16ZRI69WDCGiAvKCgTkX20me2%2Bnv2sdHBd2p7G%2FdkhMtVpMPQHxu4a%2BCQ%2Fatlb2JU1qT1GK0SGglQjtQQpb9mNJIIS5NTf2rGF5%2BIB%2BGWU%2Fj0tGl5ney6ZhxuEnRJzIbrgz6riYOsvhguntMIZxws1%2F8ZakrGE2MYsjesahI8v6eaNAJij2jjkLqX40jrleFwLGMZvidRPgOnpr4z3q5n%2FEFo5dQ%3D%3D&return_url=https%3A%2F%2Fblog.bitday.top&notify_url=https%3A%2F%2Fpay.bitday.top%2Fapi%2Fv1%2Falipay%2Falipay_notify_url&version=1.0&app_id=9021000150645052&sign_type=RSA2&timestamp=2025-07-16+16%3A34%3A25&alipay_sdk=alipay-sdk-java-4.38.157.ALL&format=json\">\n<input type=\"hidden\" name=\"biz_content\" value=\"{&quot;out_trade_no&quot;:&quot;698481154046&quot;,&quot;total_amount&quot;:100.00,&quot;subject&quot;:&quot;MyBatisBook&quot;,&quot;product_code&quot;:&quot;FAST_INSTANT_TRADE_PAY&quot;}\">\n<input type=\"submit\" value=\"立即支付\" style=\"display:none\" >\n</form>\n<script>document.forms[0].submit();</script>', '2025-07-16 16:35:36', 0, 0.00, 100.00, '2025-07-16 16:34:24', '2025-07-16 16:35:36');
INSERT INTO `pay_order` VALUES (41, 'smile02', '9890001', 'MyBatisBook', '541129857040', '2025-07-16 16:36:49', 100.00, 'DEAL_DONE', '<form name=\"punchout_form\" method=\"post\" action=\"https://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=utf-8&method=alipay.trade.page.pay&sign=Ot5z8oK7xO7jmA2pzCnVkE32AdUjurl9YPuSgNwtWjI433Ki5ZAuvhxFfW2V6N20JQPYiGA%2FFYwlgPZ2V53DfeYEDwTZ9yXyJ9ZL74J6kUWGsotnp53JUQZkTTTMT6uBIsl2r2lt5YeLo3GvOkTdxBqxpow%2F%2F8ZxvY6xi9IIN5DGQ8inhwNR8p3rAJS82PKHKFHImNeYg51pUPtU0cqJzrXeN4bT0EincERBhT7j6pcvvRjrPDX9VgSRdwtp5duQC2I9PabqihLVXhAMwhafcKkSRPrYVlJmUgedtYw2Z7Y74CTjgMdYuEKznRsEGvabqUCP2nQ2lWssVTsmJ35cJQ%3D%3D&return_url=https%3A%2F%2Fblog.bitday.top&notify_url=https%3A%2F%2Fpay.bitday.top%2Fapi%2Fv1%2Falipay%2Falipay_notify_url&version=1.0&app_id=9021000150645052&sign_type=RSA2&timestamp=2025-07-16+16%3A36%3A49&alipay_sdk=alipay-sdk-java-4.38.157.ALL&format=json\">\n<input type=\"hidden\" name=\"biz_content\" value=\"{&quot;out_trade_no&quot;:&quot;541129857040&quot;,&quot;total_amount&quot;:80.00,&quot;subject&quot;:&quot;MyBatisBook&quot;,&quot;product_code&quot;:&quot;FAST_INSTANT_TRADE_PAY&quot;}\">\n<input type=\"submit\" value=\"立即支付\" style=\"display:none\" >\n</form>\n<script>document.forms[0].submit();</script>', '2025-07-16 16:38:05', 1, 20.00, 80.00, '2025-07-16 16:36:49', '2025-07-16 16:44:47');
INSERT INTO `pay_order` VALUES (42, 'smile04', '9890001', 'MyBatisBook', '381698719384', '2025-07-16 16:43:47', 100.00, 'DEAL_DONE', '<form name=\"punchout_form\" method=\"post\" action=\"https://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=utf-8&method=alipay.trade.page.pay&sign=MN1%2F2PVMYwkEB4VE3SKphhG95QQUGlMeOwN5PwfNd1C4rdGo4PWJpBor5G3FfuHknCcugcwwQ09zhAljDG6ZxggkzwPvkh04zv5eWuWfIXXIl%2Bzj5onXv6rVvElzL1BFUJ8UxpdgjNNFVWQJVLndHA0WuAB4ZQ0W9c4Jew%2FMd1Gd3p7HzESC16Wcy1zMxg%2FSUPSjORA9Cmf2jrT1RqDAy2Sj%2FNZriEByMlX0rIz44LFCciDLBVo3JzYmshOoU%2FtDwSLn3Ob9WMlLRaKmae8X4bSzCesYbEvEmlN9jl9Mc3MN4hJ0RZ8%2FpC%2B9xk6%2FNKNW1VROTzg801iLhxUK0vAc0g%3D%3D&return_url=https%3A%2F%2Fblog.bitday.top&notify_url=https%3A%2F%2Fpay.bitday.top%2Fapi%2Fv1%2Falipay%2Falipay_notify_url&version=1.0&app_id=9021000150645052&sign_type=RSA2&timestamp=2025-07-16+16%3A43%3A47&alipay_sdk=alipay-sdk-java-4.38.157.ALL&format=json\">\n<input type=\"hidden\" name=\"biz_content\" value=\"{&quot;out_trade_no&quot;:&quot;381698719384&quot;,&quot;total_amount&quot;:80.00,&quot;subject&quot;:&quot;MyBatisBook&quot;,&quot;product_code&quot;:&quot;FAST_INSTANT_TRADE_PAY&quot;}\">\n<input type=\"submit\" value=\"立即支付\" style=\"display:none\" >\n</form>\n<script>document.forms[0].submit();</script>', '2025-07-16 16:44:47', 1, 20.00, 80.00, '2025-07-16 16:43:46', '2025-07-16 16:44:47');
INSERT INTO `pay_order` VALUES (45, 'smile04', '9890001', 'MyBatisBook', '380924838419', '2025-07-16 16:54:06', 100.00, 'DEAL_DONE', '<form name=\"punchout_form\" method=\"post\" action=\"https://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=utf-8&method=alipay.trade.page.pay&sign=SsZLFKzuRwKy%2BfH71Qr%2BhpHFi1zoDdb11O7Exl08xstV628NGoRTBzUeLiZqJWAmlCo6CA4y%2F5SLVdE1shz%2FOPYRJfvRvxIkBbn8qfH79bJyMcd3BIt3ETPPMJSHDZ9mWU1HEAhP4n5rsKBRZHG0mjo%2BxQdoApF6pAwqXBXK6xs9McrV9c%2BC77vMlQ7yMTY6LT9uYmNsFoxekbXYXswMLmiEBX2F%2BNXw77RUKhA0jlMuM1xEydy5LrNaukXgrnS9wLORosTNDRvZnvf4TIk4ln1IYFDYz2WMfLO658bXdvVDW2huxWCqn6Br9VX3I6hj6AXSnSVd7HswstXZACiieQ%3D%3D&return_url=https%3A%2F%2Fblog.bitday.top&notify_url=https%3A%2F%2Fpay.bitday.top%2Fapi%2Fv1%2Falipay%2Falipay_notify_url&version=1.0&app_id=9021000150645052&sign_type=RSA2&timestamp=2025-07-16+16%3A54%3A05&alipay_sdk=alipay-sdk-java-4.38.157.ALL&format=json\">\n<input type=\"hidden\" name=\"biz_content\" value=\"{&quot;out_trade_no&quot;:&quot;380924838419&quot;,&quot;total_amount&quot;:100.00,&quot;subject&quot;:&quot;MyBatisBook&quot;,&quot;product_code&quot;:&quot;FAST_INSTANT_TRADE_PAY&quot;}\">\n<input type=\"submit\" value=\"立即支付\" style=\"display:none\" >\n</form>\n<script>document.forms[0].submit();</script>', '2025-07-16 16:55:14', 0, 0.00, 100.00, '2025-07-16 16:54:05', '2025-07-16 16:55:14');
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -0,0 +1,147 @@
/* ========== 全局 ========== */
*{
margin:0;padding:0;box-sizing:border-box;
font-family:'PingFang SC','Helvetica Neue',Arial,sans-serif;
}
body{
background:#f5f5f5;
color:#333;
max-width:500px;
margin:0 auto;
position:relative;
padding-bottom:30px;
}
/* ========== 轮播图 ========== */
.swiper-container{
width:100%;height:375px;position:relative;overflow:hidden;
}
.swiper-wrapper{display:flex;transition:transform .3s;}
.swiper-slide{flex:0 0 100%;height:375px;}
.swiper-slide img{width:100%;height:100%;object-fit:contain;background:#fff;}
.swiper-pagination{
position:absolute;bottom:10px;left:50%;
transform:translateX(-50%);display:flex;gap:6px;
}
.swiper-dot{
width:8px;height:8px;border-radius:50%;
background:rgba(255,255,255,.5);transition:all .3s;
}
.swiper-dot.active{background:#ff5000;width:16px;border-radius:4px;}
/* ========== 商品信息 ========== */
.product-info{background:#fff;padding:15px;margin-bottom:10px;}
.price-row{display:flex;align-items:center;margin-bottom:12px;}
.current-price{color:#ff5000;font-size:28px;font-weight:bold;}
.current-price::before{content:"¥";font-size:18px;}
.original-price{
color:#999;font-size:16px;text-decoration:line-through;margin-left:8px;
}
.original-price::before{content:"¥";}
.title{font-size:18px;font-weight:bold;line-height:1.4;margin-bottom:10px;}
/* 促销行 */
.promo-row{display:flex;align-items:center;gap:6px;margin-top:6px;}
.promo-tag{
flex-shrink:0;
display:inline-block;
background:linear-gradient(90deg,#ff2c2c,#ff6b22);
color:#fff;font-size:12px;padding:2px 6px;border-radius:2px;
}
.promo-box{
display:inline-block;font-size:13px;padding:2px 6px;
border-radius:4px;font-weight:600;line-height:1.2;white-space:nowrap;
}
.promo-box.drop,
.promo-box.sold{
background:linear-gradient(90deg,#ff7e00,#ff5000);
color:#fff;
}
/* ========== 拼单列表 ========== */
.group-buying{background:#fff;padding:15px;margin-bottom:10px;position:relative;overflow:hidden;}
.section-title{
font-size:16px;font-weight:bold;margin-bottom:12px;position:relative;padding-left:10px;
}
.section-title::before{
content:"";position:absolute;left:0;top:50%;transform:translateY(-50%);
width:3px;height:16px;background:#ff5000;border-radius:2px;
}
.group-users{height:120px;position:relative;overflow:hidden;}
.user-list{position:absolute;top:0;left:0;width:100%;transition:transform .5s ease;}
.user-item{
display:flex;align-items:center;padding:8px 0;border-bottom:1px solid #f5f5f5;
}
.user-item:last-child{border-bottom:none;}
.user-avatar{
width:40px;height:40px;border-radius:50%;background:#f5f5f5;
display:flex;align-items:center;justify-content:center;margin-right:10px;
color:#999;font-size:20px;
}
.user-info{flex:1;}
.user-name{font-size:15px;font-weight:bold;margin-bottom:4px;}
.user-status{font-size:13px;color:#666;}
.countdown{
display:inline-block;background:#ff5000;color:#fff;
padding:1px 4px;border-radius:2px;margin-left:5px;
}
.buy-btn{
background:linear-gradient(90deg,#ff2c2c,#ff6b22);
color:#fff;border:none;border-radius:4px;padding:6px 15px;
font-size:14px;font-weight:bold;cursor:pointer;
}
/* ========== 底部操作栏 ========== */
.action-bar{
position:fixed;inset-inline:0;bottom:0;max-width:500px;margin:0 auto;
background:#fff;display:flex;height:60px;
box-shadow:0 -2px 10px rgba(0,0,0,.1);z-index:100;
}
.action-btn{
flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;
font-size:12px;color:#666;
}
.action-btn i{font-size:20px;margin-bottom:4px;}
.purchase-btn{flex:2;display:flex;}
.btn-single,
.btn-group{
flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;
gap:2px;border:none;cursor:pointer;
}
.btn-single{background:#ff9500;color:#fff;}
.btn-group {background:#ff5000;color:#fff;}
.btn-price{font-size:18px;font-weight:700;line-height:1;}
.btn-label{font-size:12px;line-height:1;}
/* ========== 支付弹窗 ========== */
.payment-overlay{
position:fixed;inset:0;z-index:9999;
background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;
backdrop-filter:blur(2px);
}
.payment-modal{
width:300px;max-width:90vw;padding:26px 28px 30px;background:#fff;
border-radius:12px;box-shadow:0 12px 30px rgba(0,0,0,.18);
font-size:14px;line-height:1.45;text-align:center;
}
.payment-modal h3{font-size:18px;margin:0 0 14px;color:#333;}
.payment-modal p{margin:6px 0;color:#555;word-break:break-all;}
.payment-modal .copyable{color:#ff5000;cursor:pointer;text-decoration:underline;}
.modal-buttons{margin-top:22px;display:flex;gap:12px;}
.modal-buttons button{
flex:1;padding:8px 0;border-radius:6px;font-size:14px;cursor:pointer;border:none;
}
.confirm-btn{background:#ff5000;color:#fff;}
.cancel-btn{background:#f2f3f5;color:#333;}

View File

@ -0,0 +1,94 @@
/* ==================== Reset & 基础 ==================== */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: "Helvetica Neue", Arial, sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
/* ==================== 登录卡片 ==================== */
.login-container {
background-color: #fff;
padding: 40px;
border-radius: 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
text-align: center;
max-width: 400px;
width: 90%;
transition: all 0.3s ease;
}
.login-container:hover {
transform: translateY(-5px);
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.15);
}
.login-container h1 {
margin-bottom: 30px;
color: #333;
font-size: 28px;
font-weight: 600;
}
/* ==================== Logo ==================== */
.logo {
width: 80px;
height: 80px;
margin-bottom: 20px;
}
/* ==================== 二维码 ==================== */
.qr-code {
margin: 30px 0;
position: relative;
}
.qr-code img {
width: 200px;
height: 200px;
border-radius: 10px;
transition: all 0.3s ease;
}
.qr-code::before {
content: "";
position: absolute;
inset: -5px;
background: linear-gradient(45deg, #12c2e9, #c471ed, #f64f59);
z-index: -1;
filter: blur(20px);
border-radius: 15px;
opacity: 0;
transition: opacity 0.3s ease;
}
.qr-code:hover::before {
opacity: 1;
}
/* ==================== 说明文字 ==================== */
.instructions {
color: #666;
font-size: 16px;
margin-top: 20px;
line-height: 1.5;
}
/* ==================== 动画 ==================== */
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.pulse {
animation: pulse 2s infinite;
}

View File

@ -1,5 +0,0 @@
<form name="punchout_form" method="post" action="https://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=utf-8&method=alipay.trade.page.pay&sign=aeJrx00JaoiPo%2Bd%2BelwPekikVqqcT0UjTQluUAWGtxia0Zp9BuzIljU%2BOrQtBj9vhV9cfh1qFxh4%2BGqpTyJN0ZTTPoQa1U4tT1UE2be5JKcV%2BKFtXfpSL%2BFHx7oPLoqXUnhksF5Q8h82n680LVt%2FFGfwo4mBKuFzJ%2FX1libjCzFwdcIHvosESbClSHb5hmNSyWSmyh4UZra9wvaofUjZvrE2%2FhEYIK01Sl9cTXFvmnCHaGLdKgBPUYdx5zVQryKuV%2BfZgDw9s7kEjPommlx3s0XK0tFb8dmitYc%2FdFvVDjDTJ8YE4PuFDedG2qtmOgJ%2BteXfrUHjWwfsMLTuor9elA%3D%3D&return_url=https%3A%2F%2Fblog.bitday.top&notify_url=https%3A%2F%2Fpay.bitday.top%2Fapi%2Fv1%2Falipay%2Falipay_notify_url&version=1.0&app_id=9021000150645052&sign_type=RSA2&timestamp=2025-07-16+13%3A57%3A47&alipay_sdk=alipay-sdk-java-4.38.157.ALL&format=json">
<input type="hidden" name="biz_content" value="{&quot;out_trade_no&quot;:&quot;274640446882&quot;,&quot;total_amount&quot;:80.00,&quot;subject&quot;:&quot;MyBatisBook&quot;,&quot;product_code&quot;:&quot;FAST_INSTANT_TRADE_PAY&quot;}">
<input type="submit" value="立即支付" style="display:none" >
</form>
<script>document.forms[0].submit();</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 950 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 KiB

View File

@ -1,123 +1,92 @@
<!DOCTYPE html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>商品下单支付页</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f7f7f7;
text-align: center; /* 添加居中对齐 */
}
.container {
max-width: 600px;
margin: 50px auto;
padding: 20px;
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.product-info {
margin-bottom: 20px;
border: 1px solid #ddd; /* 添加边框 */
padding: 10px; /* 添加内边距 */
text-align: center; /* 添加居中对齐 */
}
.product-info h2 {
margin: 0;
margin-bottom: 5px;
}
.product-info p {
font-size: 36px; /* 加大字体 */
font-weight: bold; /* 加粗字体 */
color: red; /* 字体颜色改为红色 */
margin: 0; /* 移除默认的margin */
margin-top: 20px;
}
.order-button {
display: block;
width: calc(100% - 40px); /* 减去padding的宽度 */
padding: 10px;
margin: 10px auto; /* 添加自动外边距实现居中 */
font-size: 16px;
color: #fff;
background-color: #007bff;
border: none;
cursor: pointer;
border-radius: 20px; /* 添加圆角 */
}
.order-button:hover {
background-color: #0056b3;
}
.account-info {
font-size: 12px; /* 设置字体大小为9号 */
text-align: center; /* 居中对齐 */
display: block; /* 使span表现得像块级元素 */
margin: 10px 0; /* 添加上下外边距 */
}
</style>
</head>
<body>
<div class="container">
<div class="product-info">
<h2>程序员 - 同款机械键盘</h2>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>手写 MyBatis渐进式源码实践 - 拼多多</title>
<img width="350" src="images/keyboard-001.jpg"/>
<p>价格¥1.68</p>
<!-- 现成样式 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
<link rel="stylesheet" href="css/index.css" />
</head>
<body>
<!-- 顶部轮播图 -->
<div class="swiper-container">
<div class="swiper-wrapper">
<div class="swiper-slide"><img src="images/goods_info2.png" /></div>
<div class="swiper-slide"><img src="images/goods_info3.png" /></div>
<div class="swiper-slide"><img src="images/goods_info1.png" /></div>
</div>
<button id="orderButton" class="order-button">立即下单「沙箱支付」</button>
<span class="account-info">测试账号jhmfgu7187@sandbox.com 密码111111 支付111111</span>
<div class="swiper-pagination"></div>
</div>
<script>
function getCookie(name) {
let cookieArr = document.cookie.split(";");
for(let i = 0; i < cookieArr.length; i++) {
let cookiePair = cookieArr[i].split("=");
if(name == cookiePair[0].trim()) {
return decodeURIComponent(cookiePair[1]);
}
}
return null;
}
<!-- 商品信息 -->
<div class="product-info">
<div class="price-row">
<div class="current-price" id="currentPrice"></div>
<div class="original-price" id="originalPrice"></div>
</div>
document.getElementById('orderButton').addEventListener('click', function() {
var userId = getCookie("loginToken");
if (!userId) {
window.location.href = "login.html"; // 跳转到登录页
return;
}
<div class="title" id="goodsTitle">手写 MyBatis渐进式源码实践全彩</div>
var productId = "10001";
var url = 'http://127.0.0.1:8092/api/v1/alipay/create_pay_order';
<div class="promo-row">
<span class="promo-tag">大促优惠</span>
<span class="promo-box drop" id="dropPrice"></span>
<span class="promo-box sold" id="soldBox"></span>
</div>
</div>
var requestBody = {
userId: userId,
productId: productId
};
<!-- 拼单区域 -->
<div class="group-buying">
<div class="section-title" id="groupTitle"></div>
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestBody) // 将请求体转换为JSON字符串
})
.then(response => response.json()) // 解析JSON格式的响应
.then(json => {
if (json.code === "0000") { // 假设成功的code是"0000"
var formHtml = json.data; // 获取data字段中的HTML表单字符串
document.body.innerHTML += formHtml; // 将表单添加到页面上
document.forms[0].submit(); // 自动提交表单
} else {
console.error('Error:', json.info); // 输出错误信息
}
})
.catch(error => console.error('Error:', error));
});
</script>
<div class="group-users">
<div class="user-list" id="userList"></div>
</div>
</div>
<!-- 底部操作栏 -->
<div class="action-bar">
<div class="action-btn"><i class="fas fa-home"></i><span>首页</span></div>
<div class="action-btn"><i class="fas fa-heart"></i><span>收藏</span></div>
<div class="action-btn"><i class="fas fa-shopping-cart"></i><span>购物车</span></div>
<div class="purchase-btn">
<button class="btn-single" id="btnSingle" data-price="">
<span class="btn-price" id="singlePrice"></span>
<span class="btn-label">单独购买</span>
</button>
<button class="btn-group" id="btnGroup" data-price="">
<span class="btn-price" id="groupPrice"></span>
<span class="btn-label">开团购买</span>
</button>
</div>
</div>
<!-- 支付弹窗模板:浏览器不会渲染 -->
<template id="tpl-payment">
<div class="payment-overlay">
<div class="payment-modal">
<h3>支付确认</h3>
<p>商品金额:<strong id="priceText"></strong></p>
<p>买家账号:
<span class="copyable" data-copy="kvhmoj3832@sandbox.com">
kvhmoj3832@sandbox.com
</span>
</p>
<p>登录密码111111</p>
<p>支付密码111111</p>
<div class="modal-buttons">
<button class="cancel-btn">取消支付</button>
<button class="confirm-btn">确认支付</button>
</div>
</div>
</div>
</template>
<!-- 逻辑脚本 -->
<script src="js/index.js"></script>
</body>
</html>

View File

@ -0,0 +1,347 @@
document.addEventListener('DOMContentLoaded', () => {
/* ========== 通用工具 ========== */
const getCookie = k =>
document.cookie
.split(';')
.map(c => c.trim())
.find(c => c.startsWith(k + '='))?.split('=')[1] || null;
const $ = id => document.getElementById(id);
/* ----------- 0. DOM 快捷引用 ----------- */
const currentPrice = $('currentPrice');
const originalPrice = $('originalPrice');
const dropPrice = $('dropPrice');
const soldBox = $('soldBox');
const groupTitle = $('groupTitle');
const userList = $('userList');
const singlePriceSpan = $('singlePrice');
const groupPriceSpan = $('groupPrice');
const btnSingle = $('btnSingle');
const btnGroup = $('btnGroup');
/* =====================================================
* 1. 取接口数据并渲染
* =================================================== */
const CFG_API = 'http://127.0.0.1:8091/api/v1/gbm/index/query_group_buy_market_config';
// 登录检查
const loginToken = getCookie('loginToken');
if (!loginToken) { location.href = 'login.html'; return; }
const username = loginToken;
// 保存活动 id开团 / 参团时要用)
let activityId = null;
const POST_BODY = {
userId : username,
source : 's01',
channel: 'c01',
goodsId: '9890001'
};
fetch(CFG_API, {
method : 'POST',
headers: { 'Content-Type': 'application/json' },
body : JSON.stringify(POST_BODY)
})
.then(r => r.json())
.then(({ code, info, data }) => {
if (code !== '0000' || !data) { console.error(info); return; }
activityId = data.activityId;
renderGoods(data.goods);
renderStatistic(data.teamStatistic);
renderTeams(data.teamList, data.goods?.payPrice);
})
.catch(console.error);
/* ------------- 渲染商品信息 ------------- */
function renderGoods(g = {}) {
const { originalPrice: op = 0, payPrice = 0, deductionPrice = 0 } = g;
currentPrice.textContent = payPrice;
originalPrice.textContent = op;
dropPrice.textContent = `直降 ¥${deductionPrice}`;
singlePriceSpan.textContent = `${op}`;
groupPriceSpan.textContent = `${payPrice}`;
btnSingle.dataset.price = op;
btnGroup.dataset.price = payPrice;
}
/* ------------- 渲染统计信息 ------------- */
function renderStatistic(stat = {}) {
const { allTeamUserCount = 0 } = stat;
groupTitle.textContent = `${allTeamUserCount}人在抢,参与可立即拼成`;
soldBox.textContent = `${allTeamUserCount}人再抢`;
}
/* ------------- 渲染拼团列表 ------------- */
function renderTeams(list = [], groupPrice = 0) {
if (!list?.length) {
groupTitle.textContent = '小伙伴,赶紧去开团吧,做村里最靓的仔。';
return;
}
userList.innerHTML = '';
list.forEach(t => userList.appendChild(makeItem(t, groupPrice)));
initUserMarquee();
initCountdown();
}
// 把 teamId / activityId 写到 user-info 的 dataset 上,便于点击时读取
function makeItem(team, price) {
const { userId, targetCount, lockCount,
validTimeCountdown, teamId, activityId: tActId } = team;
const leftNum = Math.max(targetCount - lockCount, 0);
const timeText = validTimeCountdown || '00:00:00';
const div = document.createElement('div');
div.className = 'user-item';
div.innerHTML = `
<div class="user-avatar"><i class="fas fa-user"></i></div>
<div class="user-info"
data-teamid="${teamId}"
data-activityid="${tActId}">
<div class="user-name">${userId}</div>
<div class="user-status">
仅剩${leftNum}人成团
<span class="countdown">${timeText}</span>
</div>
</div>
<button class="buy-btn" data-price="${price}">参与拼团</button>
`;
return div;
}
/* =====================================================
* 2. 拼单列表纵向轮播
* =================================================== */
function initUserMarquee() {
const items = userList.querySelectorAll('.user-item');
if (items.length <= 1) return;
const itemH = items[0].offsetHeight;
userList.appendChild(items[0].cloneNode(true));
let idx = 0;
userList.addEventListener('transitionend', () => {
if (idx >= items.length) {
userList.style.transition = 'none';
userList.style.transform = 'translateY(0)';
idx = 0;
void userList.offsetWidth;
}
});
setInterval(() => {
idx++;
userList.style.transition = 'transform .5s ease';
userList.style.transform = `translateY(${-idx * itemH}px)`;
}, 3000);
}
/* =====================================================
* 3. 倒计时
* =================================================== */
let countdownData = [];
function initCountdown() {
const els = document.querySelectorAll('.countdown');
countdownData = Array.from(els).map(el => ({
el,
remain: toSec(el.textContent.trim())
}));
setInterval(tick, 1000);
}
const toSec = t => {
if (!t.includes(':')) return 0;
const [h='00', m='00', s='00'] = t.split(':');
return +h*3600 + +m*60 + +s;
};
const fmt = n => String(n).padStart(2,'0');
const format = s => `${fmt(s/3600|0)}:${fmt((s%3600)/60|0)}:${fmt(s%60)}`;
function tick() {
countdownData.forEach(c=>{
if (c.remain > 0) {
c.remain--;
c.el.textContent = format(c.remain);
if(!c.remain) expire(c.el);
}
});
}
function expire(el){
el.textContent = '00:00:00';
const item = el.closest('.user-item');
item?.classList.add('expired');
item?.querySelector('.buy-btn')?.setAttribute('disabled','disabled');
}
/* =====================================================
* 4. 支付相关
* =================================================== */
const PAY_MALL_URL = 'http://127.0.0.1:8092';
const CREATE_PAY_API = `${PAY_MALL_URL}/api/v1/alipay/create_pay_order`;
// 4.1 支付确认弹窗
function showPaymentConfirm(price){
if(document.querySelector('.payment-overlay')) return;
const tpl = document.getElementById('tpl-payment');
const overlay = tpl.content.firstElementChild.cloneNode(true);
overlay.querySelector('#priceText').textContent = `${price}`;
overlay.querySelector('.copyable').onclick = function(){
navigator.clipboard.writeText(this.dataset.copy)
.then(()=>alert('买家账号已复制到剪贴板'));
};
overlay.querySelector('.cancel-btn').onclick = ()=>{
document.querySelectorAll('form').forEach(f=>f.remove());
overlay.remove();
};
overlay.querySelector('.confirm-btn').onclick = ()=>{
document.querySelector('form')?.submit();
overlay.remove();
};
overlay.addEventListener('click',e=>{
if(e.target===overlay) overlay.querySelector('.cancel-btn').click();
});
document.body.appendChild(overlay);
}
/* ---------- 4.2 单独购买 ---------- */
btnSingle.addEventListener('click', () => {
if (!getCookie('loginToken')) { location.href = 'login.html'; return; }
fetch(CREATE_PAY_API, {
method : 'POST',
headers: { 'Content-Type': 'application/json' },
body : JSON.stringify({
userId : username,
productId : POST_BODY.goodsId,
marketType: 0
})
})
.then(r=>r.json())
.then(json=>{
if (json.code!=='0000') return alert(json.info||'下单失败');
document.querySelectorAll('form').forEach(f=>f.remove());
document.body.insertAdjacentHTML('beforeend', json.data);
showPaymentConfirm(btnSingle.dataset.price);
})
.catch(console.error);
});
/* ---------- 4.3 开团购买 ---------- */
btnGroup.addEventListener('click', () => {
if (!getCookie('loginToken')) { location.href = 'login.html'; return; }
fetch(CREATE_PAY_API, {
method : 'POST',
headers: { 'Content-Type': 'application/json' },
body : JSON.stringify({
userId : username,
productId : POST_BODY.goodsId,
marketType: 1,
activityId: activityId
})
})
.then(r=>r.json())
.then(json=>{
if (json.code!=='0000') return alert(json.info||'下单失败');
document.querySelectorAll('form').forEach(f=>f.remove());
document.body.insertAdjacentHTML('beforeend', json.data);
showPaymentConfirm(btnGroup.dataset.price);
})
.catch(console.error);
});
/* ---------- 4.4 参与拼团( buy-btn ---------- */
document.body.addEventListener('click', e => {
const joinBtn = e.target.closest('.buy-btn');
if (!joinBtn) return;
if (!getCookie('loginToken')) { location.href = 'login.html'; return; }
// 获取 teamId、activityId
const userInfo = joinBtn.closest('.user-item')?.querySelector('.user-info');
const teamId = userInfo?.dataset.teamid;
const actId = userInfo?.dataset.activityid || activityId;
if (!teamId) { return alert('拼团信息已失效,请刷新页面'); }
fetch(CREATE_PAY_API, {
method : 'POST',
headers: { 'Content-Type': 'application/json' },
body : JSON.stringify({
userId : username,
productId : POST_BODY.goodsId,
teamId : teamId,
activityId: actId,
marketType: 1 // 参团
})
})
.then(r=>r.json())
.then(json=>{
if (json.code!=='0000') return alert(json.info||'参团失败');
document.querySelectorAll('form').forEach(f=>f.remove());
document.body.insertAdjacentHTML('beforeend', json.data);
showPaymentConfirm(joinBtn.dataset.price);
})
.catch(console.error);
});
/* =====================================================
* 5. 顶部横向轮播原逻辑保留
* =================================================== */
const wrapper = document.querySelector('.swiper-wrapper');
const slides = [...wrapper.children];
const pagination = document.querySelector('.swiper-pagination');
const count = slides.length;
let current = 0, startX = 0, dragging = false, timer;
for (let i = 0; i < count; i++) {
const dot = document.createElement('div');
dot.className = 'swiper-dot' + (i===0?' active':'');
dot.onclick = () => goTo(i);
pagination.appendChild(dot);
}
const dots = pagination.children;
const goTo = i => {
current = (i+count)%count;
wrapper.style.transition = 'transform .3s ease';
wrapper.style.transform = `translateX(-${current*100}%)`;
[...dots].forEach((d,j)=>d.classList.toggle('active',j===current));
};
const auto = () => { timer=setInterval(()=>goTo(current+1),3000); };
const stop = () => clearInterval(timer);
auto();
const getX = e => e.touches?e.touches[0].clientX:e.clientX;
wrapper.addEventListener('pointerdown', e=>{
stop(); dragging=true; startX=getX(e); wrapper.style.transition='none';
});
wrapper.addEventListener('pointermove', e=>{
if(!dragging) return;
const diff=getX(e)-startX;
wrapper.style.transform=`translateX(calc(${-current*100}% + ${diff}px))`;
});
wrapper.addEventListener('pointerup',endSwipe);
wrapper.addEventListener('pointercancel',endSwipe);
wrapper.addEventListener('pointerleave',endSwipe);
function endSwipe(e){
if(!dragging) return; dragging=false;
const diff=getX(e)-startX;
const limit=wrapper.offsetWidth*0.15;
if(diff> limit) goTo(current-1);
else if(diff<-limit) goTo(current+1);
else goTo(current);
auto();
}
});

View File

@ -0,0 +1,52 @@
/* -------------------- 配置 -------------------- */
const sPayMallUrl = "http://127.0.0.1:8092";
/* -------------------- 工具函数 -------------------- */
function setCookie(name, value, days) {
const date = new Date();
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
document.cookie = `${name}=${value};expires=${date.toUTCString()};path=/`;
}
/* -------------------- 主逻辑 -------------------- */
document.addEventListener("DOMContentLoaded", () => {
// 1) 先拿二维码 ticket
fetch(`${sPayMallUrl}/api/v1/login/weixin_qrcode_ticket`)
.then(res => res.json())
.then(data => {
if (data.code !== "0000") {
console.error("获取二维码 ticket 失败:", data.info);
return;
}
const ticket = data.data;
const qrCodeImg = document.getElementById("qr-code-img");
qrCodeImg.src = `https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=${ticket}`;
qrCodeImg.classList.remove("pulse");
// 2) 轮询确认登录
const intervalId = setInterval(() => checkLoginStatus(ticket, intervalId), 3000);
})
.catch(err => console.error("请求失败:", err));
});
/* -------------------- 轮询检查登录 -------------------- */
function checkLoginStatus(ticket, intervalId) {
fetch(`${sPayMallUrl}/api/v1/login/check_login?ticket=${ticket}`)
.then(res => res.json())
.then(data => {
if (data.code === "0000") {
// 登录成功,停轮询
clearInterval(intervalId);
// 把 token 写入 cookie30 天)
setCookie("loginToken", data.data, 30);
// 跳转首页
window.location.href = "./index.html";
} else {
console.info("login wait");
}
})
.catch(err => console.error("请求失败:", err));
}

View File

@ -1,105 +1,30 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>S Pay Mall 商城登录页</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.login-container {
background-color: #fff;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
text-align: center;
max-width: 400px;
width: 100%;
}
.login-container h1 {
margin-bottom: 20px;
color: #333;
}
.qr-code {
margin: 20px 0;
}
.qr-code img {
width: 200px;
height: 200px;
}
.instructions {
color: #666;
font-size: 14px;
}
</style>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>S Pay Mall 商城登录</title>
<!-- 独立样式文件 -->
<link rel="stylesheet" href="css/login.css" />
</head>
<body>
<div class="login-container">
<h1>S Pay Mall 商城登录页</h1>
<img src="images/logo.png" alt="S Pay Mall Logo" class="logo" />
<h1>欢迎登录 - 拼团团</h1>
<div class="qr-code">
<img id="qr-code-img" src="images/placeholder.png" alt="微信二维码">
<!-- 占位图先显示,加载成功后替换 -->
<img id="qr-code-img" src="images/placeholder.png" alt="微信二维码" class="pulse" />
</div>
<p class="instructions">请使用微信扫描二维码登录</p>
<p class="instructions">
请使用微信扫描二维码登录<br />
扫码后自动登录商城
</p>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
// 获取二维码 ticket
fetch('http://localhost:8092/api/v1/login/weixin_qrcode_ticket')
.then(response => response.json())
.then(data => {
if (data.code === "0000") {
const ticket = data.data;
const qrCodeImg = document.getElementById('qr-code-img');
qrCodeImg.src = `https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=${ticket}`;
// 开始轮询检查登录状态
const intervalId = setInterval(() => {
checkLoginStatus(ticket, intervalId);
}, 3000); // 每3秒检查一次
} else {
console.error('获取二维码 ticket 失败:', data.info);
}
})
.catch(error => {
console.error('请求失败:', error);
});
function checkLoginStatus(ticket, intervalId) {
fetch(`http://localhost:8092/api/v1/login/check_login?ticket=${ticket}`)
.then(response => response.json())
.then(data => {
if (data.code === "0000") {
console.info("login success");
// 停止轮询
clearInterval(intervalId);
// 保存登录 token 到 cookie设置有效期为30天
setCookie('loginToken', data.data, 30);
// 在这里可以重定向到登录后的页面
window.location.href = 'index.html'; // 假设登录成功后跳转到首页
} else {
console.info("login wait");
}
})
.catch(error => {
console.error('请求失败:', error);
});
}
function setCookie(name, value, days) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
const expires = "expires=" + date.toUTCString();
document.cookie = name + "=" + value + ";" + expires + ";path=/";
}
});
</script>
<!-- 独立脚本文件 -->
<script src="js/login.js"></script>
</body>
</html>

View File

@ -27,11 +27,12 @@ public abstract class AbstractOrderService implements IOrderService {
*/
@Override
public PayOrderEntity createOrder(ShopCartEntity shopCartEntity) throws Exception {
// 1. 查询当前用户是否存在掉单未支付订单
// 1. 查询当前用户是否存在掉单未支付订单
OrderEntity unpaidOrderEntity = repository.queryUnPayOrder(shopCartEntity);
// 如果已有未支付订单且状态为支付等待则直接复用
if (unpaidOrderEntity != null && OrderStatusVO.PAY_WAIT.equals(unpaidOrderEntity.getOrderStatusVO())) {
// 如果已有未支付订单且状态为等待支付直接复用该订单
if (unpaidOrderEntity != null
&& OrderStatusVO.PAY_WAIT.equals(unpaidOrderEntity.getOrderStatusVO())) {
log.info("创建订单-存在已存在未支付订单。userId:{} productId:{} orderId:{}",
shopCartEntity.getUserId(), shopCartEntity.getProductId(), unpaidOrderEntity.getOrderId());
return PayOrderEntity.builder()
@ -39,41 +40,64 @@ public abstract class AbstractOrderService implements IOrderService {
.payUrl(unpaidOrderEntity.getPayUrl())
.build();
// 如果已有订单仅创建了记录但未生成支付单则生成支付单
} else if (unpaidOrderEntity != null && OrderStatusVO.CREATE.equals(unpaidOrderEntity.getOrderStatusVO())) {
// 如果已有订单仅创建了记录但未生成支付单则进入支付单生成流程
} else if (unpaidOrderEntity != null
&& OrderStatusVO.CREATE.equals(unpaidOrderEntity.getOrderStatusVO())) {
log.info("创建订单-存在,存在未创建支付单订单,创建支付单开始 userId:{} productId:{} orderId:{}",
shopCartEntity.getUserId(), shopCartEntity.getProductId(), unpaidOrderEntity.getOrderId());
Integer marketType = unpaidOrderEntity.getMarketType();
BigDecimal marketDeductionAmount = unpaidOrderEntity.getMarketDeductionAmount();
PayOrderEntity payOrderEntity;
if (MarketTypeVO.GROUP_BUY_MARKET.getCode().equals(marketType) && marketDeductionAmount == null) {
// === marketType 分支处理 ===
if (MarketTypeVO.GROUP_BUY_MARKET.getCode().equals(marketType)
&& marketDeductionAmount == null) {
// 1) 是拼团类型且还未计算优惠deductionAmount == null
// 先锁定优惠如拼团满减获取 discount 对象
MarketPayDiscountEntity discount = lockMarketPayOrder(
shopCartEntity.getUserId(), shopCartEntity.getTeamId(),
shopCartEntity.getActivityId(), shopCartEntity.getProductId(),
shopCartEntity.getUserId(),
shopCartEntity.getTeamId(),
shopCartEntity.getActivityId(),
shopCartEntity.getProductId(),
unpaidOrderEntity.getOrderId());
// 2) 再发起预支付传入优惠后金额
payOrderEntity = doPrepayOrder(
shopCartEntity.getUserId(), shopCartEntity.getProductId(),
unpaidOrderEntity.getProductName(), unpaidOrderEntity.getOrderId(),
unpaidOrderEntity.getTotalAmount(), discount);
shopCartEntity.getUserId(),
shopCartEntity.getProductId(),
unpaidOrderEntity.getProductName(),
unpaidOrderEntity.getOrderId(),
unpaidOrderEntity.getTotalAmount(),
discount);
} else if (MarketTypeVO.GROUP_BUY_MARKET.getCode().equals(marketType)) {
// 是拼团类型但已经计算过优惠直接使用 payAmount已扣优惠
payOrderEntity = doPrepayOrder(
shopCartEntity.getUserId(), shopCartEntity.getProductId(),
unpaidOrderEntity.getProductName(), unpaidOrderEntity.getOrderId(),
shopCartEntity.getUserId(),
shopCartEntity.getProductId(),
unpaidOrderEntity.getProductName(),
unpaidOrderEntity.getOrderId(),
unpaidOrderEntity.getPayAmount());
} else {
// 其他类型如单独购买或普通下单按总价全额下单
payOrderEntity = doPrepayOrder(
shopCartEntity.getUserId(), shopCartEntity.getProductId(),
unpaidOrderEntity.getProductName(), unpaidOrderEntity.getOrderId(),
shopCartEntity.getUserId(),
shopCartEntity.getProductId(),
unpaidOrderEntity.getProductName(),
unpaidOrderEntity.getOrderId(),
unpaidOrderEntity.getTotalAmount());
}
// 返回已生成支付单的订单信息
return PayOrderEntity.builder()
.orderId(payOrderEntity.getOrderId())
.payUrl(payOrderEntity.getPayUrl())
.build();
}
// 以下为全新下单流程
// 2. 查询商品信息
ProductEntity productEntity = port.queryProductByProductId(shopCartEntity.getProductId());
@ -89,16 +113,18 @@ public abstract class AbstractOrderService implements IOrderService {
.build();
this.doSaveOrder(orderAggregate);
// 4. 如果是拼团发起营销锁单
// 4. 如果是拼团marketType == GROUP_BUY_MARKET先锁定营销优惠
MarketPayDiscountEntity marketPayDiscountEntity = null;
if (MarketTypeVO.GROUP_BUY_MARKET.equals(shopCartEntity.getMarketTypeVO())) {
marketPayDiscountEntity = this.lockMarketPayOrder(
shopCartEntity.getUserId(), shopCartEntity.getTeamId(),
shopCartEntity.getActivityId(), shopCartEntity.getProductId(),
marketPayDiscountEntity = this.lockMarketPayOrder( //调用拼团交易系统的锁单逻辑
shopCartEntity.getUserId(),
shopCartEntity.getTeamId(),
shopCartEntity.getActivityId(),
shopCartEntity.getProductId(),
orderEntity.getOrderId());
}
// 5. 创建支付订单
// 5. 创建支付订单预支付如果有拼团优惠则传入 discount否则按原价下单
PayOrderEntity payOrderEntity = doPrepayOrder(
shopCartEntity.getUserId(),
productEntity.getProductId(),
@ -110,12 +136,14 @@ public abstract class AbstractOrderService implements IOrderService {
log.info("创建订单-完成生成支付单。userId: {} orderId: {} payUrl: {}",
shopCartEntity.getUserId(), orderEntity.getOrderId(), payOrderEntity.getPayUrl());
// 返回新订单的支付链接
return PayOrderEntity.builder()
.orderId(orderEntity.getOrderId())
.payUrl(payOrderEntity.getPayUrl())
.build();
}
/**
* 保存订单
*/

View File

@ -56,7 +56,7 @@ public class OrderService extends AbstractOrderService{
@Override
protected PayOrderEntity doPrepayOrder(String userId, String productId, String productName, String orderId, BigDecimal totalAmount, MarketPayDiscountEntity marketPayDiscountEntity) throws AlipayApiException {
// 支付金额
// 支付金额如果走拼团流程就取PayPrice否则就是totalAmount
BigDecimal payAmount = null == marketPayDiscountEntity ? totalAmount : marketPayDiscountEntity.getPayPrice();
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();

View File

@ -54,6 +54,7 @@ public class AliPayController implements IPayService {
.productId(productId)
.teamId(teamId)
.marketTypeVO(MarketTypeVO.valueOf(marketType))
.activityId(createPayRequestDTO.getActivityId())
.build());
log.info("商品下单根据商品ID创建支付单完成 userId:{} productId:{} orderId:{}", userId, productId, payOrderEntity.getOrderId());

View File

@ -25,12 +25,13 @@ public class NoPayNotifyOrderJob {
private final AlipayClient alipayClient;
/**
* 3 秒执行一次扫描超过1分钟未收到回调的待支付订单
* 10 秒执行一次扫描超过1分钟未收到回调的待支付订单
*/
@Scheduled(cron = "0/3 * * * * ?")
@Scheduled(cron = "0/10 * * * * ?")
public void exec() {
try {
log.info("任务:检测未接收到或未正确处理的支付回调通知");
//查找所有STATUS为PAY_WAIT的订单
List<String> orderIds = orderService.queryNoPayNotifyOrder();
if (null == orderIds || orderIds.isEmpty()) return;

View File

@ -18,9 +18,9 @@ public class TimeoutCloseOrderJob {
private IOrderService orderService;
/**
* 10分钟执行一次扫描超时未支付订单
* 15 分钟执行一次扫描超时未支付订单
*/
@Scheduled(cron = "0 0/30 * * * ?")
@Scheduled(cron = "0 0/15 * * * ?")
public void exec() {
try {
log.info("任务超时30分钟订单关闭");
@ -31,11 +31,12 @@ public class TimeoutCloseOrderJob {
}
// 遍历订单逐一关闭并记录结果
for (String orderId : orderIds) {
// 设置 STATUS为CLOSE
boolean status = orderService.changeOrderClose(orderId);
log.info("定时任务,超时30分钟订单关闭 orderId: {} status{}", orderId, status);
log.info("定时任务,超时15分钟订单关闭 orderId: {} status{}", orderId, status);
}
} catch (Exception e) {
log.error("定时任务,超时30分钟订单关闭失败", e);
log.error("定时任务,超时15分钟订单关闭失败", e);
}
}

View File

@ -26,6 +26,9 @@ public class MessageTextEntity {
@XStreamAlias("MsgId")
private String msgId;
@XStreamAlias("MsgID")
private String msgID;
@XStreamAlias("Status")
private String status;
@ -84,6 +87,14 @@ public class MessageTextEntity {
this.msgId = msgId;
}
public String getMsgID() {
return msgID;
}
public void setMsgID(String msgID) {
this.msgID = msgID;
}
public String getStatus() {
return status;
}