<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <title>校园卡办理</title> <link href="https://cdn.wjlo.cc/modstatic/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.wjlo.cc/modstatic/css/bootstrap-icons.min.css" rel="stylesheet"> <style> :root{--primary: #4f46e5;--primary-light: #eef2ff}*{margin:0;padding:0;box-sizing:border-box}body{font-family:system-ui,-apple-system,sans-serif;background:#f5f5f5;max-width:750px;margin:0 auto;min-height:100vh}.header{background:linear-gradient(135deg,#4f46e5,#7c3aed);color:#fff;padding:2rem 1.5rem 3rem}.header h1{font-size:1.5rem;font-weight:700}.header .sub{font-size:.85rem;opacity:.8;margin-top:.25rem}.campus-bar{background:#fff;margin:-1.5rem 1rem 1rem;border-radius:12px;padding:1rem;box-shadow:0 2px 12px #00000014;display:flex;align-items:center;gap:.5rem}.campus-bar i{color:var(--primary);font-size:1.2rem}.campus-bar .name{font-weight:600;flex:1}.campus-bar .change{color:var(--primary);font-size:.85rem;cursor:pointer}.section{padding:0 1rem;margin-bottom:1rem}.section-title{font-size:1rem;font-weight:700;margin-bottom:.75rem;color:#333}.product-card{background:#fff;border-radius:12px;padding:1.25rem;margin-bottom:.75rem;box-shadow:0 1px 4px #0000000f;cursor:pointer;transition:transform .15s ease,box-shadow .15s ease}.product-card:active{transform:scale(.97);box-shadow:0 0 2px #0000001a}.product-card .carrier{font-size:.75rem;padding:.2em .6em;border-radius:20px;font-weight:600}.carrier-CMCC{background:linear-gradient(135deg,#dbeafe,#bfdbfe);color:#1d4ed8}.carrier-CUCC{background:linear-gradient(135deg,#fce7f3,#fbcfe8);color:#be185d}.carrier-CTCC{background:linear-gradient(135deg,#fef3c7,#fde68a);color:#b45309}.carrier-CBN{background:linear-gradient(135deg,#f3e8ff,#e9d5ff);color:#7e22ce}.product-card .name{font-size:1.05rem;font-weight:600;margin:.5rem 0 .25rem}.product-card .price{color:#dc2626;font-size:1.3rem;font-weight:700}.product-card .price small{font-size:.75rem;color:#999}.product-card .btn-buy{background:var(--primary);color:#fff;border:none;padding:.5rem 1.5rem;border-radius:25px;font-weight:600;transition:transform .12s ease,opacity .12s ease}.product-card .btn-buy:active{transform:scale(.9);opacity:.85}.btn{transition:transform .12s ease,opacity .12s ease}.btn:active:not(.btn-loading){transform:scale(.95);opacity:.9}.btn.btn-loading{pointer-events:none;opacity:.7;position:relative}.btn.btn-loading .btn-text{visibility:hidden}.btn.btn-loading:after{content:"";position:absolute;width:18px;height:18px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite;top:50%;left:50%;margin:-9px 0 0 -9px}.bottom-nav{position:fixed;bottom:0;left:50%;transform:translate(-50%);width:100%;max-width:750px;background:#fff;border-top:1px solid #eee;display:flex;padding:.5rem 0;padding-bottom:calc(.5rem + env(safe-area-inset-bottom));z-index:100}.bottom-nav .nav-item{flex:1;text-align:center;color:#999;font-size:.7rem;cursor:pointer;padding:.25rem 0;transition:color .2s ease}.bottom-nav .nav-item i{font-size:1.3rem;display:block;margin-bottom:.15rem;transition:transform .3s cubic-bezier(.34,1.56,.64,1)}.bottom-nav .nav-item.active{color:var(--primary)}.bottom-nav .nav-item.active i{transform:scale(1.15)}.bottom-nav .nav-item.nav-bounce i{animation:navBounce .4s cubic-bezier(.34,1.56,.64,1)}.page{display:none;padding-bottom:80px}.page.active{display:block;animation:fadeIn .3s ease both}.order-card{background:#fff;border-radius:12px;padding:1rem;margin-bottom:.75rem;box-shadow:0 1px 4px #0000000f}.order-card .order-no{font-size:.8rem;color:#999}.order-card .product{font-weight:600;margin:.25rem 0}.order-card .status{font-size:.8rem;padding:.2em .6em;border-radius:20px}.fill-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:#0009;z-index:1000;display:flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:opacity .25s ease}.fill-overlay.show{opacity:1;pointer-events:auto}.fill-card{background:#fff;border-radius:16px;padding:2rem;width:90%;max-width:380px;text-align:center;transform:scale(.8);opacity:0;transition:transform .3s cubic-bezier(.34,1.56,.64,1),opacity .25s ease}.fill-overlay.show .fill-card{transform:scale(1);opacity:1}.fill-card h3{color:#dc2626;margin-bottom:.5rem}.fill-card p{color:#666;font-size:.9rem;margin-bottom:1.5rem}.fill-card input{width:100%;padding:.75rem;border:2px solid #e5e7eb;border-radius:10px;font-size:1.1rem;text-align:center;letter-spacing:2px;margin-bottom:1rem;transition:border-color .2s ease}.fill-card input:focus{border-color:var(--primary);outline:none}.empty-state{text-align:center;padding:3rem 1rem;color:#999}.empty-state i{font-size:3rem;margin-bottom:1rem}.campus-picker-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:#00000080;z-index:1001;opacity:0;pointer-events:none;transition:opacity .3s ease}.campus-picker-overlay.show{opacity:1;pointer-events:auto}.campus-picker-panel{position:fixed;bottom:0;left:50%;transform:translate(-50%) translateY(100%);width:100%;max-width:750px;background:#fff;border-radius:16px 16px 0 0;z-index:1002;transition:transform .35s cubic-bezier(.32,.72,0,1);max-height:60vh;display:flex;flex-direction:column;padding-bottom:env(safe-area-inset-bottom)}.campus-picker-panel.show{transform:translate(-50%) translateY(0)}.campus-picker-header{padding:1rem 1.25rem;border-bottom:1px solid #f0f0f0;display:flex;justify-content:space-between;align-items:center;flex-shrink:0}.campus-picker-header .title{font-size:1.05rem;font-weight:700;color:#333}.campus-picker-header .close-btn{width:32px;height:32px;border-radius:50%;background:#f5f5f5;border:none;display:flex;align-items:center;justify-content:center;font-size:1.1rem;color:#999;cursor:pointer}.campus-picker-list{overflow-y:auto;flex:1;padding:.5rem 0}.campus-picker-item{padding:.9rem 1.25rem;display:flex;align-items:center;gap:.75rem;cursor:pointer;transition:background .15s ease}.campus-picker-item:active{background:#f5f5f5}.campus-picker-item .icon{width:36px;height:36px;border-radius:10px;background:var(--primary-light);display:flex;align-items:center;justify-content:center;color:var(--primary);font-size:1.1rem;flex-shrink:0}.campus-picker-item .label{font-size:.95rem;color:#333;flex:1}.campus-picker-item .check{color:var(--primary);font-size:1.2rem;opacity:0;transition:opacity .15s}.campus-picker-item.selected .check{opacity:1}.campus-picker-handle{width:36px;height:4px;border-radius:2px;background:#e0e0e0;margin:8px auto 0}.toast-container{position:fixed;top:0;left:50%;transform:translate(-50%);width:100%;max-width:750px;z-index:2000;pointer-events:none;padding:.5rem 1rem 0}.toast-item{pointer-events:auto;display:flex;align-items:center;gap:.5rem;padding:.75rem 1rem;margin-bottom:.5rem;border-radius:10px;color:#fff;font-size:.9rem;font-weight:500;transform:translateY(-120%);opacity:0;transition:transform .35s cubic-bezier(.32,.72,0,1),opacity .35s ease;box-shadow:0 4px 16px #00000026}.toast-item.show{transform:translateY(0);opacity:1}.toast-item.toast-success{background:linear-gradient(135deg,#22c55e,#16a34a)}.toast-item.toast-error{background:linear-gradient(135deg,#ef4444,#dc2626)}.toast-item.toast-info{background:linear-gradient(135deg,#3b82f6,#2563eb)}.toast-item i{font-size:1.1rem;flex-shrink:0}.skeleton-wrap{padding:0 1rem}.skeleton-bar{height:14px;border-radius:7px;background:linear-gradient(90deg,#eee 25%,#f5f5f5,#eee 75%);background-size:200% 100%;animation:shimmer 1.2s ease-in-out infinite;margin-bottom:.75rem}.skeleton-bar.w60{width:60%}.skeleton-bar.w40{width:40%}.skeleton-card{height:100px;border-radius:12px;background:linear-gradient(90deg,#eee 25%,#f5f5f5,#eee 75%);background-size:200% 100%;animation:shimmer 1.2s ease-in-out infinite;margin-bottom:.75rem}.empty-guide{text-align:center;padding:3rem 2rem}.empty-guide .icon-wrap{width:80px;height:80px;border-radius:50%;background:var(--primary-light);display:flex;align-items:center;justify-content:center;margin:0 auto 1.25rem}.empty-guide .icon-wrap i{font-size:2rem;color:var(--primary)}.empty-guide h4{font-size:1.05rem;font-weight:700;color:#333;margin-bottom:.5rem}.empty-guide p{font-size:.85rem;color:#999;margin-bottom:1.25rem;line-height:1.6}.empty-guide .guide-btn{display:inline-block;background:var(--primary);color:#fff;border:none;padding:.6rem 2rem;border-radius:25px;font-weight:600;font-size:.9rem;cursor:pointer}.welcome-skeleton{position:fixed;top:0;left:50%;transform:translate(-50%);width:100%;max-width:750px;height:100vh;background:#f5f5f5;z-index:3000;display:flex;flex-direction:column;transition:opacity .5s ease}.welcome-skeleton.fade-out{opacity:0;pointer-events:none}.welcome-skeleton .sk-header{height:120px;background:linear-gradient(135deg,#d8d5f7,#e0daf7)}.welcome-skeleton .sk-body{padding:1.5rem 1rem;flex:1}.pay-result{text-align:center;padding:3rem 1.5rem}.pay-result .icon-circle{width:80px;height:80px;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 1.25rem;font-size:2.5rem}.pay-result .icon-circle.success{background:#dcfce7;color:#16a34a}.pay-result .icon-circle.fail{background:#fee2e2;color:#dc2626}.pay-result h3{font-weight:700;margin-bottom:.5rem}.pay-result .info{color:#666;font-size:.9rem;margin-bottom:1.5rem;line-height:1.8}.sales-header{background:linear-gradient(135deg,#4f46e5,#7c3aed);color:#fff;padding:1rem 1.5rem 1.5rem}.sales-header .greeting{font-size:1.1rem;font-weight:700}.sales-header .meta{font-size:.8rem;opacity:.8;margin-top:.15rem}.sales-stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:.75rem;padding:0 1rem;margin:-1rem 0 1rem}.sales-stat-card{background:#fff;border-radius:12px;padding:1rem;box-shadow:0 2px 8px #0000000f;text-align:center}.sales-stat-card .val{font-size:1.4rem;font-weight:700;color:var(--primary)}.sales-stat-card .val.green{color:#059669}.sales-stat-card .val.orange{color:#ea580c}.sales-stat-card .lbl{font-size:.75rem;color:#999;margin-top:.15rem}.sales-quick-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:.5rem;padding:0 1rem;margin-bottom:1rem}.sales-quick-item{background:#fff;border-radius:12px;padding:.75rem .5rem;text-align:center;box-shadow:0 1px 4px #0000000f;cursor:pointer;transition:transform .15s}.sales-quick-item:active{transform:scale(.95)}.sales-quick-item i{font-size:1.5rem;color:var(--primary);display:block;margin-bottom:.25rem}.sales-quick-item span{font-size:.75rem;color:#666}.sales-sub-header{display:flex;align-items:center;padding:1rem;gap:.5rem}.sales-sub-header .back-btn{width:32px;height:32px;border-radius:50%;background:#f0f0f0;border:none;display:flex;align-items:center;justify-content:center;cursor:pointer}.sales-sub-header .title{font-size:1.1rem;font-weight:700;flex:1}.sales-member-card{background:#fff;border-radius:12px;padding:1rem;margin:0 1rem .75rem;box-shadow:0 1px 4px #0000000f;display:flex;align-items:center;gap:.75rem}.sales-member-card .avatar{width:40px;height:40px;border-radius:50%;background:var(--primary-light);display:flex;align-items:center;justify-content:center;color:var(--primary);font-weight:700;flex-shrink:0}.sales-member-card .info{flex:1}.sales-member-card .info .name{font-weight:600;font-size:.95rem}.sales-member-card .info .meta{font-size:.8rem;color:#999}.comm-card{background:#fff;border-radius:12px;padding:1rem;margin:0 1rem .75rem;box-shadow:0 1px 4px #0000000f}.comm-card .top{display:flex;justify-content:space-between;align-items:center;margin-bottom:.25rem}.comm-card .amount{font-size:1.1rem;font-weight:700}.comm-card .amount.green{color:#059669}.comm-card .amount.orange{color:#ea580c}.comm-card .detail{font-size:.8rem;color:#999}.comm-filter-bar{display:flex;gap:.5rem;padding:0 1rem;margin-bottom:1rem;overflow-x:auto}.comm-filter-bar .chip{padding:.35rem .85rem;border-radius:20px;font-size:.8rem;background:#fff;border:1px solid #e5e7eb;cursor:pointer;white-space:nowrap;transition:all .15s}.comm-filter-bar .chip.active{background:var(--primary);color:#fff;border-color:var(--primary)}.invite-section{padding:0 1rem}.invite-code-box{background:#fff;border-radius:16px;padding:2rem;text-align:center;box-shadow:0 2px 12px #0000000f;margin-bottom:1rem}.invite-code-box .code{font-size:2rem;font-weight:800;letter-spacing:4px;color:var(--primary);margin:.75rem 0}.invite-code-box .label{font-size:.85rem;color:#999}.invite-link-box{background:#fff;border-radius:12px;padding:1rem;box-shadow:0 1px 4px #0000000f;margin-bottom:1rem}.invite-link-box .link-text{font-size:.8rem;color:#666;word-break:break-all;background:#f9fafb;padding:.5rem;border-radius:8px;margin-bottom:.75rem}.qrcode-box{background:#fff;border-radius:12px;padding:1.5rem;box-shadow:0 1px 4px #0000000f;text-align:center}.qrcode-box img{border-radius:8px}.checkout-card{background:#fff;border-radius:16px;padding:1rem;margin-bottom:.85rem;box-shadow:0 2px 10px #0000000f}.checkout-card .title{font-size:.95rem;font-weight:700;margin-bottom:.75rem}.checkout-row{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;margin-bottom:.6rem;font-size:.92rem}.checkout-row:last-child{margin-bottom:0}.checkout-row .label{color:#6b7280}.checkout-row .value{color:#111827;font-weight:600;text-align:right}.mode-option{border:1.5px solid #e5e7eb;border-radius:14px;padding:.9rem;display:flex;justify-content:space-between;align-items:center;gap:.75rem;cursor:pointer;margin-bottom:.75rem;transition:all .18s ease}.mode-option.active{border-color:var(--primary);background:#eef2ff;box-shadow:0 0 0 3px #4f46e514}.mode-option .name{font-weight:700}.mode-option .desc{color:#6b7280;font-size:.82rem;margin-top:.2rem}.mode-tag{display:inline-flex;align-items:center;gap:.25rem;padding:.28rem .7rem;border-radius:999px;font-size:.78rem;font-weight:600;background:#eef2ff;color:var(--primary)}.checkout-submit-bar{position:sticky;bottom:calc(70px + env(safe-area-inset-bottom));z-index:20;background:#f5f7fbeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);padding-top:.5rem}.checkout-submit{background:linear-gradient(135deg,var(--primary),#6366f1);color:#fff;border:none;border-radius:14px;width:100%;padding:.95rem 1rem;font-size:1rem;font-weight:700;box-shadow:0 10px 24px #4f46e52e;position:relative}.checkout-submit .sub{display:block;font-size:.76rem;opacity:.9;font-weight:500;margin-top:.15rem}.mode-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#0f172a00;z-index:1200;display:flex;align-items:flex-end;justify-content:center;opacity:0;pointer-events:none;transition:opacity .26s ease,background .26s ease}.mode-overlay.show{opacity:1;pointer-events:auto;background:#0f172a73}.mode-panel{width:100%;max-width:750px;background:#fff;border-radius:20px 20px 0 0;padding:1rem 1rem calc(1rem + env(safe-area-inset-bottom));transform:translateY(100%) scale(.98);opacity:.88;transition:transform .36s cubic-bezier(.22,1,.36,1),opacity .24s ease;box-shadow:0 -12px 36px #0f172a29}.mode-overlay.show .mode-panel{transform:translateY(0) scale(1);opacity:1}.mode-panel .header{display:flex;justify-content:space-between;align-items:center;margin-bottom:.8rem}.mode-panel .header h5{margin:0;font-weight:700}.mode-panel .close-btn{width:34px;height:34px;border-radius:50%;border:none;background:#f3f4f6;color:#6b7280;transition:transform .15s ease,background .2s ease,color .2s ease}.mode-panel .close-btn:active{transform:scale(.92)}.mode-panel .close-btn:hover{background:#e5e7eb;color:#374151}.mode-panel .header,.mode-panel #serviceModeOptions,.mode-panel .btn{animation:fadeIn .28s ease}.mode-option{transform:translateY(10px);opacity:0;animation:modeOptionIn .3s ease forwards}.mode-option:nth-child(2){animation-delay:.04s}.mode-option:active{transform:scale(.985)}.mode-option.active .name{color:var(--primary)}@keyframes modeOptionIn{0%{transform:translateY(10px);opacity:0}to{transform:translateY(0);opacity:1}}.global-loading{position:fixed;top:0;left:0;right:0;bottom:0;background:#ffffffeb;z-index:5000;display:flex;flex-direction:column;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:opacity .25s ease}.global-loading.show{opacity:1;pointer-events:auto}.global-loading .spinner{width:40px;height:40px;border:3px solid #e5e7eb;border-top-color:var(--primary);border-radius:50%;animation:spin .7s linear infinite;margin-bottom:1rem}.global-loading .text{color:#666;font-size:.95rem}.phone-verify-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:#0009;z-index:3000;display:flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:opacity .25s ease}.phone-verify-overlay.show{opacity:1;pointer-events:auto}.phone-verify-card{background:#fff;border-radius:16px;padding:2rem;width:90%;max-width:380px;transform:scale(.8);opacity:0;transition:transform .3s cubic-bezier(.34,1.56,.64,1),opacity .25s ease}.phone-verify-overlay.show .phone-verify-card{transform:scale(1);opacity:1}.select-confirm-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:#0009;z-index:3000;display:flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:opacity .25s ease}.select-confirm-overlay.show{opacity:1;pointer-events:auto}.select-confirm-card{background:#fff;border-radius:16px;padding:2rem;width:90%;max-width:380px;text-align:center;transform:scale(.8);opacity:0;transition:transform .3s cubic-bezier(.34,1.56,.64,1),opacity .25s ease}.select-confirm-overlay.show .select-confirm-card{transform:scale(1);opacity:1}.select-confirm-card .warn-icon{width:56px;height:56px;border-radius:50%;background:#fef3c7;display:flex;align-items:center;justify-content:center;margin:0 auto 1rem}.select-confirm-card .warn-icon i{font-size:1.8rem;color:#f59e0b}.select-confirm-card h4{font-weight:700;margin-bottom:.75rem}.select-confirm-card .tips{text-align:left;background:#fffbeb;border-radius:10px;padding:.85rem;margin-bottom:1.25rem;font-size:.88rem;color:#92400e;line-height:1.7}.select-confirm-card .tips b{color:#dc2626}.settings-item{display:flex;justify-content:space-between;align-items:center;padding:.9rem 0;border-bottom:1px solid #f0f0f0;cursor:pointer}.settings-item:last-child{border-bottom:none}.settings-item .label{color:#333;font-size:.95rem}.settings-item .value{color:#999;font-size:.9rem;display:flex;align-items:center;gap:.25rem}.settings-item .value i{color:#ccc}.phone-verify-card h4{text-align:center;font-weight:700;margin-bottom:.5rem}.phone-verify-card .desc{text-align:center;color:#666;font-size:.85rem;margin-bottom:1.5rem}.phone-verify-card .code-row{display:flex;gap:.5rem;margin-bottom:1rem}.phone-verify-card .code-row input{flex:1}.phone-verify-card .code-row button{white-space:nowrap;min-width:100px}@keyframes fadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}@keyframes navBounce{0%{transform:scale(1)}40%{transform:scale(1.3)}70%{transform:scale(.95)}to{transform:scale(1.15)}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}</style> </head> <body><div class="toast-container" id="toastContainer"></div> <div class="welcome-skeleton" id="welcomeSkeleton" style="display:none"><div class="sk-header"></div><div class="sk-body"><div style="height:52px;border-radius:12px;background:linear-gradient(90deg,#eee 25%,#f5f5f5 50%,#eee 75%);background-size:200% 100%;animation:shimmer 1.2s ease-in-out infinite;margin-bottom:1rem;margin-top:-1.5rem;"></div><div class="skeleton-bar w60"></div><div class="skeleton-card"></div><div class="skeleton-card"></div><div class="skeleton-bar w40"></div></div></div> <div id="page-home" class="page active"> <div class="header"><h1 id="siteName">校园卡办理</h1><div class="sub">选好号，办好卡，一步到位</div></div> <div class="campus-bar" id="campusBar"><i class="bi bi-geo-alt-fill"></i><span class="name" id="campusName">定位中...</span><span class="change" onclick="showCampusPicker()">切换 <i class="bi bi-chevron-right"></i></span></div> <div class="section"><div class="section-title">可选套餐</div><div id="productList"><div class="skeleton-wrap"><div class="skeleton-bar w60"></div><div class="skeleton-card"></div><div class="skeleton-card"></div></div></div></div> </div> <div id="page-product" class="page"><div style="padding:1rem;"><button class="btn btn-sm btn-light mb-3" onclick="showPage('home')"><i class="bi bi-arrow-left"></i> 返回</button><div id="productDetail"></div></div></div> <div id="page-orders" class="page"><div style="padding:1rem;"><h5 class="fw-bold mb-3">我的订单</h5><div id="orderList"><div class="empty-state"><i class="bi bi-receipt"></i><p>暂无订单</p></div></div></div></div> <div id="page-order-detail" class="page"><div style="padding:1rem;"><button class="btn btn-sm btn-light mb-3" onclick="showPage('orders')"><i class="bi bi-arrow-left"></i> 返回订单列表</button><div id="orderDetailContent"></div></div></div> <div id="page-me" class="page"> <div style="padding:1rem;"> <div class="card p-4 text-center mb-3" id="userInfo"><div style="width:64px;height:64px;border-radius:50%;background:#eef2ff;margin:0 auto 0.75rem;display:flex;align-items:center;justify-content:center;"><i class="bi bi-person" style="font-size:2rem;color:var(--primary)"></i></div><h5 id="userNickname">未登录</h5><p class="text-muted small" id="userPhone"></p></div> <div class="card mb-3" id="wxLoginPanel"><div class="card-body text-center py-4"><div style="width:64px;height:64px;border-radius:50%;background:#dcfce7;margin:0 auto 1rem;display:flex;align-items:center;justify-content:center;"><i class="bi bi-wechat" style="font-size:2rem;color:#07c160;"></i></div><h5 class="fw-bold mb-2">微信授权登录</h5><p class="text-muted small mb-3">请在微信中打开本页面，即可自动登录</p><button class="btn w-100" onclick="startWechatLogin()" style="border-radius:10px;background:#07c160;color:#fff;font-weight:600;"><i class="bi bi-box-arrow-in-right me-1"></i>微信登录</button></div></div> <div class="card mb-3" id="salesPanel" style="display:none"><div class="card-header fw-bold d-flex justify-content-between align-items-center" style="cursor:pointer;" onclick="showPage('sales')"><span>销售面板</span><span style="color:var(--primary);font-size:0.85rem;">进入销售中心 <i class="bi bi-chevron-right"></i></span></div><div class="card-body"><div class="row text-center" id="salesStats"></div><div class="mt-3"><p class="small text-muted">我的邀请码</p><div class="d-flex align-items-center gap-2"><code class="fs-4" id="myInviteCode"></code><button class="btn btn-sm btn-outline-primary" onclick="copyInviteCode()">复制</button></div></div></div></div> <div class="list-group"> <a class="list-group-item list-group-item-action d-flex justify-content-between" onclick="showPage('orders')"><span><i class="bi bi-receipt me-2"></i>我的订单</span><i class="bi bi-chevron-right"></i></a> <a class="list-group-item list-group-item-action d-flex justify-content-between" onclick="showPage('settings')"><span><i class="bi bi-gear me-2"></i>个人设置</span><i class="bi bi-chevron-right"></i></a> <a class="list-group-item list-group-item-action d-flex justify-content-between" id="joinTeamBtn" onclick="openSalesApply()" style="display:none"><span><i class="bi bi-people me-2"></i>加入团队</span><span id="joinTeamStatus"><i class="bi bi-chevron-right"></i></span></a> <a class="list-group-item list-group-item-action d-flex justify-content-between" id="contactBtn" onclick="showContact()"><span><i class="bi bi-headset me-2"></i>联系客服</span><i class="bi bi-chevron-right"></i></a> <a class="list-group-item list-group-item-action d-flex justify-content-between text-danger" id="logoutBtn" onclick="doLogout()" style="display:none"><span><i class="bi bi-box-arrow-left me-2"></i>退出登录</span><i class="bi bi-chevron-right"></i></a> </div> </div> </div> <div id="page-settings" class="page"><div style="padding:1rem;"><button class="btn btn-sm btn-light mb-3" onclick="showPage('me')"><i class="bi bi-arrow-left"></i> 返回</button><h5 class="fw-bold mb-3">个人设置</h5><div class="card"><div class="card-body" id="settingsContent"></div></div></div></div> <div id="page-edit-nickname" class="page"><div style="padding:1rem;"><button class="btn btn-sm btn-light mb-3" onclick="showPage('settings')"><i class="bi bi-arrow-left"></i> 返回</button><h5 class="fw-bold mb-3">修改昵称</h5><div class="card p-3"><div class="mb-3"><label class="form-label small text-muted">昵称</label><input type="text" class="form-control" id="editNicknameInput" placeholder="请输入昵称" maxlength="20"></div><button class="btn btn-primary w-100" id="saveNicknameBtn" onclick="saveNickname()" style="border-radius:10px;">保存</button></div></div></div> <div id="page-pay-result" class="page"><div style="padding:1rem;"><button class="btn btn-sm btn-light mb-3" onclick="showPage('home')"><i class="bi bi-arrow-left"></i> 返回首页</button><div id="payResultContent"></div></div></div> <!-- ===== Sales Center Pages ===== --> <div id="page-sales" class="page"><div id="salesDashContent"></div></div> <div id="page-sales-customers" class="page"><div class="sales-sub-header"><button class="back-btn" onclick="showPage('sales')"><i class="bi bi-arrow-left"></i></button><span class="title">我的客户</span></div><div id="salesCustomersContent"></div></div> <div id="page-sales-team" class="page"><div class="sales-sub-header"><button class="back-btn" onclick="showPage('sales')"><i class="bi bi-arrow-left"></i></button><span class="title">我的团队</span></div><div id="salesTeamContent"></div></div> <div id="page-sales-commissions" class="page"><div class="sales-sub-header"><button class="back-btn" onclick="showPage('sales')"><i class="bi bi-arrow-left"></i></button><span class="title">佣金明细</span></div><div id="salesCommContent"></div></div> <div id="page-sales-invite" class="page"><div class="sales-sub-header"><button class="back-btn" onclick="showPage('sales')"><i class="bi bi-arrow-left"></i></button><span class="title">邀请推广</span></div><div id="salesInviteContent"></div></div> <div id="page-sales-orders" class="page"><div class="sales-sub-header"><button class="back-btn" onclick="showPage('sales')"><i class="bi bi-arrow-left"></i></button><span class="title">销售订单</span></div><div id="salesOrdersContent"></div></div> <div id="page-sales-share" class="page"><div class="sales-sub-header"><button class="back-btn" onclick="showPage('sales')"><i class="bi bi-arrow-left"></i></button><span class="title">分享商品</span></div><div id="salesShareContent"></div></div> <div id="page-sales-posters" class="page"><div class="sales-sub-header"><button class="back-btn" onclick="showPage('sales')"><i class="bi bi-arrow-left"></i></button><span class="title">海报生成</span></div><div id="salesPostersContent"></div></div> <div id="posterGenOverlay" class="mode-overlay" style="z-index:3000;" onclick="if(event.target===this)closePosterGen()"><div class="mode-panel" onclick="event.stopPropagation()" style="max-width:400px;"><div class="header"><h5>生成海报</h5><button class="close-btn" onclick="closePosterGen()"><i class="bi bi-x"></i></button></div><div id="posterGenContent" style="text-align:center;padding:1rem;"><div class="text-muted">生成中...</div></div><div style="padding:0 1rem 1rem;"><button class="btn btn-primary w-100" onclick="downloadPoster()" id="posterDownloadBtn" style="display:none;"><i class="bi bi-download"></i> 保存海报</button></div></div></div> <div class="select-confirm-overlay" id="selectConfirmOverlay" onclick="if(event.target===this)closeSelectConfirm()"><div class="select-confirm-card"><div class="warn-icon"><i class="bi bi-exclamation-triangle-fill"></i></div><h4>选号前请务必阅读</h4><div class="tips">1 点击下方按钮将跳转到运营商选号页面<br>2 <b>选号完成后请务必返回本页面</b><br>3 返回后点击 <b>"我已选号，填写手机号"</b><br>4 将办理的手机号回填，否则订单无法完成</div><a class="btn btn-primary w-100" id="selectConfirmLink" href="#" target="_blank" style="border-radius:10px;font-weight:600;" onclick="onSelectConfirmClick()">我已了解，前往选号</a><button class="btn btn-outline-secondary w-100 mt-2" onclick="closeSelectConfirm()" style="border-radius:10px;">取消</button></div></div> <div class="global-loading" id="globalLoading"><div class="spinner"></div><div class="text" id="globalLoadingText">登录中...</div></div> <div class="phone-verify-overlay" id="phoneVerifyOverlay"><div class="phone-verify-card"><h4>验证手机号</h4><div class="desc">为保障您的账户安全，请验证手机号</div><div class="mb-3"><input type="tel" class="form-control" id="verifyPhone" placeholder="请输入手机号" maxlength="11"></div><div class="code-row"><input type="text" class="form-control" id="verifyCode" placeholder="验证码" maxlength="6"><button class="btn btn-outline-primary" id="sendCodeBtn" onclick="sendVerifyCode()">发送验证码</button></div><button class="btn btn-primary w-100" id="verifySubmitBtn" onclick="submitPhoneVerify()" style="border-radius:10px;">确认绑定</button></div></div> <div class="bottom-nav" id="bottomNav"> <div class="nav-item active" data-tab="home" onclick="showPage('home')"><i class="bi bi-house-fill"></i>首页</div> <div class="nav-item" data-tab="orders" onclick="showPage('orders')"><i class="bi bi-receipt"></i>订单</div> <div class="nav-item" data-tab="sales" onclick="showPage('sales')" id="salesNavItem" style="display:none"><i class="bi bi-bar-chart"></i>销售</div> <div class="nav-item" data-tab="me" onclick="showPage('me')"><i class="bi bi-person"></i>我的</div> </div> <div class="campus-picker-overlay" id="campusPickerOverlay" onclick="closeCampusPicker()"></div> <div class="campus-picker-panel" id="campusPickerPanel"><div class="campus-picker-handle"></div><div class="campus-picker-header"><span class="title">选择校区</span><button class="close-btn" onclick="closeCampusPicker()"><i class="bi bi-x"></i></button></div><div class="campus-picker-list" id="campusPickerList"></div></div> <div id="serviceModeOverlay" class="mode-overlay" onclick="closeServiceModePicker()"><div class="mode-panel" onclick="event.stopPropagation()"><div class="header"><h5>请选择办理方式</h5><button class="close-btn" onclick="closeServiceModePicker()"><i class="bi bi-x"></i></button></div><div id="serviceModeOptions"></div><button class="btn btn-primary w-100" onclick="confirmServiceMode(false)" style="border-radius:12px;">确认</button></div></div> <!-- 销售注册弹窗 --> <div id="salesApplyOverlay" class="fill-overlay" onclick="if(event.target===this)closeSalesApply()"> <div class="fill-card" style="max-width:380px;"> <h3 style="margin-bottom:0.25rem;">加入销售团队</h3> <p class="text-muted small" id="salesApplyHint">填写信息提交申请，审核通过后即可成为销售</p> <div style="margin-bottom:0.75rem;"> <label class="form-label small fw-bold">真实姓名 *</label> <input type="text" class="form-control" id="applyRealName" placeholder="请填写本人真实姓名" maxlength="20"> </div> <div style="margin-bottom:0.75rem;"> <label class="form-label small fw-bold">学校 *</label> <select class="form-select" id="applySchool" onchange="onApplySchoolChange(this.value)"><option value="">请选择学校</option></select> </div> <div style="margin-bottom:0.75rem;"> <label class="form-label small fw-bold">校区 *</label> <select class="form-select" id="applyCampus"><option value="">请先选择学校</option></select> </div> <div style="margin-bottom:0.75rem;"> <label class="form-label small fw-bold">上级邀请码（选填）</label> <input type="text" class="form-control" id="applyParentCode" placeholder="如有上级销售的邀请码可填写"> </div> <button class="btn btn-primary w-100" onclick="submitSalesApply()" id="salesApplyBtn" style="border-radius:10px;">提交申请</button> <button class="btn btn-link w-100 text-muted small mt-1" onclick="closeSalesApply()">暂不加入</button> </div> </div> <div id="fillOverlay" class="fill-overlay"><div class="fill-card"><h3>请回填手机号</h3><p>您已完成选号，请将办理的手机号填入以下输入框，以便我们为您完成后续服务</p><input type="tel" id="fillPhoneInput" placeholder="请输入办理的手机号" maxlength="11"><button class="btn btn-primary btn-lg w-100" onclick="submitFillPhone()" id="fillSubmitBtn" style="border-radius:10px;position:relative;"><span class="btn-text">确认提交</span></button><p class="text-muted small mt-2">提交后即可查看订单详情</p></div></div> <script src="https://cdn.wjlo.cc/modstatic/js/bootstrap.bundle.min.js"></script> <script> var API = "/api"; var token = localStorage.getItem("user_token"); var currentUser = null; var currentCampus = null; var currentOrderForFill = null; var cachedCampuses = null; var pendingProductId = null; var pendingFillIsOrder = false; var currentProduct = null; var currentHandlingMode = null; var serviceModePromptedProductId = null; function showToast(msg,type){type = type || "info";var container = document.getElementById("toastContainer");var icons ={success:"bi-check-circle-fill",error:"bi-x-circle-fill",info:"bi-info-circle-fill"}var el = document.createElement("div");el.className = "toast-item toast-" + type;el.innerHTML = '<i class="bi ' + (icons[type]||icons.info) + '"></i><span>' + msg + "</span>";container.appendChild(el);requestAnimationFrame(function(){requestAnimationFrame(function(){el.classList.add("show");});});setTimeout(function(){el.classList.remove("show"); setTimeout(function(){el.remove();},350);},3000)}function setBtnLoading(btn,loading){if (loading){btn.classList.add("btn-loading");btn.disabled = true}else{btn.classList.remove("btn-loading");btn.disabled = false}}var tenantSlug = new URLSearchParams(window.location.search).get("t"); if (!tenantSlug){document.body.innerHTML = '<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:2rem;text-align:center;background:#f5f5f5;">' + '<div style="background:#fff;border-radius:16px;padding:2.5rem 2rem;max-width:360px;width:100%;box-shadow:0 4px 20px rgba(0,0,0,0.08);">' + '<div style="font-size:3rem;margin-bottom:1rem;">🔗</div>' + '<h2 style="font-size:1.25rem;font-weight:700;color:#333;margin-bottom:0.75rem;">链接无效</h2>' + '<p style="font-size:0.9rem;color:#888;line-height:1.6;margin:0;">请通过销售人员分享的专属链接访问<br>如有疑问请联系您的销售顾问</p>' + "</div></div>";throw new Error("No tenant")}function showGlobalLoading(text){document.getElementById("globalLoadingText").textContent = text || "加载中...";document.getElementById("globalLoading").classList.add("show")}function hideGlobalLoading(){document.getElementById("globalLoading").classList.remove("show")}var _codeTimer = null; function showPhoneVerify(){document.getElementById("phoneVerifyOverlay").classList.add("show");document.getElementById("verifyPhone").value = "";document.getElementById("verifyCode").value = ""}function hidePhoneVerify(){document.getElementById("phoneVerifyOverlay").classList.remove("show")}async function sendVerifyCode(){var phone = document.getElementById("verifyPhone").value.trim();if (!/^1[3-9]\d{9}$/.test(phone)){showToast("请输入正确的手机号","error");return}var btn = document.getElementById("sendCodeBtn");setBtnLoading(btn,true);try{await api("/auth/send-code",{method: "POST",body: {phone: phone}});showToast("验证码已发送","success");// 倒计时60秒 var sec = 60;btn.disabled = true;btn.classList.remove("btn-loading");btn.textContent = sec + "s";_codeTimer = setInterval(function() {sec--; if (sec <= 0) {clearInterval(_codeTimer); btn.disabled = false; btn.textContent = "发送验证码";} else {btn.textContent = sec + "s";}},1000)}catch(e){showToast(e.message || "发送失败","error");setBtnLoading(btn,false)}}async function submitPhoneVerify(){var phone = document.getElementById("verifyPhone").value.trim();var code = document.getElementById("verifyCode").value.trim();if (!/^1[3-9]\d{9}$/.test(phone)){showToast("请输入正确的手机号","error");return}if (!code){showToast("请输入验证码","error");return}var btn = document.getElementById("verifySubmitBtn");setBtnLoading(btn,true);try{await api("/auth/verify-phone",{method: "POST",body: {phone: phone,code: code}});if (currentUser) currentUser.phone = phone;hidePhoneVerify();showToast("手机号绑定成功","success");loadUserProfile()}catch(e){showToast(e.message || "验证失败","error")}finally{setBtnLoading(btn,false)}}async function api(path,opts){opts = opts ||{}var separator = path.includes("?") ? "&" : "?";path += separator + "tenant=" + tenantSlug;var headers ={"Content-Type": "application/json"}if (token) headers["Authorization"] = "Bearer " + token;var res = await fetch(API + path,{method: opts.method || "GET",headers: headers,body: opts.body ? JSON.stringify(opts.body) : undefined});var data = await res.json();if (data.code !== 0) throw new Error(data.msg);return data.data}// ======================== Hash Router ======================== var isNavigating = false; // 底部 tab 名称映射：page name -> tab data-tab var pageToTab ={home:"home",orders:"orders","order-detail":"orders",me:"me",settings:"me","edit-nickname":"me",sales:"sales","sales-customers":"sales","sales-team":"sales","sales-commissions":"sales","sales-invite":"sales","sales-orders":"sales","sales-share":"sales","sales-posters":"sales",product:null,"pay-result":null}; function showPage(page,params){isNavigating = true;if (page === "product" && params){location.hash = "#/product/" + params}else if (page === "order-detail" && params){location.hash = "#/orders/" + params}else if (page === "home"){location.hash = "#/"}else if (page === "sales-customers"){location.hash = "#/sales/customers"}else if (page === "sales-team"){location.hash = "#/sales/team"}else if (page === "sales-commissions"){location.hash = "#/sales/commissions"}else if (page === "sales-invite"){location.hash = "#/sales/invite"}else if (page === "sales-orders"){location.hash = "#/sales/orders"}else if (page === "sales-share"){location.hash = "#/sales/share"}else if (page === "sales-posters"){location.hash = "#/sales/posters"}else if (page === "settings"){location.hash = "#/settings"}else if (page === "edit-nickname"){location.hash = "#/settings/nickname"}else{location.hash = "#/" + page}_doShowPage(page,params)}var paramsForPage = null; function _doShowPage(page,params){paramsForPage = params || null;// 销售路由权限检查 if (page.indexOf("sales") === 0 && (!currentUser || !currentUser.is_sales)){showPage("home");return}document.querySelectorAll(".page").forEach(function(p) {p.classList.remove("active");});var el = document.getElementById("page-" + page);if (el) el.classList.add("active");// 更新底部导航 active var tab = pageToTab[page];document.querySelectorAll(".bottom-nav .nav-item").forEach(function(n) {var isTarget = n.dataset.tab === tab; n.classList.toggle("active",isTarget); if (isTarget) {n.classList.remove("nav-bounce"); void n.offsetWidth; n.classList.add("nav-bounce");}});// 加载数据 if (page === "orders") loadUserOrders();if (page === "order-detail") loadOrderDetail(paramsForPage);if (page === "me") loadUserProfile();if (page === "sales") loadSalesDashboard();if (page === "sales-customers") loadSalesCustomers();if (page === "sales-team") loadSalesTeam();if (page === "sales-commissions") loadSalesCommissions();if (page === "sales-invite") loadSalesInvite();if (page === "sales-orders") loadSalesOrders();if (page === "sales-share") loadSalesShare();if (page === "sales-posters") loadSalesPosters();if (page === "settings") loadSettings();if (page === "edit-nickname") loadEditNickname()}function handleRoute(){if (isNavigating){isNavigating = false;return}var hash = location.hash.slice(1) || "/";var match;if ((match = hash.match(/^\/product\/(\d+)$/))){showProduct(parseInt(match[1]))}else if ((match = hash.match(/^\/orders\/(\d+)$/))){_doShowPage("order-detail",parseInt(match[1]))}else if (hash === "/orders"){_doShowPage("orders")}else if (hash === "/me"){_doShowPage("me")}else if (hash === "/pay-result"){_doShowPage("pay-result")}else if (hash === "/sales/customers"){_doShowPage("sales-customers")}else if (hash === "/sales/team"){_doShowPage("sales-team")}else if (hash === "/sales/commissions"){_doShowPage("sales-commissions")}else if (hash === "/sales/invite"){_doShowPage("sales-invite")}else if (hash === "/sales/orders"){_doShowPage("sales-orders")}else if (hash === "/sales/share"){_doShowPage("sales-share")}else if (hash === "/sales/posters"){_doShowPage("sales-posters")}else if (hash === "/sales"){_doShowPage("sales")}else if (hash === "/settings"){_doShowPage("settings")}else if (hash === "/settings/nickname"){_doShowPage("edit-nickname")}else{_doShowPage("home")}}window.addEventListener("hashchange",handleRoute); // 更新底部导航：显示/隐藏销售 tab function updateBottomNav(){var salesNav = document.getElementById("salesNavItem");if (currentUser && currentUser.is_sales){salesNav.style.display = ""}else{salesNav.style.display = "none"}}// ======================== Location & Campus ======================== async function initLocation(){try{if (navigator.geolocation){navigator.geolocation.getCurrentPosition(async function(pos) {var c = await api("/campuses?lat=" + pos.coords.latitude + "&lng=" + pos.coords.longitude); cachedCampuses = c; if (c.length > 0) selectCampus(c[0]);},async function() {var c = await api("/campuses"); cachedCampuses = c; if (c.length > 0) selectCampus(c[0]); else document.getElementById("campusName").textContent = "暂无校区";})}}catch(e){document.getElementById("campusName").textContent = "暂无校区"}}function selectCampus(campus){currentCampus = campus;document.getElementById("campusName").textContent = campus.name;loadProducts(campus.id)}async function showCampusPicker(){var campuses = cachedCampuses;if (!campuses){try{campuses = await api("/campuses");cachedCampuses = campuses}catch(e){showToast("获取校区列表失败","error");return}}var list = document.getElementById("campusPickerList");list.innerHTML = campuses.map(function(c,i) {var sel = currentCampus && currentCampus.id === c.id ? " selected" : ""; return '<div class="campus-picker-item' + sel + '" onclick="pickCampus(' + i + ')"><div class="icon"><i class="bi bi-geo-alt"></i></div><span class="label">' + c.name + '</span><i class="bi bi-check-lg check"></i></div>';}).join("");document.getElementById("campusPickerOverlay").classList.add("show");requestAnimationFrame(function() {document.getElementById("campusPickerPanel").classList.add("show");})}function closeCampusPicker(){document.getElementById("campusPickerPanel").classList.remove("show");setTimeout(function() {document.getElementById("campusPickerOverlay").classList.remove("show");},350)}function pickCampus(index){if (cachedCampuses && cachedCampuses[index]) selectCampus(cachedCampuses[index]);closeCampusPicker()}// ======================== Products ======================== async function loadProducts(campusId){var params = campusId ? "?campus_id=" + campusId : "";var products = await api("/products" + params);var carrierMap ={CMCC:"中国移动",CUCC: "中国联通",CTCC: "中国电信",CBN: "中国广电"}document.getElementById("productList").innerHTML = products.length ? products.map(function(p) {return '<div class="product-card" onclick="showProduct(' + p.id + ')"><div class="d-flex justify-content-between align-items-start"><div><span class="carrier carrier-' + (p.carrier||"CMCC") + '">' + (carrierMap[p.carrier]||p.carrier||"运营商") + '</span><div class="name">' + p.name + '</div><div class="text-muted small">' + (p.description||"") + '</div></div><div class="text-end"><div class="price">¥' + p.registration_fee + '<small>/注册费</small></div><button class="btn-buy mt-2">立即办理</button></div></div></div>';}).join("") : '<div class="empty-state"><i class="bi bi-phone"></i><p>该校区暂无可办理的套餐</p></div>'}function getHandlingModeMeta(mode){if (mode === "offline") return{label:"线下办理",desc: "到店 / 到点办理，使用线下选号链接"}return{label:"邮寄办理",desc: "线上选号，卡片通过邮寄寄送"}}function resolveSelectUrl(product,mode){if (!product) return "";if (Number(product.enable_service_modes || 0)){if (mode === "offline") return product.offline_select_url || product.select_number_url || "";return product.delivery_select_url || product.select_number_url || ""}return product.select_number_url || ""}function ensureHandlingMode(product){if (!product) return "delivery";if (!Number(product.enable_service_modes || 0)) return "delivery";if (currentHandlingMode === "offline" || currentHandlingMode === "delivery") return currentHandlingMode;return ""}function renderServiceModeOptions(product){var selected = ensureHandlingMode(product) || "delivery";var options = [{key: "delivery",icon: "bi-truck",title: "邮寄办理",desc: "适合不方便到场的同学，线上选号后邮寄到手"},{key: "offline",icon: "bi-shop",title: "线下办理",desc: "到线下点位办理，使用线下专属选号链接"}];return options.map(function(opt) {var active = selected === opt.key ? " active" : ""; return '<div class="mode-option' + active + '" onclick="selectServiceMode(\'' + opt.key + '\')"><div><div class="name"><i class="bi ' + opt.icon + ' me-2"></i>' + opt.title + '</div><div class="desc">' + opt.desc + '</div></div><i class="bi bi-check-circle-fill" style="color:' + (active ? "var(--primary)" : "#d1d5db") + ';font-size:1.2rem;"></i></div>';}).join("")}function openServiceModePicker(){if (!currentProduct || !Number(currentProduct.enable_service_modes || 0)) return;document.getElementById("serviceModeOptions").innerHTML = renderServiceModeOptions(currentProduct);document.getElementById("serviceModeOverlay").classList.add("show")}function closeServiceModePicker(){document.getElementById("serviceModeOverlay").classList.remove("show")}function selectServiceMode(mode){currentHandlingMode = mode;document.getElementById("serviceModeOptions").innerHTML = renderServiceModeOptions(currentProduct)}function confirmServiceMode(continueAction){// 如果用户没有手动点选，取视觉上默认选中的值 if (!currentHandlingMode) currentHandlingMode = "delivery";closeServiceModePicker();renderProductDetail(currentProduct)}function renderProductDetail(p){currentProduct = p;var cn ={"CMCC":"中国移动","CUCC":"中国联通","CTCC":"中国电信","CBN":"中国广电"}var mode = ensureHandlingMode(p);var modeMeta = getHandlingModeMeta(mode || "delivery");var hasModePicker = Number(p.enable_service_modes || 0);var selectUrl = resolveSelectUrl(p,mode || "delivery");var hasSelectUrl = !!(selectUrl && selectUrl.trim());var actionText = hasSelectUrl ? "去选号并下单" : "立即办理";var fee = Number(p.registration_fee || 0).toFixed(2);var currentInvite = getInviteCode();var html = "";// ===== 登录引导横幅（未登录时显示） ===== if (!token){html += '<div style="background:linear-gradient(135deg,#ff6b6b 0%,#ee5a6f 100%);border-radius:12px;padding:1rem 1.25rem;color:#fff;margin-bottom:0.85rem;box-shadow:0 4px 12px rgba(238,90,111,0.3);">';html += '<div style="display:flex;align-items:center;justify-content:space-between;">';html += '<div><div style="font-weight:700;font-size:1rem;margin-bottom:0.25rem;"><i class="bi bi-lock-fill me-1"></i>请先登录</div>';html += '<div style="opacity:0.9;font-size:0.8rem;">登录后可查看完整套餐详情并办理</div></div>';html += '<button onclick="doLoginWithReturn()" style="background:#fff;color:#ee5a6f;border:none;padding:0.5rem 1rem;border-radius:20px;font-weight:600;font-size:0.85rem;cursor:pointer;box-shadow:0 2px 8px rgba(0,0,0,0.1);">去登录</button>';html += "</div></div>"}else if (!currentUser || !currentUser.phone){html += '<div style="background:linear-gradient(135deg,#ffa726 0%,#ff7043 100%);border-radius:12px;padding:1rem 1.25rem;color:#fff;margin-bottom:0.85rem;box-shadow:0 4px 12px rgba(255,112,67,0.3);">';html += '<div style="display:flex;align-items:center;justify-content:space-between;">';html += '<div><div style="font-weight:700;font-size:1rem;margin-bottom:0.25rem;"><i class="bi bi-phone-fill me-1"></i>请绑定手机号</div>';html += '<div style="opacity:0.9;font-size:0.8rem;">绑定手机号后才能办理套餐</div></div>';html += '<button onclick="showPhoneVerify()" style="background:#fff;color:#ff7043;border:none;padding:0.5rem 1rem;border-radius:20px;font-weight:600;font-size:0.85rem;cursor:pointer;box-shadow:0 2px 8px rgba(0,0,0,0.1);">去绑定</button>';html += "</div></div>"}// ===== Hero 产品信息 ===== html += '<div style="background:linear-gradient(135deg,#4338ca 0%,#6366f1 100%);border-radius:16px;padding:1.15rem 1.25rem;color:#fff;margin-bottom:0.85rem;">';html += '<div style="display:flex;justify-content:space-between;align-items:flex-start;">';html += '<div style="flex:1;"><span class="carrier carrier-' + (p.carrier||"CMCC") + '" style="display:inline-block;margin-bottom:0.4rem;font-size:0.7rem;padding:0.15em 0.5em;">' + (cn[p.carrier]||"运营商") + "</span>";html += '<div style="font-weight:800;font-size:1.1rem;margin-bottom:0.2rem;">' + p.name + "</div>";html += '<div style="opacity:0.8;font-size:0.78rem;line-height:1.3;">' + (p.description || "官方套餐") + "</div></div>";html += '<div style="text-align:right;padding-left:0.75rem;"><div style="font-size:1.8rem;font-weight:900;line-height:1;">¥' + fee + '</div><div style="opacity:0.7;font-size:0.72rem;">注册费</div></div>';html += "</div></div>";// ===== 合并卡片：办理方式 + 订单摘要 ===== html += '<div class="checkout-card">';// 办理方式行 if (hasModePicker){html += '<div style="display:flex;justify-content:space-between;align-items:center;padding-bottom:0.75rem;border-bottom:1px solid #f0f0f0;cursor:pointer;" onclick="openServiceModePicker()">';html += '<div><span class="text-muted small">办理方式</span><div style="font-weight:700;margin-top:2px;">' + modeMeta.label + "</div></div>";html += '<span style="color:var(--primary);font-size:0.85rem;">切换 <i class="bi bi-chevron-right"></i></span></div>'}else{html += '<div style="padding-bottom:0.75rem;border-bottom:1px solid #f0f0f0;"><div class="checkout-row" style="margin:0;"><span class="label">办理方式</span><span class="value"><span class="mode-tag"><i class="bi bi-truck"></i> 默认</span></span></div></div>'}// 校区 html += '<div class="checkout-row" style="padding-top:0.75rem;margin-bottom:0;"><span class="label">办理校区</span><span class="value">' + (p.campus_name || currentCampus?.name || "-") + "</span></div>";// ===== 办理流程（精简为步骤条） ===== html += '<div class="checkout-card" style="padding:0.85rem 1rem;">';if (hasSelectUrl){html += '<div style="display:flex;gap:0.5rem;align-items:flex-start;">';html += '<div style="flex:1;text-align:center;"><div style="width:28px;height:28px;border-radius:50%;background:var(--primary);color:#fff;display:inline-flex;align-items:center;justify-content:center;font-size:0.75rem;font-weight:700;">1</div><div style="font-size:0.72rem;color:#374151;margin-top:0.25rem;font-weight:600;">选号</div></div>';html += '<div style="flex:1;text-align:center;"><div style="width:28px;height:28px;border-radius:50%;background:#e5e7eb;color:#6b7280;display:inline-flex;align-items:center;justify-content:center;font-size:0.75rem;font-weight:700;">2</div><div style="font-size:0.72rem;color:#374151;margin-top:0.25rem;font-weight:600;">支付</div></div>';html += '<div style="flex:0 0 auto;padding-top:12px;color:#d1d5db;"><i class="bi bi-chevron-right"></i></div>';html += '<div style="flex:1;text-align:center;"><div style="width:28px;height:28px;border-radius:50%;background:#e5e7eb;color:#6b7280;display:inline-flex;align-items:center;justify-content:center;font-size:0.75rem;font-weight:700;">3</div><div style="font-size:0.72rem;color:#374151;margin-top:0.25rem;font-weight:600;">回填号码</div></div>';html += "</div>"}else{html += '<div style="display:flex;gap:0.5rem;align-items:flex-start;">';html += '<div style="flex:1;text-align:center;"><div style="width:28px;height:28px;border-radius:50%;background:var(--primary);color:#fff;display:inline-flex;align-items:center;justify-content:center;font-size:0.75rem;font-weight:700;">1</div><div style="font-size:0.72rem;color:#374151;margin-top:0.25rem;font-weight:600;">创建订单</div></div>';html += '<div style="flex:0 0 auto;padding-top:12px;color:#d1d5db;"><i class="bi bi-chevron-right"></i></div>';html += '<div style="flex:1;text-align:center;"><div style="width:28px;height:28px;border-radius:50%;background:#e5e7eb;color:#6b7280;display:inline-flex;align-items:center;justify-content:center;font-size:0.75rem;font-weight:700;">2</div><div style="font-size:0.72rem;color:#374151;margin-top:0.25rem;font-weight:600;">支付</div></div>';html += "</div>"}// ===== 辅助区域：推荐人 + 客服 ===== html += '<div class="checkout-card" style="padding:0.75rem 1rem;">';// 推荐销售 — 已绑定则锁定不可改 html += '<div id="salesBindCard">';var boundSales = currentUser && currentUser.bound_sales;if (boundSales){// 后端已绑定销售，锁死 html += '<div style="display:flex;align-items:center;justify-content:space-between;"><span class="small" style="color:#059669;"><i class="bi bi-lock-fill me-1"></i>专属推荐人：' + (boundSales.real_name || boundSales.nickname || "销售") + "（<code>" + boundSales.invite_code + '</code>）</span><span class="small text-muted"><i class="bi bi-shield-check"></i> 已锁定</span></div>'}else if (currentInvite){// 有邀请码（URL带来或已选择），锁定显示，不可更换 html += '<div style="display:flex;align-items:center;justify-content:space-between;"><span class="small" style="color:#059669;"><i class="bi bi-check-circle-fill me-1"></i>推荐人 <code>' + currentInvite + '</code></span><span class="small text-muted"><i class="bi bi-lock"></i> 已选定</span></div>'}else{// 未绑定且无邀请码，允许选择（选完后锁定） html += '<div id="salesBindInfo" style="display:none"></div>';html += '<div id="salesBindSearch">';html += '<div id="salesBindCollapsed" style="display:flex;align-items:center;justify-content:space-between;cursor:pointer;" onclick="expandSalesSearch()">';html += '<span class="small text-muted">有推荐人的邀请码？</span><a class="small" style="color:var(--primary);">填写 <i class="bi bi-chevron-right"></i></a></div>';html += '<div id="salesBindExpanded" style="display:none;margin-top:0.5rem;">';html += '<div style="display:flex;gap:0.5rem;"><input type="text" class="form-control form-control-sm" id="salesSearchInput" placeholder="邀请码、姓名或ID" autocomplete="off" onkeydown="if(event.key===\'Enter\'){event.preventDefault();searchSales();}" style="border-radius:8px;font-size:0.82rem;"><button class="btn btn-sm btn-outline-primary" onclick="searchSales()" style="white-space:nowrap;border-radius:8px;">查找</button></div>';html += '<div id="salesSearchResults" style="margin-top:0.4rem;"></div>';html += "</div>"}// 客服入口 if (p.contact_qrcode){html += '<div style="border-top:1px solid #f0f0f0;margin-top:0.6rem;padding-top:0.6rem;display:flex;align-items:center;justify-content:space-between;">';html += '<span class="small text-muted">遇到问题？</span>';html += '<a href="javascript:void(0)" onclick="showContact()" class="small" style="color:var(--primary);">联系客服 <i class="bi bi-chevron-right"></i></a>';html += "</div>"}// ===== 底部操作栏 ===== html += '<div class="checkout-submit-bar">';html += '<button class="checkout-submit" id="checkoutSubmitBtn" onclick="submitCheckout()"><span style="font-size:1.05rem;">' + actionText + '</span><span class="sub">总计 ¥' + fee + "</span></button>";if (hasSelectUrl) html += '<button class="btn btn-outline-primary w-100 mt-2" onclick="promptFillPhone(' + p.id + ')" style="border-radius:12px;font-size:0.9rem;">我已选号，填写手机号</button>';html += "</div>";document.getElementById("productDetail").innerHTML = html}async function showProduct(id){var p = await api("/products/" + id);var isSameProduct = currentProduct && Number(currentProduct.id) === Number(p.id);currentProduct = p;var needModePicker = Number(p.enable_service_modes || 0);if (!needModePicker){currentHandlingMode = "delivery"}else if (!isSameProduct){currentHandlingMode = null}// 注意：如果 isSameProduct 且 needModePicker，保留已有的 currentHandlingMode showPage("product",id);renderProductDetail(p);if (needModePicker && !currentHandlingMode){serviceModePromptedProductId = p.id;setTimeout(openServiceModePicker,120)}}function startSelectNumber(productId){pendingProductId = productId;setTimeout(function() {if (pendingProductId === productId) showFillOverlay(productId);},5000);document.addEventListener("visibilitychange",function handler() {if (!document.hidden && pendingProductId === productId) {showFillOverlay(productId); document.removeEventListener("visibilitychange",handler);}})}function showFillOverlay(productId){currentOrderForFill = productId;pendingFillIsOrder = false;document.getElementById("fillOverlay").classList.add("show");var input = document.getElementById("fillPhoneInput");if (!input.value.trim()) input.value = "";setTimeout(function() {input.focus();},300)}function hideFillOverlay(){document.getElementById("fillOverlay").classList.remove("show")}function promptFillPhone(productId){pendingFillIsOrder = false;showFillOverlay(productId)}function submitCheckout(){if (!currentProduct){showToast("产品信息加载中，请稍后","info");return}if (!token){showToast("请先登录后再办理","error");showPage("me");return}if (currentUser && !currentUser.phone){showToast("请先绑定手机号","info");showPhoneVerify();return}// 确定办理方式 var needModePicker = Number(currentProduct.enable_service_modes || 0);var mode;if (needModePicker){if (!currentHandlingMode){showToast("请先选择办理方式","info");openServiceModePicker();return}mode = currentHandlingMode}else{mode = "delivery"}var selectUrl = resolveSelectUrl(currentProduct,mode);if (selectUrl){showSelectConfirm(selectUrl);return}doCreateOrder(mode)}async function doCreateOrder(mode){var btn = document.getElementById("checkoutSubmitBtn");setBtnLoading(btn,true);try{var order = await api("/orders",{method: "POST",body: {product_id: currentProduct.id,invite_code: getInviteCode(),handling_mode: mode}});handlePayment(order)}catch(e){showToast(e.message || "操作失败，请重试","error")}finally{setBtnLoading(btn,false)}}function showSelectConfirm(url){document.getElementById("selectConfirmLink").href = url;document.getElementById("selectConfirmOverlay").classList.add("show")}function closeSelectConfirm(){document.getElementById("selectConfirmOverlay").classList.remove("show")}function onSelectConfirmClick(){// 用户真实点击了 <a> 链接，浏览器会正常跳转 closeSelectConfirm();if (currentProduct) startSelectNumber(currentProduct.id);showToast("选号完成后请务必返回本页面回填手机号！","info")}async function submitFillPhone(){var phone = document.getElementById("fillPhoneInput").value.trim();if (!/^1[3-9]\d{9}$/.test(phone)){showToast("请输入正确的11位手机号","error");return}var btn = document.getElementById("fillSubmitBtn");setBtnLoading(btn,true);try{if (pendingFillIsOrder){await api("/orders/" + currentOrderForFill + "/fill-phone",{method: "POST",body: {phone_number: phone}});hideFillOverlay();pendingFillIsOrder = false;showToast("手机号已提交成功","success");loadUserOrders()}else{var order = await api("/orders",{method: "POST",body: {product_id: currentOrderForFill,invite_code: getInviteCode(),handling_mode: currentHandlingMode || "delivery"}});await api("/orders/" + (order.order_id || order.id) + "/fill-phone",{method: "POST",body: {phone_number: phone}});hideFillOverlay();pendingProductId = null;handlePayment(order)}}catch(e){showToast(e.message || "提交失败，请重试","error")}finally{setBtnLoading(btn,false)}}function handlePayment(order){var orderId = order.order_id || order.id;if (order.paid){showPayResult(true,order);return}if (order.pay_params){showPage("order-detail",orderId);callWechatPay(order.pay_params,orderId);return}showToast("订单已创建，请等待处理","info");showPage("order-detail",orderId)}function callWechatPay(payParams,orderId){if (typeof WeixinJSBridge === "undefined"){showToast("请在微信中打开进行支付","error");return}WeixinJSBridge.invoke("getBrandWCPayRequest",{appId: payParams.appId,timeStamp: payParams.timeStamp,nonceStr: payParams.nonceStr,package: payParams.package,signType: payParams.signType,paySign: payParams.paySign},function(res) {if (res.err_msg === "get_brand_wcpay_request:ok") pollPayStatus(orderId); else if (res.err_msg === "get_brand_wcpay_request:cancel") {showToast("支付已取消","info"); showPage("order-detail",orderId);} else {showToast("支付失败，请重试","error"); showPage("order-detail",orderId);}})}async function pollPayStatus(orderId,maxRetry){maxRetry = maxRetry || 10;for (var i = 0; i < maxRetry; i++){await new Promise(function(r) {setTimeout(r,2000);});try{var result = await api("/orders/" + orderId + "/pay-status");if (result.paid){showPayResult(true,result);return}}catch(e){}}showToast("支付确认中，请稍后在订单详情查看","info");showPage("order-detail",orderId)}function showPayResult(success,order){var content = document.getElementById("payResultContent");if (success){content.innerHTML = '<div class="pay-result"><div class="icon-circle success"><i class="bi bi-check-lg"></i></div><h3 style="color:#16a34a;">支付成功</h3><div class="info">' + (order.order_no ? "订单号：" + order.order_no + "<br>" : "") + (order.product_name ? "套餐：" + order.product_name + "<br>" : "") + (order.amount ? "金额：¥" + Number(order.amount).toFixed(2) : "") + '</div><button class="btn btn-primary btn-lg w-100 mb-2" onclick="showPage(\'orders\')" style="border-radius:10px;"><i class="bi bi-receipt me-1"></i>查看订单</button><button class="btn btn-outline-secondary w-100" onclick="showPage(\'home\')" style="border-radius:10px;">返回首页</button></div>'}else{content.innerHTML = '<div class="pay-result"><div class="icon-circle fail"><i class="bi bi-x-lg"></i></div><h3 style="color:#dc2626;">支付失败</h3><div class="info">支付未完成，您可以重新尝试支付。</div><button class="btn btn-primary btn-lg w-100 mb-2" onclick="showPage(\'orders\')" style="border-radius:10px;">查看订单</button><button class="btn btn-outline-secondary w-100" onclick="showPage(\'home\')" style="border-radius:10px;">返回首页</button></div>'}showPage("pay-result")}async function retryPay(orderId){showGlobalLoading("正在发起支付...");try{var order = await api("/orders/" + orderId + "/pay-retry",{method: "POST"});hideGlobalLoading();handlePayment(order)}catch(e){hideGlobalLoading();showToast(e.message || "获取支付信息失败","error")}}function getInviteCode(){// 已绑定销售的用户，始终返回绑定的邀请码 if (currentUser && currentUser.bound_sales) return currentUser.bound_sales.invite_code || "";var params = new URLSearchParams(window.location.search);return params.get("invite") || params.get("code") || localStorage.getItem("invite_code") || ""}var salesSearchTimer = null; async function searchSales(){var kw = document.getElementById("salesSearchInput")?.value?.trim();if (!kw){showToast("请输入邀请码、姓名或ID","info");return}var container = document.getElementById("salesSearchResults");container.innerHTML = '<div class="text-muted small">搜索中...</div>';try{var list = await api("/sales/search?keyword=" + encodeURIComponent(kw));if (!list || !list.length){container.innerHTML = '<div class="text-muted small">未找到匹配的销售</div>';return}container.innerHTML = list.map(function(s) {return '<div style="display:flex;align-items:center;justify-content:space-between;padding:0.5rem;background:#f8f9fa;border-radius:8px;margin-bottom:0.25rem;">' + '<span class="small"><i class="bi bi-person me-1"></i>' + s.name + ' <code class="ms-1">' + s.code + "</code></span>" + '<button class="btn btn-sm btn-primary" onclick="bindSales(\'' + s.code + "','" + s.name.replace(/'/g, "\\'") + '')">选择</button></div>';
}).join("");} catch(e) {container.innerHTML = '<div class="text-danger small">搜索失败</div>';} } function bindSales(code,name) {localStorage.setItem("invite_code",code); // 选定后锁定，直接重新渲染整个产品详情（会走 currentInvite 逻辑显示锁定状态） showToast("已选定推荐人: " + name,"success"); if (currentProduct) renderProductDetail(currentProduct);} // ==================== 销售申请 ==================== async function checkSalesRegistration() {if (!token || !currentUser || currentUser.is_sales) return; try {var status = await api("/sales/registration-status"); if (status.open) {// 检查是否已申请过 var app = await api("/sales/my-application"); if (!app || app.status === "rejected") {document.getElementById("salesApplyHint").textContent = "当前正在招募销售成员，填写信息即可申请加入"; document.getElementById("salesApplyOverlay").classList.add("show");}}} catch(e) {}} function openSalesApply() {if (!token) {showToast("请先登录","info"); return;} api("/sales/my-application").then(function(app) {if (app && app.status === "pending") {showToast("您的申请正在审核中，请耐心等待","info"); return;} if (currentUser && currentUser.is_sales) {showToast("您已经是销售成员","info"); return;} document.getElementById("applyRealName").value = currentUser?.real_name || currentUser?.nickname || ""; document.getElementById("applyParentCode").value = getInviteCode(); document.getElementById("salesApplyHint").textContent = "填写信息提交申请，审核通过后即可成为销售"; // 加载学校列表 loadApplySchools(); document.getElementById("salesApplyOverlay").classList.add("show");}).catch(function() {loadApplySchools(); document.getElementById("salesApplyOverlay").classList.add("show");});} async function loadApplySchools() {var sel = document.getElementById("applySchool"); sel.innerHTML = '<option value="">加载中...</option>'; try {var schools = await api("/schools"); sel.innerHTML = '<option value="">请选择学校</option>' + schools.map(function(s) {return '<option value="' + s.id + '">' + s.name + "</option>";}).join("");} catch(e) {sel.innerHTML = '<option value="">加载失败</option>';} document.getElementById("applyCampus").innerHTML = '<option value="">请先选择学校</option>';} async function onApplySchoolChange(schoolId) {var sel = document.getElementById("applyCampus"); if (!schoolId) {sel.innerHTML = '<option value="">请先选择学校</option>'; return;} sel.innerHTML = '<option value="">加载中...</option>'; try {var campuses = await api("/schools/" + schoolId + "/campuses"); if (campuses.length === 0) {sel.innerHTML = '<option value="">该学校暂无校区</option>';} else if (campuses.length === 1) {sel.innerHTML = '<option value="' + campuses[0].id + '">' + campuses[0].name + "</option>";} else {sel.innerHTML = '<option value="">请选择校区</option>' + campuses.map(function(c) {return '<option value="' + c.id + '">' + c.name + "</option>";}).join("");}} catch(e) {sel.innerHTML = '<option value="">加载失败</option>';}} function closeSalesApply() {document.getElementById("salesApplyOverlay").classList.remove("show");} async function submitSalesApply() {var realName = document.getElementById("applyRealName").value.trim(); var schoolId = document.getElementById("applySchool").value; var campusId = document.getElementById("applyCampus").value; var parentCode = document.getElementById("applyParentCode").value.trim(); if (!realName) {showToast("请填写真实姓名","error"); return;} if (realName.length < 2) {showToast("姓名至少2个字","error"); return;} if (!schoolId) {showToast("请选择学校","error"); return;} if (!campusId) {showToast("请选择校区","error"); return;} var btn = document.getElementById("salesApplyBtn"); btn.disabled = true; btn.textContent = "提交中..."; try {await api("/sales/apply",{method: "POST",body: {real_name: realName,school_id: parseInt(schoolId),campus_id: parseInt(campusId),parent_code: parentCode}}); closeSalesApply(); showToast("申请已提交，请等待审核","success"); updateJoinTeamBtn();} catch(e) {showToast(e.message || "提交失败","error");} finally {btn.disabled = false; btn.textContent = "提交申请";}} async function updateJoinTeamBtn() {var btn = document.getElementById("joinTeamBtn"); if (!btn || !currentUser) return; if (currentUser.is_sales) {btn.style.display = "none"; return;} btn.style.display = ""; try {var app = await api("/sales/my-application"); var statusEl = document.getElementById("joinTeamStatus"); if (app && app.status === "pending") {statusEl.innerHTML = '<span class="badge bg-warning text-dark">审核中</span>';} else if (app && app.status === "rejected") {statusEl.innerHTML = '<span class="badge bg-danger">被拒绝</span>';} else {statusEl.innerHTML = '<i class="bi bi-chevron-right"></i>';}} catch(e) {}} function expandSalesSearch() {var c = document.getElementById("salesBindCollapsed"); var e = document.getElementById("salesBindExpanded"); if (c) c.style.display = "none"; if (e) {e.style.display = ""; setTimeout(function() {var input = document.getElementById("salesSearchInput"); if (input) input.focus();},100);}} function showFillForOrder(orderId,productId,existingPhone) {currentOrderForFill = orderId; pendingFillIsOrder = true; document.getElementById("fillOverlay").classList.add("show"); document.getElementById("fillPhoneInput").value = existingPhone || ""; setTimeout(function() {document.getElementById("fillPhoneInput").focus();},300);} // ======================== Orders ======================== async function loadOrderDetail(orderId) {if (!token) {showPage("me"); return;} var box = document.getElementById("orderDetailContent"); box.innerHTML = '<div class="checkout-card text-center text-muted">订单加载中...</div>'; try {var o = await api("/orders/" + orderId); var sm = {pending:["待支付","warning"],paid:["已支付","info"],confirmed:["已完成","success"],cancelled:["已取消","secondary"],refunded:["已退款","dark"]}; var s = sm[o.status] || [o.status,"secondary"]; var hm = o.handling_mode === "offline" ? "线下办理" : "邮寄办理"; var canRetry = o.status === "pending"; var canSelectNumber = !o.phone_filled && o.status !== "confirmed" && o.status !== "cancelled" && !!o.select_number_url; var canFill = !o.phone_filled && o.status !== "pending" && o.status !== "cancelled"; var canEditFilledBeforePay = o.status === "pending" && !!o.phone_filled; var html = ""; html += '<div class="checkout-card"><div class="d-flex justify-content-between align-items-start"><div><div class="title mb-2">订单状态</div><div class="product">' + (o.product_name||"-") + '</div><div class="text-muted small mt-1">订单号：' + (o.order_no||"-") + '</div></div><span class="badge bg-' + s[1] + '" style="font-size:0.85rem;">' + s[0] + "</span></div></div>"; html += '<div class="checkout-card"><div class="title">订单信息</div><div class="checkout-row"><span class="label">套餐名称</span><span class="value">' + (o.product_name||"-") + '</span></div><div class="checkout-row"><span class="label">办理方式</span><span class="value">' + hm + '</span></div><div class="checkout-row"><span class="label">办理校区</span><span class="value">' + (o.campus_name||"-") + '</span></div><div class="checkout-row"><span class="label">订单金额</span><span class="value" style="color:#dc2626;">¥' + Number(o.amount||0).toFixed(2) + '</span></div><div class="checkout-row"><span class="label">创建时间</span><span class="value">' + (o.created_at||"-") + "</span></div></div>"; html += '<div class="checkout-card"><div class="title">办理进度</div>'; if (o.status === "pending" && canEditFilledBeforePay) html += '<div class="alert alert-warning mb-0"><i class="bi bi-credit-card me-1"></i>订单待支付。请确认下方手机号无误，支付前仍可修改。</div>'; else if (o.status === "pending") html += '<div class="alert alert-warning mb-0"><i class="bi bi-credit-card me-1"></i>订单已创建，等待支付。支付完成后再去选号或回填手机号。</div>'; else if (o.phone_filled) html += '<div class="alert alert-success mb-0"><i class="bi bi-check-circle me-1"></i>已完成办理并回填手机号：' + (o.phone_number||"-") + "</div>"; else if (canSelectNumber) html += '<div class="alert alert-info mb-0"><i class="bi bi-phone me-1"></i>您还未完成选号/回填。请先去选号，完成后回来填写办理手机号。</div>'; else html += '<div class="alert alert-info mb-0"><i class="bi bi-pencil-square me-1"></i>请尽快填写办理手机号，方便后续跟进。</div>'; html += "</div>"; if (o.phone_filled) html += '<div class="checkout-card"><div class="title">办理手机号</div><div class="checkout-row"><span class="label">当前填写号码</span><span class="value">' + (o.phone_number||"-") + "</span></div>" + (canEditFilledBeforePay ? '<button class="btn btn-outline-primary w-100 mt-2" onclick="showFillForOrder(' + o.id + "," + o.product_id + ",'" + (o.phone_number||"") + '\')" style="border-radius:12px;">修改办理手机号</button>' : "") + "</div>"; if (canSelectNumber) html += '<div class="checkout-card"><div class="title">选号入口</div><a class="btn btn-outline-primary w-100" href="' + o.select_number_url + '" target="_blank" onclick="startSelectNumber(' + o.product_id + ')" style="border-radius:12px;"><i class="bi bi-phone me-1"></i>去选号</a><div class="text-muted small mt-2">选号完成后，请返回本页回填办理手机号。</div></div>'; if (o.contact_qrcode) html += '<div class="checkout-card text-center"><div class="title text-start">客服协助</div><img src="' + o.contact_qrcode + '" style="max-width:150px;border-radius:12px;"></div>'; html += '<div class="checkout-submit-bar">'; if (canRetry) html += '<button class="checkout-submit" onclick="retryPay(' + o.id + ')">立即支付<span class="sub">总计 ¥' + Number(o.amount||0).toFixed(2) + "</span></button>"; if (canFill) html += '<button class="btn btn-outline-primary w-100 mt-2" onclick="showFillForOrder(' + o.id + "," + o.product_id + ",'" + (o.phone_number||"") + '\')" style="border-radius:12px;">填写办理手机号</button>'; html += "</div>"; box.innerHTML = html;} catch(e) {box.innerHTML = '<div class="checkout-card text-center text-danger">' + (e.message || "订单加载失败") + "</div>";}} async function loadUserOrders() {if (!token) {document.getElementById("orderList").innerHTML = '<div class="empty-guide"><div class="icon-wrap"><i class="bi bi-person-lock"></i></div><h4>还未登录</h4><p>登录后可查看您的订单状态<br>跟踪办卡进度，及时回填手机号</p><button class="guide-btn" onclick="showPage(\'home\')"><i class="bi bi-house me-1"></i>去首页看看</button></div>'; return;} try {var data = await api("/orders"); var list = data.list || []; var statusMap = {pending: ["待支付","warning"],paid: ["已支付","info"],confirmed: ["已完成","success"],cancelled: ["已取消","secondary"]}; document.getElementById("orderList").innerHTML = list.length ? list.map(function(o) {var s = statusMap[o.status] || [o.status,"secondary"]; var sText = s[0],sColor = s[1]; var actionHtml = ""; if (o.status === "pending") {actionHtml = '<button class="btn btn-sm btn-primary" onclick="retryPay(' + o.id + ')"><i class="bi bi-credit-card me-1"></i>去支付</button>';} else if (o.status === "paid") {if (!o.phone_filled) {actionHtml = '<span class="text-success small me-2"><i class="bi bi-check-circle"></i> 已支付</span><button class="btn btn-sm btn-outline-danger" onclick="showFillForOrder(' + o.id + ",'" + (o.product_id||"") + "','" + (o.phone_number||"") + "')\">回填手机号</button>";} else {actionHtml = '<span class="text-success small"><i class="bi bi-check-circle"></i> 已支付 · ' + o.phone_number + "</span>";}} else if (o.status === "confirmed") {actionHtml = '<span class="text-success small"><i class="bi bi-check-circle-fill"></i> 已完成</span>';} else {if (!o.phone_filled) {actionHtml = '<button class="btn btn-sm btn-danger" onclick="showFillForOrder(' + o.id + ",'" + (o.product_id||"") + "','" + (o.phone_number||"") + "')\">回填手机号</button>";} else {actionHtml = '<span class="text-success small"><i class="bi bi-check-circle"></i> ' + o.phone_number + "</span>";}} var hm = o.handling_mode === "offline" ? "线下办理" : "邮寄办理"; return '<div class="order-card" onclick="showPage(\'order-detail\',' + o.id + ')"><div class="d-flex justify-content-between"><span class="order-no">' + o.order_no + '</span><span class="status badge bg-' + sColor + '">' + sText + '</span></div><div class="product">' + (o.product_name||"-") + '</div><div class="text-muted small mb-2">办理方式：' + hm + '</div><div class="d-flex justify-content-between align-items-center"><span class="text-danger fw-bold">¥' + o.amount + '</span><div onclick="event.stopPropagation()">' + actionHtml + "</div></div></div>";}).join("") : '<div class="empty-guide"><div class="icon-wrap"><i class="bi bi-receipt"></i></div><h4>暂无订单</h4><p>还没有办理记录<br>去首页选择心仪的套餐开始办理吧</p><button class="guide-btn" onclick="showPage(\'home\')"><i class="bi bi-phone me-1"></i>选套餐办卡</button></div>';} catch(e) {}} // ======================== User Profile ======================== async function loadUserProfile() {var wxPanel = document.getElementById("wxLoginPanel"); var logoutBtn = document.getElementById("logoutBtn"); var salesPanel = document.getElementById("salesPanel"); if (!token) {wxPanel.style.display = "block"; logoutBtn.style.display = "none"; salesPanel.style.display = "none"; document.getElementById("userNickname").textContent = "未登录"; document.getElementById("userPhone").textContent = ""; updateBottomNav(); return;} wxPanel.style.display = "none"; logoutBtn.style.display = "flex"; try {var user = await api("/auth/me"); currentUser = user; document.getElementById("userNickname").textContent = user.nickname || "用户"; document.getElementById("userPhone").textContent = user.phone || "未绑定手机"; document.getElementById("userPhone").innerHTML = user.phone ? user.phone : '<span style="color:#ea580c;cursor:pointer;" onclick="showPhoneVerify()"><i class="bi bi-exclamation-circle me-1"></i>未绑定手机号，点击绑定</span>'; updateBottomNav(); if (user.is_sales) {salesPanel.style.display = "block"; document.getElementById("myInviteCode").textContent = user.invite_code; try {var dash = await api("/sales/dashboard"); document.getElementById("salesStats").innerHTML = [{label: "直属用户",value: dash.stats.direct_users},{label: "总佣金",value: "¥" + Number(dash.stats.total_commission).toFixed(2)},{label: "本月订单",value: dash.stats.month_orders},].map(function(s) {return '<div class="col-4"><div class="fw-bold">' + s.value + '</div><div class="small text-muted">' + s.label + "</div></div>";}).join("");} catch(e) {}} else {salesPanel.style.display = "none"; updateJoinTeamBtn();}} catch(e) {token = null; localStorage.removeItem("user_token"); currentUser = null; wxPanel.style.display = "block"; logoutBtn.style.display = "none"; document.getElementById("userNickname").textContent = "未登录"; updateBottomNav();}} // ======================== Settings ======================== function loadSettings() {if (!currentUser) {showPage("me"); return;} var html = ""; html += '<div class="settings-item" onclick="showPage(\'edit-nickname\')"><span class="label">昵称</span><span class="value">' + (currentUser.nickname || "未设置") + ' <i class="bi bi-chevron-right"></i></span></div>'; html += '<div class="settings-item" onclick="showPhoneVerify()"><span class="label">手机号</span><span class="value">' + (currentUser.phone || "<span style=color:#ea580c>未绑定</span>") + ' <i class="bi bi-chevron-right"></i></span></div>'; html += '<div class="settings-item"><span class="label">用户ID</span><span class="value">' + currentUser.id + "</span></div>"; html += '<div class="settings-item"><span class="label">邀请码</span><span class="value">' + (currentUser.invite_code || "-") + "</span></div>"; document.getElementById("settingsContent").innerHTML = html;} function loadEditNickname() {if (!currentUser) {showPage("me"); return;} document.getElementById("editNicknameInput").value = currentUser.nickname || "";} async function saveNickname() {var nickname = document.getElementById("editNicknameInput").value.trim(); if (!nickname) {showToast("昵称不能为空","error"); return;} var btn = document.getElementById("saveNicknameBtn"); setBtnLoading(btn,true); try {var user = await api("/auth/update-profile",{method: "POST",body: {nickname: nickname}}); currentUser.nickname = user.nickname; showToast("昵称修改成功","success"); showPage("settings");} catch(e) {showToast(e.message || "修改失败","error");} finally {setBtnLoading(btn,false);}} function copyInviteCode() {var code = document.getElementById("myInviteCode").textContent; var url = location.origin + location.pathname + "?invite=" + code; if (navigator.clipboard) {navigator.clipboard.writeText(url).then(function() {showToast("邀请链接已复制","success");});} else {showToast("复制失败，请手动复制","error");}} function showContact() {if (currentCampus && currentCampus.contact_qrcode) {var overlay = document.createElement("div"); overlay.style.cssText = "position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.6);z-index:9999;display:flex;align-items:center;justify-content:center;animation:fadeIn 0.2s ease;"; overlay.onclick = function(e) {if (e.target === overlay) overlay.remove();}; overlay.innerHTML = '<div style="background:#fff;border-radius:16px;padding:2rem 1.5rem;text-align:center;max-width:320px;width:90%;box-shadow:0 8px 32px rgba(0,0,0,0.2);animation:slideUp 0.3s ease;">' + '<div style="font-size:1.1rem;font-weight:600;margin-bottom:0.5rem;">扫码联系客服</div>' + '<p style="color:#6c757d;font-size:0.85rem;margin-bottom:1rem;">长按识别二维码添加客服</p>' + '<img src="' + currentCampus.contact_qrcode + '" style="width:200px;height:200px;object-fit:contain;border-radius:12px;border:1px solid #eee;">' + '<div style="margin-top:1.2rem;"><button onclick="this.closest(\'div[style*=fixed]\').remove()" style="background:#f0f0f0;border:none;border-radius:8px;padding:0.5rem 2rem;font-size:0.95rem;color:#333;">关闭</button></div>' + "</div>"; document.body.appendChild(overlay);} else {showToast("请联系您所在校区的客服","info");}} function startWechatLogin() {var appId = window.__wxAppId || ""; if (!appId) {showToast("请在微信中打开本页面","info"); return;} showGlobalLoading("正在跳转微信授权..."); var redirectUri = encodeURIComponent(location.origin + location.pathname + "?t=" + tenantSlug); var url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appId + "&redirect_uri=" + redirectUri + "&response_type=code&scope=snsapi_userinfo&state=login#wechat_redirect"; location.href = url;} // 登录并保存返回页面 function doLoginWithReturn() {// 保存当前 hash，登录成功后返回 sessionStorage.setItem("loginReturnPage",location.hash || "#/"); startWechatLogin();} function doLogout() {token = null; currentUser = null; localStorage.removeItem("user_token"); updateBottomNav(); showToast("已退出登录","info"); showPage("home");} function checkWechatAuth() {var params = new URLSearchParams(window.location.search); var invite = params.get("invite"); if (invite) localStorage.setItem("invite_code",invite); if (token) return; // 微信授权回调带 code 参数，state=login 表示登录 var wxCode = params.get("code"); var state = params.get("state"); if (wxCode && state === "login") {showGlobalLoading("正在登录中..."); api("/auth/wechat/login",{method: "POST",body: {code: wxCode,invite_code: getInviteCode()}}).then(function(data) {token = data.token; localStorage.setItem("user_token",token); currentUser = data.user; updateBottomNav(); hideGlobalLoading(); showToast("登录成功！欢迎 " + (data.user.nickname || ""),"success"); // 清理 URL 参数 var cleanUrl = location.origin + location.pathname + "?t=" + tenantSlug + location.hash; window.history.replaceState({},"",cleanUrl); // 如果没有手机号，弹出验证 if (data.user.need_complete || !data.user.phone) {setTimeout(showPhoneVerify,500);} else if (!data.user.is_sales) {// 检查是否在销售注册窗口期 setTimeout(checkSalesRegistration,800);} // 如果有保存的返回页面，跳转回去 var returnPage = sessionStorage.getItem("loginReturnPage"); if (returnPage) {sessionStorage.removeItem("loginReturnPage"); setTimeout(function() {navigateTo(returnPage);},300);}}).catch(function(e) {hideGlobalLoading(); showToast("微信登录失败：" + (e.message || "请重试"),"error");});}} function showWelcomeSkeleton() {if (!token) return; var sk = document.getElementById("welcomeSkeleton"); sk.style.display = "flex"; setTimeout(function() {sk.classList.add("fade-out"); setTimeout(function() {sk.style.display = "none"; sk.classList.remove("fade-out");},500);},800);} </script> <script> // ======================== Sales Center ======================== var salesCommFilter = "all"; async function loadSalesDashboard() {if (!currentUser || !currentUser.is_sales) {showPage("home"); return;} var container = document.getElementById("salesDashContent"); container.innerHTML = '<div class="sales-header"><div class="greeting">👋 ' + (currentUser.nickname || "销售员") + '</div><div class="meta" id="salesTierMeta">邀请码：' + (currentUser.invite_code || "-") + '</div></div><div class="sales-stat-grid" id="salesDashStats"><div class="sales-stat-card"><div class="val">-</div><div class="lbl">加载中</div></div></div><div id="salesTierProgress"></div><div class="section"><div class="section-title">快捷入口</div></div><div class="sales-quick-grid"><div class="sales-quick-item" onclick="showPage(\'sales-customers\')"><i class="bi bi-person-lines-fill"></i><span>我的客户</span></div><div class="sales-quick-item" onclick="showPage(\'sales-team\')"><i class="bi bi-people"></i><span>我的团队</span></div><div class="sales-quick-item" onclick="showPage(\'sales-commissions\')"><i class="bi bi-cash-stack"></i><span>佣金明细</span></div><div class="sales-quick-item" onclick="showPage(\'sales-share\')"><i class="bi bi-box-arrow-up-right"></i><span>分享商品</span></div><div class="sales-quick-item" onclick="showPage(\'sales-posters\')"><i class="bi bi-image"></i><span>海报生成</span></div><div class="sales-quick-item" onclick="showPage(\'sales-invite\')"><i class="bi bi-share"></i><span>邀请推广</span></div></div>'; try {// 同时加载面板数据和梯度信息 var results = await Promise.all([api("/sales/dashboard"),api("/sales/tier-info")]); var dash = results[0],tierInfo = results[1]; var s = dash.stats; // 梯度信息 var tierName = tierInfo.current_tier ? tierInfo.current_tier.name : "未达标"; document.getElementById("salesTierMeta").innerHTML = "邀请码：" + (currentUser.invite_code || "-") + " &nbsp;|&nbsp; 当前梯度：<b>" + tierName + "</b>"; document.getElementById("salesDashStats").innerHTML = [{val: tierInfo.total_sales || 0,lbl: "累计销量",cls: ""},{val: tierName,lbl: "当前梯度",cls: ""},{val: "¥" + Number(s.total_commission || 0).toFixed(2),lbl: "总佣金",cls: "green"},{val: "¥" + Number(s.pending_commission || 0).toFixed(2),lbl: "待结算",cls: "orange"},].map(function(item) {return '<div class="sales-stat-card"><div class="val ' + item.cls + '">' + item.val + '</div><div class="lbl">' + item.lbl + "</div></div>";}).join(""); // 晋级进度 if (tierInfo.next_tier) {var progress = tierInfo.current_tier && tierInfo.current_tier.max_sales ? Math.min(100,Math.round((tierInfo.total_sales - tierInfo.current_tier.min_sales + 1) / (tierInfo.current_tier.max_sales - tierInfo.current_tier.min_sales + 1) * 100)) : 100; document.getElementById("salesTierProgress").innerHTML = '<div style="padding:0 1rem;margin-bottom:1rem;"><div style="background:#fff;border-radius:12px;padding:1rem;box-shadow:0 1px 4px rgba(0,0,0,0.06);"><div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.5rem;font-size:0.85rem;"><span>距离 <b>' + tierInfo.next_tier.name + '</b></span><span>还差 <b style="color:var(--primary)">' + tierInfo.next_tier.remaining + '</b> 单</span></div><div style="background:#e5e7eb;border-radius:99px;height:8px;overflow:hidden;"><div style="background:linear-gradient(90deg,var(--primary),#7c3aed);height:100%;width:' + progress + '%;border-radius:99px;transition:width 0.5s;"></div></div></div></div>';} else {document.getElementById("salesTierProgress").innerHTML = '<div style="padding:0 1rem;margin-bottom:1rem;"><div style="background:#fff;border-radius:12px;padding:1rem;box-shadow:0 1px 4px rgba(0,0,0,0.06);text-align:center;color:#059669;"><i class="bi bi-trophy-fill me-1"></i> 已达到最高梯度！</div></div>';}} catch(e) {document.getElementById("salesDashStats").innerHTML = '<div class="sales-stat-card"><div class="val">-</div><div class="lbl">加载失败</div></div>';}} // 我的客户 async function loadSalesCustomers() {var container = document.getElementById("salesCustomersContent"); container.innerHTML = '<div class="px-3 py-2 text-muted small">加载中...</div>'; try {var data = await api("/sales/users"); var list = Array.isArray(data) ? data : (data.list || []); if (!list.length) {container.innerHTML = '<div class="empty-guide"><div class="icon-wrap"><i class="bi bi-person-lines-fill"></i></div><h4>暂无客户</h4><p>分享邀请码，用户通过你的链接注册即成为你的客户</p><button class="guide-btn" onclick="showPage(\'sales-invite\')"><i class="bi bi-share me-1"></i>去邀请</button></div>'; return;} container.innerHTML = '<div class="px-3 py-2"><small class="text-muted">共 ' + list.length + " 位客户</small></div>" + list.map(function(u) {var initial = (u.real_name || u.nickname || u.phone || "?")[0]; var orderInfo = Number(u.order_count) > 0 ? '<span style="color:#059669;">' + u.order_count + "单 · ¥" + Number(u.total_spent || 0).toFixed(0) + "</span>" : '<span style="color:#999;">暂未购买</span>'; return '<div class="sales-member-card"><div class="avatar">' + initial + '</div><div class="info"><div class="name">' + (u.real_name || u.nickname || "用户") + (u.is_sales ? ' <span class="badge bg-primary" style="font-size:0.65rem;">销售</span>' : "") + '</div><div class="meta">' + (u.phone || "未绑定手机") + " · " + orderInfo + "</div></div></div>";}).join("");} catch(e) {container.innerHTML = '<div class="empty-state"><i class="bi bi-exclamation-circle"></i><p>加载失败，请重试</p></div>';}} // 我的团队（按销售额排序） async function loadSalesTeam() {var container = document.getElementById("salesTeamContent"); container.innerHTML = '<div class="px-3 py-2 text-muted small">加载中...</div>'; try {var data = await api("/sales/team"); var list = Array.isArray(data) ? data : (data.list || []); if (!list.length) {container.innerHTML = '<div class="empty-guide"><div class="icon-wrap"><i class="bi bi-people"></i></div><h4>暂无团队成员</h4><p>邀请更多销售加入你的团队</p><button class="guide-btn" onclick="showPage(\'sales-invite\')"><i class="bi bi-share me-1"></i>去邀请</button></div>'; return;} container.innerHTML = '<div class="px-3 py-2"><small class="text-muted">共 ' + list.length + " 位团队成员（按销售额排序）</small></div>" + list.map(function(u) {var initial = (u.real_name || u.nickname || u.phone || "?")[0]; var salesAmt = Number(u.sales_amount || 0); return '<div class="sales-member-card"><div class="avatar">' + initial + '</div><div class="info"><div class="name">' + (u.real_name || u.nickname || "销售") + ' <span class="small text-muted">' + (u.current_tier_name || "") + '</span></div><div class="meta">销售额 ¥' + salesAmt.toFixed(0) + " · " + (u.order_count || 0) + "单 · " + (u.customer_count || 0) + '客户</div></div><div style="text-align:right;"><div style="font-weight:700;color:var(--primary);">¥' + Number(u.total_commission || 0).toFixed(0) + '</div><div class="small text-muted">佣金</div></div></div>';}).join("");} catch(e) {container.innerHTML = '<div class="empty-state"><i class="bi bi-exclamation-circle"></i><p>加载失败，请重试</p></div>';}} async function loadSalesCommissions() {var container = document.getElementById("salesCommContent"); container.innerHTML = '<div class="comm-filter-bar"><div class="chip active" onclick="setSalesCommFilter(this,\'all\')">全部</div><div class="chip" onclick="setSalesCommFilter(this,\'pending\')">待确认</div><div class="chip" onclick="setSalesCommFilter(this,\'confirmed\')">已确认</div><div class="chip" onclick="setSalesCommFilter(this,\'settled\')">已结算</div></div><div id="salesCommList"><div class="px-3 text-muted small">加载中...</div></div>'; _fetchSalesCommissions();} function setSalesCommFilter(el,filter) {salesCommFilter = filter; document.querySelectorAll(".comm-filter-bar .chip").forEach(function(c) {c.classList.remove("active");}); el.classList.add("active"); _fetchSalesCommissions();} async function _fetchSalesCommissions() {var listEl = document.getElementById("salesCommList"); if (!listEl) return; listEl.innerHTML = '<div class="px-3 text-muted small">加载中...</div>'; try {var params = salesCommFilter !== "all" ? "?status=" + salesCommFilter : ""; var data = await api("/sales/commissions" + params); var list = data.list || (Array.isArray(data) ? data : []); var summary = data.summary || {}; var statusMap = {pending: ["待确认","orange"],confirmed: ["已确认","green"],settled: ["已结算","green"],cancelled: ["已取消",""]}; if (!list.length) {listEl.innerHTML = '<div class="empty-guide"><div class="icon-wrap"><i class="bi bi-cash-stack"></i></div><h4>暂无佣金记录</h4><p>推广用户办理套餐即可获得佣金</p></div>'; return;} var html = '<div class="px-3 pb-2"><div class="d-flex justify-content-between text-sm"><span class="text-muted">合计佣金</span><span class="fw-bold" style="color:#059669;">¥' + Number(summary.total || 0).toFixed(2) + '</span></div><div class="d-flex justify-content-between text-sm"><span class="text-muted">已结算</span><span>¥' + Number(summary.settled || 0).toFixed(2) + '</span></div><div class="d-flex justify-content-between text-sm"><span class="text-muted">待确认</span><span style="color:#d97706;">¥' + Number(summary.pending || 0).toFixed(2) + "</span></div></div>"; html += list.map(function(c) {var sm = statusMap[c.status] || [c.status,""]; var modeLabel = c.handling_mode === "offline" ? "线下" : "线上"; return '<div class="comm-card"><div class="top"><span class="detail">' + (c.order_no || "-") + '</span><span class="badge ' + (sm[1]==="green"?"bg-success text-white":"bg-warning text-dark") + '" style="font-size:0.75rem;">' + sm[0] + '</span></div><div class="d-flex justify-content-between align-items-center"><div><div class="amount ' + sm[1] + '">¥' + Number(c.amount || 0).toFixed(2) + '</div><div class="detail">' + (c.product_name || "-") + " · " + modeLabel + '</div><div class="detail" style="font-size:0.75rem;color:#999;">梯度：' + (c.tier_name || "-") + " · 当时累计" + (c.sales_count || 0) + '单</div></div><div class="text-end"><div class="detail">' + (c.created_at || "").substring(0,10) + "</div></div></div></div>";}).join(""); listEl.innerHTML = html;} catch(e) {listEl.innerHTML = '<div class="empty-state"><i class="bi bi-exclamation-circle"></i><p>加载失败，请重试</p></div>';}} async function loadSalesInvite() {if (!currentUser) return; var container = document.getElementById("salesInviteContent"); var code = currentUser.invite_code || ""; var url = location.origin + location.pathname + "?invite=" + code; container.innerHTML = '<div class="invite-section"><div class="invite-code-box"><div class="label">我的邀请码</div><div class="code">' + code + '</div><button class="btn btn-primary" onclick="copyText(\'' + code + '\')"><i class="bi bi-clipboard me-1"></i>复制邀请码</button></div><div class="invite-link-box"><p class="fw-bold mb-2">邀请链接</p><div class="link-text" id="inviteLinkText">' + url + '</div><button class="btn btn-outline-primary w-100" onclick="copyText(document.getElementById(\'inviteLinkText\').textContent)"><i class="bi bi-link-45deg me-1"></i>复制链接</button></div><div class="qrcode-box"><p class="fw-bold mb-3">邀请二维码</p><div id="qrcodeContainer"><div class="text-muted small">二维码生成中...</div></div><p class="text-muted small mt-2">截图保存，分享给朋友扫码注册</p></div><div class="mt-3"><button class="btn btn-primary w-100" onclick="showSalesPosterPicker()"><i class="bi bi-image me-1"></i>生成个人海报</button></div><div class="mt-3 p-3" style="background:#fff;border-radius:12px;box-shadow:0 1px 4px rgba(0,0,0,0.06);"><p class="fw-bold mb-1">推广说明</p><p class="text-muted small mb-0">通过您的邀请码注册并办理套餐的用户，您将获得相应佣金。佣金比例以产品设置为准。</p></div></div>'; // 生成二维码（用 Google Charts API 或 canvas） var qrcEl = document.getElementById("qrcodeContainer"); var qrUrl = "https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=" + encodeURIComponent(url); qrcEl.innerHTML = '<img src="' + qrUrl + '" width="200" height="200" style="border-radius:8px;" onerror="this.parentElement.innerHTML=\'<div class=text-muted>二维码加载失败</div>\'">';} function copyText(text) {if (navigator.clipboard) {navigator.clipboard.writeText(text).then(function() {showToast("已复制","success");});} else {try {var el = document.createElement("textarea"); el.value = text; document.body.appendChild(el); el.select(); document.execCommand("copy"); el.remove(); showToast("已复制","success");} catch(e) {showToast("复制失败","error");}}} // 海报生成 async function loadSalesPosters() {var container = document.getElementById("salesPostersContent"); container.innerHTML = '<div class="px-3 py-2 text-muted small">加载中...</div>'; try {// 加载商品海报和销售海报 var [products,salesPosters] = await Promise.all([api("/products"),api("/posters/sales")]); var html = ""; // 商品海报 if (products && products.length) {html += '<div class="section"><div class="section-title">商品海报</div></div>'; html += '<div class="px-3">' + products.map(function(p) {return '<div class="checkout-card mb-2" style="padding:1rem;">' + '<div class="d-flex justify-content-between align-items-center">' + '<div><div style="font-weight:700;">' + p.name + "</div>" + '<div class="small text-muted">' + (p.campus_name || "") + "</div></div>" + '<button class="btn btn-sm btn-primary" onclick="showProductPosterPicker(' + p.id + ')">生成海报</button>' + "</div></div>";}).join("") + "</div>";} // 销售个人海报 if (salesPosters && salesPosters.length) {html += '<div class="section mt-3"><div class="section-title">个人推广海报</div></div>'; html += '<div class="px-3">' + salesPosters.map(function(p) {return '<div class="mb-3" style="border:1px solid #e5e7eb;border-radius:12px;overflow:hidden;background:#fff;">' + '<img src="' + p.image_url + '" style="width:100%;height:160px;object-fit:cover;">' + '<div style="padding:0.75rem;display:flex;justify-content:space-between;align-items:center;">' + '<span style="font-weight:600;">' + p.name + "</span>" + '<button class="btn btn-sm btn-primary" onclick="generatePoster(' + p.id + ",'sales')\">生成</button>" + "</div></div>";}).join("") + "</div>";} if (!html) html = '<div class="empty-guide"><div class="icon-wrap"><i class="bi bi-image"></i></div><h4>暂无海报模板</h4><p>请联系管理员配置海报</p></div>'; container.innerHTML = html;} catch(e) {container.innerHTML = '<div class="empty-state"><i class="bi bi-exclamation-circle"></i><p>加载失败</p></div>';}} var currentPosterData = null; async function showProductPosterPicker(productId,btn) {if (!btn) btn = event && event.target; if (btn) {btn.disabled = true; btn.textContent = "加载中...";} try {var posters = await api("/posters/product/" + productId); if (!posters || !posters.length) {showToast("该商品暂无海报模板","info"); return;} // 直接选第一个生成 await generatePoster(posters[0].id,"product",productId,btn);} catch(e) {showToast("加载海报模板失败","error"); if (btn) {btn.disabled = false; btn.textContent = "海报";}}} async function generatePoster(posterId,type,targetId,btn) {if (!btn) btn = event && event.target; if (btn) {btn.disabled = true; btn.textContent = "生成中...";} try {// 使用前端专用接口获取海报模板 var poster = await api("/posters/" + posterId); currentPosterData = poster; // 构建二维码链接 var qrUrl = ""; if (type === "product") {qrUrl = location.origin + location.pathname + "?t=" + tenantSlug + "&invite=" + (currentUser ? currentUser.invite_code : "") + "&product=" + targetId;} else {qrUrl = location.origin + location.pathname + "?t=" + tenantSlug + "&invite=" + (currentUser ? currentUser.invite_code : "");} // 使用前端 Canvas 合成 await composePoster(poster,qrUrl);} catch(e) {showToast("生成失败","error");} finally {if (btn) {btn.disabled = false; btn.textContent = "生成";}}} async function composePoster(poster,qrUrl) {return new Promise(function(resolve,reject) {var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); var bgImg = new Image(); bgImg.crossOrigin = "anonymous"; bgImg.onload = function() {canvas.width = bgImg.width; canvas.height = bgImg.height; ctx.drawImage(bgImg,0,0); // 画二维码 if (poster.enable_qr !== 0) {var qrImg = new Image(); qrImg.crossOrigin = "anonymous"; qrImg.onload = function() {var qrX = (poster.qr_x / 100) * canvas.width; var qrY = (poster.qr_y / 100) * canvas.height; var qrW = (poster.qr_w / 100) * canvas.width; var qrH = (poster.qr_h / 100) * canvas.height; ctx.drawImage(qrImg,qrX,qrY,qrW,qrH); // 画文字 drawPosterTexts(ctx,canvas,poster); // 显示结果 var resultUrl = canvas.toDataURL("image/png"); showPosterResult(resultUrl); resolve();}; qrImg.onerror = reject; qrImg.src = "https://api.qrserver.com/v1/create-qr-code/?size=400x400&data=" + encodeURIComponent(qrUrl);} else {// 无二维码，直接画文字 drawPosterTexts(ctx,canvas,poster); var resultUrl = canvas.toDataURL("image/png"); showPosterResult(resultUrl); resolve();}}; bgImg.onerror = reject; bgImg.src = poster.image_url;});} function drawPosterTexts(ctx,canvas,poster) {var texts = poster.text_configs; if (!texts) {console.log("drawPosterTexts: no text_configs"); return;} if (typeof texts === "string") {try {texts = JSON.parse(texts); console.log("drawPosterTexts: parsed texts",texts);} catch(e) {console.error("drawPosterTexts: parse error",e,texts); return;}} if (!Array.isArray(texts) || !texts.length) {console.log("drawPosterTexts: empty texts array"); return;} texts.forEach(function(t,i) {var x = (t.x / 100) * canvas.width; var y = (t.y / 100) * canvas.height; var content = t.content || ""; // 支持变量替换 content = content.replace(/\{invite_code\}/g,currentUser ? currentUser.invite_code : ""); content = content.replace(/\{nickname\}/g,currentUser ? (currentUser.nickname || "") : ""); content = content.replace(/\{real_name\}/g,currentUser ? (currentUser.real_name || "") : ""); var size = t.size || 24; ctx.font = size + "px sans-serif"; ctx.fillStyle = t.color || "#000000"; ctx.textAlign = "left"; ctx.textBaseline = "top"; ctx.fillText(content,x,y); console.log("drawPosterTexts: drew text #" + i,content,"at",x,y,"size",size,"color",t.color);});} function showPosterResult(dataUrl) {var isWechat = /MicroMessenger/i.test(navigator.userAgent); var isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); var hintHtml = ""; if (isWechat) {hintHtml = '<div style="background:#fffbe6;border:1px solid #ffe58f;border-radius:8px;padding:12px;margin-bottom:12px;font-size:14px;color:#d48806;"><i class="bi bi-info-circle"></i> <strong>微信内保存方法：</strong><br>长按下方图片 → 选择"保存图片"</div>';} else if (isMobile) {hintHtml = '<div style="background:#e6f7ff;border:1px solid #91d5ff;border-radius:8px;padding:12px;margin-bottom:12px;font-size:14px;color:#096dd9;"><i class="bi bi-info-circle"></i> <strong>保存方法：</strong>长按图片选择"保存图片"，或点击下方按钮下载</div>';} document.getElementById("posterGenContent").innerHTML = hintHtml + '<div style="position:relative;display:inline-block;"><img src="' + dataUrl + '" style="max-width:100%;border-radius:12px;box-shadow:0 4px 20px rgba(0,0,0,0.15);" id="generatedPosterImg"></div>'; var downloadBtn = document.getElementById("posterDownloadBtn"); downloadBtn.style.display = ""; downloadBtn.onclick = function() {var a = document.createElement("a"); a.href = dataUrl; a.download = "poster_" + Date.now() + ".png"; document.body.appendChild(a); a.click(); document.body.removeChild(a);}; // 添加长按保存提示 var img = document.getElementById("generatedPosterImg"); if (img) {img.addEventListener("contextmenu",function(e) {// 允许长按菜单弹出});} document.getElementById("posterGenOverlay").classList.add("show");} function closePosterGen() {document.getElementById("posterGenOverlay").classList.remove("show");} // 分享商品 async function loadSalesShare() {var container = document.getElementById("salesShareContent"); container.innerHTML = '<div class="px-3 py-2 text-muted small">加载中...</div>'; try {var products = await api("/products"); if (!products || !products.length) {container.innerHTML = '<div class="empty-guide"><div class="icon-wrap"><i class="bi bi-box-arrow-up-right"></i></div><h4>暂无可分享商品</h4><p>当前无上架商品</p></div>'; return;} var code = currentUser ? currentUser.invite_code : ""; var baseUrl = location.origin + location.pathname + "?t=" + tenantSlug; var cn = {"CMCC":"移动","CUCC":"联通","CTCC":"电信","CBN":"广电"}; container.innerHTML = '<div class="px-3 py-2"><small class="text-muted">点击复制链接或生成海报，分享给客户</small></div>' + products.map(function(p) {var shareUrl = baseUrl + (code ? "&invite=" + code : "") + "&product=" + p.id; return '<div class="checkout-card mx-3 mb-2" style="padding:1rem;">' + '<div class="d-flex justify-content-between align-items-start">' + '<div style="flex:1;"><div style="font-weight:700;">' + p.name + "</div>" + '<div class="small text-muted">' + (cn[p.carrier] || p.carrier || "") + (p.campus_name ? " · " + p.campus_name : "") + "</div>" + '<div class="mt-1"><span style="color:#dc2626;font-weight:700;">¥' + Number(p.registration_fee || 0).toFixed(0) + "</span></div></div>" + '<div class="d-flex gap-2">' + '<button class="btn btn-sm btn-outline-primary" onclick="copyShareLink(\'' + shareUrl.replace(/'/g, "\\'") + '')" style="border-radius:8px;white-space:nowrap;"><i class="bi bi-link-45deg"></i> 复制</button>' +
'<button class="btn btn-sm btn-primary" onclick="showPosterPicker(' + p.id + ')" style="border-radius:8px;white-space:nowrap;"><i class="bi bi-image"></i> 海报</button>' + "</div></div></div>"; }).join("");} catch(e) {container.innerHTML = '<div class="empty-state"><i class="bi bi-exclamation-circle"></i><p>加载失败</p></div>';} } function copyShareLink(url) {copyText(url);} // 海报选择弹窗 async function showPosterPicker(productId) {try {var posters = await api("/posters/product/" + productId); if (!posters || !posters.length) {showToast("暂无该商品的海报模板","info"); return;} var html = '<div style="max-height:60vh;overflow-y:auto;">' + posters.map(function(p) {return '<div class="mb-3" style="border:1px solid #e5e7eb;border-radius:12px;overflow:hidden;cursor:pointer;" onclick="generateProductPoster(' + p.id + ')">' + '<img src="' + p.image_url + '" style="width:100%;height:160px;object-fit:cover;">' + '<div style="padding:0.75rem;"><div style="font-weight:600;">' + p.name + "</div></div>" + "</div>";}).join("") + "</div>"; showModal({title: "选择海报模板",content: html,showCancel: true,cancelText: "取消"});} catch(e) {showToast("加载海报模板失败","error");}} // 生成商品海报 function generateProductPoster(posterId) {hideModal(); var url = API + "/posters/generate/product/" + posterId + "?tenant=" + tenantSlug; showGeneratedPoster(url);} // 生成销售个人海报（从销售中心调用） async function showSalesPosterPicker() {try {var posters = await api("/posters/sales"); if (!posters || !posters.length) {showToast("暂无销售个人海报模板","info"); return;} var html = '<div style="max-height:60vh;overflow-y:auto;">' + posters.map(function(p) {return '<div class="mb-3" style="border:1px solid #e5e7eb;border-radius:12px;overflow:hidden;cursor:pointer;" onclick="generateSalesPoster(' + p.id + ')">' + '<img src="' + p.image_url + '" style="width:100%;height:160px;object-fit:cover;">' + '<div style="padding:0.75rem;"><div style="font-weight:600;">' + p.name + "</div></div>" + "</div>";}).join("") + "</div>"; showModal({title: "选择个人海报模板",content: html,showCancel: true,cancelText: "取消"});} catch(e) {showToast("加载海报模板失败","error");}} function generateSalesPoster(posterId) {hideModal(); var url = API + "/posters/generate/sales/" + posterId + "?tenant=" + tenantSlug; showGeneratedPoster(url);} // 显示生成的海报 function showGeneratedPoster(imageUrl) {var html = '<div style="text-align:center;">' + '<img src="' + imageUrl + '" style="max-width:100%;border-radius:12px;box-shadow:0 4px 20px rgba(0,0,0,0.15);">' + '<p class="text-muted small mt-2">长按保存图片到相册</p>' + "</div>"; showModal({title: "海报已生成",content: html,confirmText: "关闭"});} // 通用弹窗 var _modalCallback = null; function showModal(opts) {opts = opts || {}; var overlay = document.createElement("div"); overlay.id = "customModalOverlay"; overlay.style.cssText = "position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.6);z-index:9999;display:flex;align-items:center;justify-content:center;padding:1rem;"; var card = document.createElement("div"); card.style.cssText = "background:#fff;border-radius:16px;max-width:360px;width:100%;max-height:80vh;overflow:hidden;display:flex;flex-direction:column;"; var header = '<div style="padding:1rem;border-bottom:1px solid #f0f0f0;font-weight:700;">' + (opts.title || "") + "</div>"; var body = '<div style="padding:1rem;overflow-y:auto;">' + (opts.content || "") + "</div>"; var footer = '<div style="padding:1rem;border-top:1px solid #f0f0f0;display:flex;gap:0.5rem;justify-content:flex-end;">'; if (opts.showCancel) footer += '<button class="btn btn-outline-secondary" onclick="hideModal()">' + (opts.cancelText || "取消") + "</button>"; footer += '<button class="btn btn-primary" onclick="hideModal(true)">' + (opts.confirmText || "确定") + "</button></div>"; card.innerHTML = header + body + footer; overlay.appendChild(card); document.body.appendChild(overlay); _modalCallback = opts.onConfirm;} function hideModal(confirmed) {var overlay = document.getElementById("customModalOverlay"); if (overlay) overlay.remove(); if (confirmed && _modalCallback) _modalCallback(); _modalCallback = null;} async function loadSalesOrders() {var container = document.getElementById("salesOrdersContent"); container.innerHTML = '<div class="px-3 py-2 text-muted small">加载中...</div>'; try {var data = await api("/orders?sales=1"); var list = (data.list || []); if (!list.length) {container.innerHTML = '<div class="empty-guide"><div class="icon-wrap"><i class="bi bi-receipt"></i></div><h4>暂无销售订单</h4><p>通过邀请码产生的订单将在此显示</p></div>'; return;} var statusMap = {pending: ["待支付","warning"],paid: ["已支付","info"],confirmed: ["已完成","success"],cancelled: ["已取消","secondary"]}; container.innerHTML = list.map(function(o) {var s = statusMap[o.status] || [o.status,"secondary"]; return '<div class="order-card mx-3"><div class="d-flex justify-content-between"><span class="order-no">' + o.order_no + '</span><span class="status badge bg-' + s[1] + '">' + s[0] + '</span></div><div class="product">' + (o.product_name || "-") + '</div><div class="d-flex justify-content-between align-items-center"><span class="text-danger fw-bold">¥' + o.amount + '</span><span class="text-muted small">' + (o.nickname || "") + " · " + (o.created_at || "").substring(0,10) + "</span></div></div>";}).join("");} catch(e) {container.innerHTML = '<div class="empty-state"><i class="bi bi-exclamation-circle"></i><p>加载失败，请重试</p></div>';}} // ======================== Init ======================== showWelcomeSkeleton(); // 获取前端初始化配置（appId等） api("/init").then(function(data) {if (data.wechat_appid) window.__wxAppId = data.wechat_appid; if (data.site_name) document.getElementById("siteName").textContent = data.site_name;}).catch(function() {}); checkWechatAuth(); // 如果已有 token，先获取用户信息再处理路由，确保 currentUser 存在 if (token) {api("/auth/me").then(function(user) {currentUser = user; updateBottomNav(); // 路由初始化 var hash = location.hash.slice(1) || "/"; if (hash !== "/" && hash !== "") {// 有非首页 hash，触发路由 isNavigating = false; handleRoute();}}).catch(function() {token = null; localStorage.removeItem("user_token"); updateBottomNav();});} else {updateBottomNav(); // 有 hash 时处理路由 if (location.hash && location.hash !== "#" && location.hash !== "#/") {isNavigating = false; handleRoute();}} initLocation(); // URL带product参数时自动跳转到商品详情 (function() {var params = new URLSearchParams(window.location.search); var productId = params.get("product"); if (productId) {// 等页面初始化完再跳 setTimeout(function() {showProduct(parseInt(productId));},500);}})(); document.getElementById("fillOverlay").addEventListener("click",function(e) {if (e.target === this) hideFillOverlay();}); </script> </body> </html>)}})}}
