| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>智价云(药店版) - 全量API验收测试工具</title>
- <style>
- * { margin: 0; padding: 0; box-sizing: border-box; }
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background: #f0f2f5; padding: 24px; font-size: 14px; }
- .container { max-width: 1400px; margin: 0 auto; }
- h1 { color: #1a1a2e; margin-bottom: 8px; text-align: center; font-size: 28px; }
- h2 { color: #1a1a2e; margin-bottom: 6px; text-align: center; font-size: 16px; font-weight: 400; color: #666; }
- .toolbar { display: flex; gap: 12px; align-items: center; background: white; padding: 16px 20px; border-radius: 8px; margin-bottom: 16px; box-shadow: 0 1px 4px rgba(0,0,0,0.08); flex-wrap: wrap; }
- .toolbar label { font-weight: 600; color: #333; white-space: nowrap; font-size: 14px; }
- .toolbar input, .toolbar select { padding: 10px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; min-width: 120px; }
- .toolbar .btn { font-size: 14px; }
- .login-section { background: #f8f9fa; border: 1px dashed #ccc; border-radius: 6px; padding: 12px 14px; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
- .login-section .login-title { font-weight: 700; font-size: 14px; color: #555; margin-right: 4px; }
- .btn { display: inline-block; padding: 10px 18px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s; }
- .btn-primary { background: #2196f3; color: white; }
- .btn-primary:hover { background: #1976d2; }
- .btn-success { background: #4caf50; color: white; }
- .btn-success:hover { background: #388e3c; }
- .btn-warning { background: #ff9800; color: white; }
- .btn-warning:hover { background: #f57c00; }
- .btn-danger { background: #f44336; color: white; }
- .btn-danger:hover { background: #d32f2f; }
- .btn-outline { background: white; color: #2196f3; border: 1px solid #2196f3; }
- .btn-outline:hover { background: #e3f2fd; }
- .btn-sm { padding: 4px 10px; font-size: 11px; }
- .module-tabs { display: flex; gap: 4px; margin-bottom: 16px; flex-wrap: wrap; }
- .module-tab { padding: 12px 20px; background: white; border: 1px solid #ddd; border-radius: 6px 6px 0 0; cursor: pointer; font-size: 14px; font-weight: 500; color: #666; transition: all 0.15s; border-bottom: 2px solid transparent; }
- .module-tab:hover { background: #e3f2fd; color: #1976d2; }
- .module-tab.active { background: white; color: #1976d2; border-bottom: 2px solid #1976d2; font-weight: 600; }
- .test-section { background: white; border-radius: 8px; padding: 18px; margin-bottom: 14px; box-shadow: 0 1px 4px rgba(0,0,0,0.08); }
- .section-title { font-size: 17px; font-weight: 600; color: #333; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 2px solid #e8e8e8; display: flex; align-items: center; gap: 8px; }
- .section-title .method { display: inline-block; padding: 3px 9px; border-radius: 3px; font-size: 12px; font-weight: 700; }
- .method-get { background: #e8f5e9; color: #2e7d32; }
- .method-post { background: #e3f2fd; color: #1565c0; }
- .method-put { background: #fff3e0; color: #e65100; }
- .method-delete { background: #fce4ec; color: #c62828; }
- .section-title .path { font-family: Consolas, Monaco, monospace; color: #555; font-size: 15px; }
- .section-title .auth-tag { font-size: 12px; padding: 3px 8px; border-radius: 3px; margin-left: auto; }
- .auth-required { background: #ffcdd2; color: #b71c1c; }
- .auth-public { background: #c8e6c9; color: #1b5e20; }
- .side-tag { font-size: 12px; padding: 3px 8px; border-radius: 3px; margin-left: 4px; font-weight: 500; }
- .side-admin { background: #e3f2fd; color: #1565c0; }
- .side-user { background: #f3e5f5; color: #7b1fa2; }
- .role-tag { font-size: 12px; padding: 3px 8px; border-radius: 3px; margin-left: 8px; font-weight: 600; background: #fff3e0; color: #e65100; }
- .section-desc { font-size: 14px; color: #888; margin-bottom: 10px; }
- .form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px; margin-bottom: 10px; }
- .form-group { margin-bottom: 8px; }
- .form-group label { display: block; margin-bottom: 4px; color: #555; font-weight: 500; font-size: 14px; }
- .form-group input, .form-group select, .form-group textarea { width: 100%; padding: 10px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 15px; }
- .form-group textarea { min-height: 60px; resize: vertical; }
- .response-box { margin-top: 12px; background: #fafafa; border: 1px solid #e0e0e0; border-radius: 4px; padding: 12px; }
- .response-header { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
- .response-title { font-weight: 600; color: #333; font-size: 14px; }
- .status-badge { display: inline-block; padding: 4px 10px; border-radius: 3px; font-size: 12px; font-weight: 500; }
- .status-success { background: #c8e6c9; color: #2e7d32; }
- .status-error { background: #ffcdd2; color: #c62828; }
- .status-warn { background: #fff3e0; color: #e65100; }
- .response-content { background: #263238; color: #aed581; padding: 14px; border-radius: 4px; font-family: Consolas, Monaco, monospace; font-size: 13px; overflow-x: auto; white-space: pre-wrap; word-break: break-all; max-height: 400px; overflow-y: auto; }
- .field-desc-box { margin-top: 8px; background: #f5f7fa; border: 1px solid #e0e0e0; border-radius: 4px; padding: 10px; }
- .field-desc-title { font-size: 12px; font-weight: 600; color: #555; margin-bottom: 6px; }
- .field-desc-table { width: 100%; border-collapse: collapse; font-size: 12px; }
- .field-desc-table th { background: #e8eaed; padding: 4px 8px; text-align: left; color: #333; font-weight: 600; border: 1px solid #ddd; }
- .field-desc-table td { padding: 4px 8px; border: 1px solid #ddd; color: #555; }
- .field-desc-table td code { background: #e8e8e8; padding: 1px 4px; border-radius: 2px; font-size: 11px; }
- .summary-bar { display: flex; gap: 16px; padding: 12px 16px; background: white; border-radius: 8px; margin-bottom: 16px; box-shadow: 0 1px 4px rgba(0,0,0,0.08); align-items: center; }
- .summary-item { display: flex; align-items: center; gap: 6px; font-size: 14px; }
- .summary-count { font-weight: 700; font-size: 20px; }
- .summary-count.pass { color: #2e7d32; }
- .summary-count.fail { color: #c62828; }
- .summary-count.pending { color: #e65100; }
- .batch-buttons { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
- @media (max-width: 768px) {
- .form-grid { grid-template-columns: 1fr; }
- .toolbar { flex-direction: column; align-items: stretch; }
- .module-tab { font-size: 11px; padding: 8px 12px; }
- }
- .side-switcher { display: flex; gap: 0; margin-bottom: 16px; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.12); }
- .side-btn { flex: 1; padding: 16px 24px; border: none; cursor: pointer; font-size: 16px; font-weight: 600; transition: all 0.25s; text-align: center; }
- .side-btn.user-side { background: #e3f2fd; color: #1565c0; }
- .side-btn.user-side.active { background: #1565c0; color: white; }
- .side-btn.admin-side { background: #fff3e0; color: #e65100; }
- .side-btn.admin-side.active { background: #e65100; color: white; }
- .side-btn:hover { opacity: 0.9; transform: translateY(-1px); }
- .admin-login-area { background: #fff8e1; border: 1px solid #ffe0b2; border-radius: 6px; }
- .admin-login-area .login-title { color: #e65100 !important; }
- .admin-tabs .module-tab.active { color: #e65100; border-bottom-color: #e65100; }
- .admin-tabs .module-tab:hover { background: #fff3e0; color: #e65100; }
- /* 支付宝扫码支付弹窗 - DeepSeek 风格 */
- .alipay-modal-overlay {
- position: fixed; top: 0; left: 0; width: 100%; height: 100%;
- background: rgba(0,0,0,0.45); z-index: 1000;
- display: flex; align-items: center; justify-content: center;
- backdrop-filter: blur(2px);
- }
- .alipay-modal {
- background: #fff; border-radius: 16px; width: 360px; max-width: 92vw;
- padding: 28px 24px 22px; box-shadow: 0 20px 60px rgba(0,0,0,0.18);
- text-align: center; position: relative; animation: modalIn 0.2s ease-out;
- }
- @keyframes modalIn {
- from { opacity: 0; transform: scale(0.95) translateY(10px); }
- to { opacity: 1; transform: scale(1) translateY(0); }
- }
- .alipay-modal-close {
- position: absolute; top: 14px; right: 16px; width: 28px; height: 28px;
- border: none; background: transparent; color: #999; font-size: 22px; line-height: 1;
- cursor: pointer; border-radius: 50%; transition: all 0.2s;
- }
- .alipay-modal-close:hover { background: #f5f5f5; color: #333; }
- .alipay-modal-logo {
- display: flex; align-items: center; justify-content: center; gap: 6px;
- margin-bottom: 16px; font-size: 18px; font-weight: 700; color: #1677ff;
- }
- .alipay-modal-logo svg { width: 26px; height: 26px; }
- .alipay-modal-title { font-size: 15px; color: #666; margin-bottom: 6px; }
- .alipay-modal-amount { font-size: 32px; font-weight: 700; color: #111; margin-bottom: 18px; }
- .alipay-modal-amount span { font-size: 18px; margin-right: 2px; }
- .alipay-modal-qr {
- width: 200px; height: 200px; margin: 0 auto 14px;
- border: 1px solid #f0f0f0; border-radius: 8px; padding: 8px; background: #fff;
- }
- .alipay-modal-qr img { width: 100%; height: 100%; object-fit: contain; }
- .alipay-modal-hint {
- font-size: 12px; color: #999; line-height: 1.6; margin-top: 8px;
- }
- .alipay-modal-status {
- margin-top: 10px; font-size: 13px; color: #1677ff; min-height: 20px;
- }
- </style>
- </head>
- <body>
- <div class="container">
- <h1>智价云(药店版) - 全量API验收测试工具</h1>
- <h2>覆盖用户侧 + 运营侧全量API | 独立登录管理 | 开发环境验证码固定 123456</h2>
- <!-- 用户侧/运营侧 切换器 -->
- <div class="side-switcher">
- <button class="side-btn user-side active" onclick="switchSide('user')">用户侧 (C端)</button>
- <button class="side-btn admin-side" onclick="switchSide('admin')">运营侧 (管理端)</button>
- </div>
- <!-- 全局工具栏 -->
- <div class="toolbar">
- <label>API 基础地址</label>
- <label for="baseUrl"></label><input type="text" id="baseUrl" value="http://8.135.32.236:8001" style="flex:1;">
- </div>
- <!-- 用户侧登录区 -->
- <div id="userLoginSection">
- <div class="toolbar">
- <label>用户Token</label>
- <label for="token"></label><input type="text" id="token" placeholder="登录后自动填入" style="flex:1;" readonly>
- <span id="roleBadge" class="role-tag" style="display:none;"></span>
- <button class="btn btn-outline btn-sm" onclick="copyToken()">复制Token</button>
- </div>
- <div class="toolbar" style="background:#f8f9fa;">
- <div class="login-section">
- <span class="login-title">短信登录</span>
- <label>手机号</label>
- <label for="phone"></label><input type="text" id="phone" value="18776156917" style="width:140px;">
- <label>验证码</label>
- <label for="smsCode"></label><input type="text" id="smsCode" placeholder="123456" value="123456" style="width:100px;">
- <button class="btn btn-primary btn-sm" onclick="sendSms()">发送验证码</button>
- <button class="btn btn-success btn-sm" onclick="smsLogin()">短信登录</button>
- </div>
- <div class="login-section">
- <span class="login-title">密码登录</span>
- <label>手机号/用户名</label>
- <label for="pwdAccount"></label><input type="text" id="pwdAccount" value="18776156917" placeholder="手机号或用户名" style="width:140px;">
- <label>密码</label>
- <label for="pwdPassword"></label><input type="password" id="pwdPassword" placeholder="输入密码" style="width:120px;">
- <button class="btn btn-success btn-sm" onclick="passwordLogin()">密码登录</button>
- </div>
- </div>
- </div>
- <!-- 运营侧登录区 -->
- <div id="adminLoginSection" style="display:none;">
- <div class="toolbar">
- <label>管理员Token</label>
- <label for="adminToken"></label><input type="text" id="adminToken" placeholder="管理员登录后自动填入" style="flex:1;" readonly>
- <span id="adminRoleBadge" class="role-tag" style="display:none;"></span>
- <button class="btn btn-outline btn-sm" onclick="copyToken()">复制Token</button>
- </div>
- <div class="toolbar admin-login-area">
- <div class="login-section">
- <span class="login-title">管理员登录(t_admin表)</span>
- <label>用户名</label>
- <label for="adminPwdUsername"></label><input type="text" id="adminPwdUsername" value="admin" placeholder="管理员用户名" style="width:150px;">
- <label>密码</label>
- <label for="adminPwdPassword"></label><input type="password" id="adminPwdPassword" value="admin@123" placeholder="管理员密码" style="width:140px;">
- <button class="btn btn-warning btn-sm" onclick="adminPasswordLogin()">管理员登录</button>
- </div>
- </div>
- </div>
- <!-- 测试统计栏 -->
- <div class="summary-bar">
- <span style="font-weight:600;">本页测试统计:</span>
- <div class="summary-item"><span style="font-weight:700;font-size:20px;color:#1565c0;" id="totalCount">0</span><span>总计</span></div>
- <div class="summary-item"><span class="summary-count pass" id="passCount">0</span><span>通过</span></div>
- <div class="summary-item"><span class="summary-count fail" id="failCount">0</span><span>失败</span></div>
- <div class="summary-item"><span class="summary-count pending" id="pendingCount">0</span><span>待测</span></div>
- <button class="btn btn-warning btn-sm" onclick="runAllTests()" style="margin-left:auto;">一键全部测试</button>
- <button class="btn btn-outline btn-sm" onclick="clearAllResults()">清除结果</button>
- </div>
- <!-- 用户侧模块标签栏 -->
- <div class="module-tabs" id="moduleTabs">
- <div class="module-tab active" data-module="auth">1.认证模块</div>
- <div class="module-tab" data-module="crawler">2.爬虫配额</div>
- <div class="module-tab" data-module="invite">3.邀请系统</div>
- <div class="module-tab" data-module="coupon">4.优惠券</div>
- <div class="module-tab" data-module="membership">5.会员等级</div>
- <div class="module-tab" data-module="payment">6.支付模块</div>
- <div class="module-tab" data-module="upload">7.文件上传</div>
- <div class="module-tab" data-module="search">8.搜索查询</div>
- <div class="module-tab" data-module="platform-account">9.平台账号</div>
- <div class="module-tab" data-module="business-license">10.营业执照</div>
- <div class="module-tab" data-module="platform-config">11.平台配置</div>
- <div class="module-tab" data-module="search-record">12.搜索记录</div>
- <div class="module-tab" data-module="platform-session">13.平台会话</div>
- </div>
- <!-- 运营侧模块标签栏 -->
- <div class="module-tabs admin-tabs" id="adminModuleTabs" style="display:none;">
- <div class="module-tab active" data-module="admin-membership">1.会员管理</div>
- <div class="module-tab" data-module="admin-invite">2.邀请管理</div>
- <div class="module-tab" data-module="admin-crawler">3.爬虫管理</div>
- <div class="module-tab" data-module="admin-coupon">4.优惠券发放</div>
- <div class="module-tab" data-module="admin-license">5.执照审核</div>
- <div class="module-tab" data-module="admin-payment">6.支付方案</div>
- <div class="module-tab" data-module="admin-anxin">7.安心付管理</div>
- <div class="module-tab" data-module="admin-trial-quota">8.体验配额</div>
- <div class="module-tab" data-module="admin-user">9.用户管理</div>
- <div class="module-tab" data-module="admin-audit">10.审计日志</div>
- <div class="module-tab" data-module="upload">11.文件上传</div>
- </div>
- <div id="panels"></div>
- </div>
- <script>
- // ===================== 全局状态 =====================
- let activeSide = 'user';
- const BASE_URL = () => document.getElementById('baseUrl').value;
- const TOKEN = () => activeSide === 'admin'
- ? (document.getElementById('adminToken')?.value || '')
- : (document.getElementById('token')?.value || '');
- const PHONE = () => activeSide === 'admin'
- ? (document.getElementById('adminPhone')?.value || '')
- : (document.getElementById('phone')?.value || '');
- const SMS_CODE = () => activeSide === 'admin'
- ? (document.getElementById('adminSmsCode')?.value || '123456')
- : (document.getElementById('smsCode')?.value || '123456');
- let stats = { pass: 0, fail: 0, pending: 0, total: 0 };
- // ===================== 大整数安全JSON解析 =====================
- /**
- * 安全解析JSON:将16位以上的大整数(如雪花ID)包裹为字符串后再解析,
- * 避免JavaScript Number精度丢失(如2069620495151230978 → 2069620495151231000)
- * 示例:":2069620495151230978," → ":\"2069620495151230978\","
- */
- function safeJsonParse(text) {
- if (!text || text.trim() === '' || text.trim() === 'null') return null;
- const safeText = text.replace(/([:,\[]\s*)(\d{16,})(\s*[,\]\}])/g, '$1"$2"$3');
- return JSON.parse(safeText);
- }
- // ===================== HTTP 请求 =====================
- async function api(method, path, body, requireAuth = true) {
- const headers = { 'Content-Type': 'application/json' };
- if (requireAuth && TOKEN()) { headers['Authorization'] = 'Bearer ' + TOKEN(); }
- const opts = { method, headers };
- if (body && method !== 'GET') { opts.body = JSON.stringify(body); }
- const startTime = Date.now();
- try {
- const res = await fetch(BASE_URL() + path, opts);
- const text = await res.text();
- let data;
- try { data = safeJsonParse(text); } catch (e) { data = { _raw: text }; }
- return { ok: res.ok, status: res.status, data, duration: Date.now() - startTime };
- } catch (e) {
- return { ok: false, status: 0, data: { error: e.message }, duration: Date.now() - startTime };
- }
- }
- // ===================== 测试执行 =====================
- async function runTest(sectionId, method, path, body, requireAuth, validatorName) {
- const box = document.getElementById(sectionId + '-box');
- const statusEl = document.getElementById(sectionId + '-status');
- const rawEl = document.getElementById(sectionId + '-raw');
- const durEl = document.getElementById(sectionId + '-duration');
- updateBadge(statusEl, 'pending', '运行中...');
- if (box) box.style.display = 'block';
- let resolvedPath = path;
- document.querySelectorAll(`[id^="${sectionId}-"]`).forEach(input => {
- if (input.tagName === 'INPUT') {
- const fieldName = input.id.replace(sectionId + '-', '');
- // 替换路径变量 {fieldName}
- resolvedPath = resolvedPath.replace('{' + fieldName + '}', input.value || fieldName);
- // 替换查询参数 ?fieldName=xxx 或 &fieldName=xxx(输入框有值时才替换)
- if (input.value) {
- const queryVal = encodeURIComponent(input.value);
- resolvedPath = resolvedPath.replace(
- new RegExp('([?&])' + fieldName + '=[^&]*', 'g'),
- '$1' + fieldName + '=' + queryVal
- );
- }
- }
- });
- const result = await api(method, resolvedPath, body, requireAuth);
- if (durEl) durEl.textContent = result.duration + 'ms';
- if (rawEl) rawEl.textContent = JSON.stringify(result.data, null, 2);
- // 检测响应中的支付结果并渲染
- const qrBase64 = result.data?.data?.qrCodeBase64;
- const payUrl = result.data?.data?.payUrl;
- const channel = result.data?.data?.channel;
- const orderNo = result.data?.data?.orderNo;
- const amount = result.data?.data?.amount;
- // 自动回填订单号到相关输入框
- if (orderNo) {
- ['payment-query-order-orderNo', 'payment-mock-pay-orderNo',
- 'payment-callback-wechat-out_trade_no', 'payment-callback-alipay-out_trade_no']
- .forEach(fid => { const el = document.getElementById(fid); if (el) el.value = orderNo; });
- }
- // 自动回填协议ID到相关输入框
- const agreementId = result.data?.data?.agreementId;
- if (agreementId) {
- ['payment-agreement-detail-agreementId', 'payment-agreement-unsign-agreementId']
- .forEach(fid => { const el = document.getElementById(fid); if (el) el.value = agreementId; });
- }
- // 自动回填签约协议号到微信回调通知
- const externalAgreementNo = result.data?.data?.externalAgreementNo;
- if (externalAgreementNo) {
- const el = document.getElementById('payment-agreement-notify-wechat-out_contract_code');
- if (el) el.value = externalAgreementNo;
- }
- // 自动回填安心付卡模板ID到相关输入框
- const anxinCardId = result.data?.data?.cardId;
- if (anxinCardId) {
- ['admin-anxin-sync-cardId', 'admin-anxin-status-cardId',
- 'payment-anxin-notify-deduct-card_id', 'payment-anxin-notify-card-change-card_id']
- .forEach(fid => { const el = document.getElementById(fid); if (el) el.value = anxinCardId; });
- }
- if (channel === 'ALIPAY' && qrBase64) {
- // 支付宝:DeepSeek 风格自定义弹窗,展示二维码
- showAlipayModal(qrBase64, amount, orderNo);
- } else if (qrBase64) {
- // 微信:显示扫码二维码
- let qrBox = document.getElementById(sectionId + '-qrcode');
- if (!qrBox) {
- qrBox = document.createElement('div');
- qrBox.id = sectionId + '-qrcode';
- qrBox.style.cssText = 'text-align:center;padding:12px;background:#fff;border-radius:4px;margin-top:8px;';
- rawEl.parentNode.insertBefore(qrBox, rawEl.nextSibling);
- }
- const channelName = channel === 'WECHAT' ? '微信' : (channel || '相应平台');
- qrBox.innerHTML = '<img src="' + qrBase64 + '" style="max-width:220px;border:1px solid #eee;border-radius:4px;"><div style="margin-top:6px;font-size:12px;color:#666;">' + channelName + '扫码付款后回调自动激活,也可手动确认支付测试回调</div>';
- } else {
- const oldBox = document.getElementById(sectionId + '-qrcode');
- if (oldBox) oldBox.remove();
- }
- let passed = false;
- if (validatorName && typeof window[validatorName] === 'function') {
- try { passed = window[validatorName](result); } catch (e) { passed = false; }
- } else {
- passed = result.ok && result.status === 200;
- }
- updateStats(sectionId, passed);
- updateBadge(statusEl, passed ? 'success' : 'error', passed ? '通过 ' + result.status : '失败 ' + result.status);
- if (box) box.style.display = 'block';
- }
- function updateBadge(el, cls, text) {
- if (!el) return;
- el.textContent = text;
- el.className = 'status-badge status-' + cls;
- }
- function updateStats(id, passed) {
- const old = stats[id];
- if (old === 'pass') stats.pass--;
- if (old === 'fail') stats.fail--;
- stats[id] = passed ? 'pass' : 'fail';
- if (passed) stats.pass++; else stats.fail++;
- stats.pending = Math.max(0, stats.total - stats.pass - stats.fail);
- document.getElementById('passCount').textContent = stats.pass;
- document.getElementById('failCount').textContent = stats.fail;
- document.getElementById('pendingCount').textContent = stats.pending;
- }
- // ===================== 登录相关 =====================
- async function sendSms() {
- const res = await api('POST', '/api/auth/sms/send', { phone: PHONE(), scene: 'login' }, false);
- if (res.ok) {
- if (activeSide === 'admin') {
- const el = document.getElementById('adminSmsCode');
- if (el) el.value = '123456';
- } else {
- document.getElementById('smsCode').value = '123456';
- }
- const label = activeSide === 'admin' ? '管理员验证码' : '验证码';
- alert(label + '已写入(开发测试模式,固定验证码: 123456)');
- } else {
- alert('发送失败: ' + JSON.stringify(res.data));
- }
- }
- async function smsLogin() {
- const phone = PHONE();
- const code = SMS_CODE();
- if (!phone) { alert(activeSide === 'admin' ? '请填写管理员手机号' : '请填写手机号'); return; }
- const payload = { phone, code, scene: 'login' };
- const res = await api('POST', '/api/auth/sms/login', payload, false);
- if (res.data?.code === 200 && res.data?.data?.accessToken) {
- if (activeSide === 'admin') {
- document.getElementById('adminToken').value = res.data.data.accessToken;
- updateAdminRoleBadge(res.data.data.role);
- alert('管理员短信登录成功!\n用户ID: ' + res.data.data.userId + '\n角色: ' + (res.data.data.role || 'USER'));
- } else {
- document.getElementById('token').value = res.data.data.accessToken;
- updateRoleBadge(res.data.data.role);
- alert('短信登录成功!\n用户ID: ' + res.data.data.userId + '\n角色: ' + (res.data.data.role || 'USER'));
- }
- } else {
- alert('登录失败: ' + (res.data?.message || '未知错误'));
- }
- }
- async function passwordLogin() {
- const password = activeSide === 'admin'
- ? document.getElementById('adminPwdPassword').value
- : document.getElementById('pwdPassword').value;
- let payload;
- if (activeSide === 'admin') {
- const username = document.getElementById('adminPwdUsername').value;
- if (!username || !password) { alert('请填写用户名和密码'); return; }
- payload = { username, password };
- } else {
- const account = document.getElementById('pwdAccount').value.trim();
- if (!account || !password) { alert('请填写手机号/用户名和密码'); return; }
- // 自动识别:纯数字视为手机号,否则视为用户名
- if (/^\d{11}$/.test(account)) {
- payload = { phone: account, password };
- } else {
- payload = { username: account, password };
- }
- }
- const res = await api('POST', '/api/auth/password/login', payload, false);
- if (res.data?.code === 200 && res.data?.data?.accessToken) {
- if (activeSide === 'admin') {
- document.getElementById('adminToken').value = res.data.data.accessToken;
- updateAdminRoleBadge(res.data.data.role);
- alert('管理员密码登录成功!\n用户ID: ' + res.data.data.userId + '\n角色: ' + (res.data.data.role || 'USER'));
- } else {
- document.getElementById('token').value = res.data.data.accessToken;
- updateRoleBadge(res.data.data.role);
- alert('密码登录成功!\n用户ID: ' + res.data.data.userId + '\n角色: ' + (res.data.data.role || 'USER'));
- }
- } else {
- alert('登录失败: ' + (res.data?.message || '未知错误'));
- }
- }
- function updateRoleBadge(role) {
- const badge = document.getElementById('roleBadge');
- if (badge) {
- if (role) {
- badge.textContent = role;
- badge.style.display = 'inline-block';
- } else {
- badge.style.display = 'none';
- }
- }
- }
- function updateAdminRoleBadge(role) {
- const badge = document.getElementById('adminRoleBadge');
- if (badge) {
- if (role) {
- badge.textContent = role;
- badge.style.display = 'inline-block';
- } else {
- badge.style.display = 'none';
- }
- }
- }
- async function adminPasswordLogin() {
- const username = document.getElementById('adminPwdUsername').value;
- const password = document.getElementById('adminPwdPassword').value;
- if (!username || !password) { alert('请填写用户名和密码'); return; }
- const payload = { username, password };
- const res = await api('POST', '/api/auth/password/login', payload, false);
- if (res.data?.code === 200 && res.data?.data?.accessToken) {
- document.getElementById('adminToken').value = res.data.data.accessToken;
- updateAdminRoleBadge(res.data.data.role);
- alert('管理员登录成功!\n用户ID: ' + res.data.data.userId + '\n角色: ' + (res.data.data.role || 'ADMIN'));
- } else {
- alert('登录失败: ' + (res.data?.message || '未知错误'));
- }
- }
- function copyToken() {
- const tok = TOKEN();
- if (!tok) { alert('请先登录'); return; }
- navigator.clipboard.writeText(tok).then(() => alert('Token已复制!'));
- }
- async function sendCodeToField(inputId, scene) {
- const phoneInput = document.getElementById(inputId);
- if (!phoneInput || !phoneInput.value) { alert('请先填写手机号'); return; }
- const res = await api('POST', '/api/auth/sms/send', { phone: phoneInput.value, scene: scene || 'login' }, false);
- if (res.ok) {
- alert('验证码已发送(开发测试模式,固定验证码: 123456)');
- } else {
- alert('发送失败: ' + JSON.stringify(res.data));
- }
- }
- async function uploadFile() {
- const fileInput = document.getElementById('upload-file');
- const statusEl = document.getElementById('upload-status');
- const rawEl = document.getElementById('upload-raw');
- const durEl = document.getElementById('upload-duration');
- const box = document.getElementById('upload-result-box');
- if (!fileInput || !fileInput.files || !fileInput.files[0]) {
- alert('请先选择文件'); return;
- }
- const formData = new FormData();
- formData.append('file', fileInput.files[0]);
- updateBadge(statusEl, 'pending', '上传中...');
- box.style.display = 'block';
- const startTime = Date.now();
- try {
- const headers = {};
- if (TOKEN()) { headers['Authorization'] = 'Bearer ' + TOKEN(); }
- const res = await fetch(BASE_URL() + '/api/upload', { method: 'POST', headers, body: formData });
- const text = await res.text();
- const duration = Date.now() - startTime;
- let data;
- try { data = safeJsonParse(text); } catch (e) { data = { _raw: text }; }
- if (durEl) durEl.textContent = duration + 'ms';
- if (rawEl) rawEl.textContent = JSON.stringify(data, null, 2);
- const passed = res.ok && data?.code === 200;
- updateBadge(statusEl, passed ? 'success' : 'error', passed ? '通过 ' + res.status : '失败 ' + res.status);
- if (!stats.hasOwnProperty('upload-file')) {
- stats.total++;
- document.getElementById('totalCount').textContent = stats.total;
- }
- updateStats('upload-file', passed);
- } catch (e) {
- if (rawEl) rawEl.textContent = 'Error: ' + e.message;
- updateBadge(statusEl, 'error', '失败');
- }
- }
- // ===================== 侧切换 =====================
- function switchSide(side) {
- activeSide = side;
- document.querySelectorAll('.side-btn').forEach(b => b.classList.remove('active'));
- document.querySelector('.side-btn.' + side + '-side').classList.add('active');
- const isAdmin = side === 'admin';
- document.getElementById('userLoginSection').style.display = isAdmin ? 'none' : '';
- document.getElementById('adminLoginSection').style.display = isAdmin ? '' : 'none';
- document.getElementById('moduleTabs').style.display = isAdmin ? 'none' : '';
- document.getElementById('adminModuleTabs').style.display = isAdmin ? '' : 'none';
- if (isAdmin) {
- const activeTab = document.querySelector('#adminModuleTabs .module-tab.active');
- renderModule(activeTab ? activeTab.dataset.module : 'admin-membership');
- } else {
- const activeTab = document.querySelector('#moduleTabs .module-tab.active');
- renderModule(activeTab ? activeTab.dataset.module : 'auth');
- }
- }
- // ===================== 模块切换 =====================
- document.getElementById('moduleTabs').addEventListener('click', (e) => {
- if (!e.target.classList.contains('module-tab')) return;
- document.querySelectorAll('#moduleTabs .module-tab').forEach(t => t.classList.remove('active'));
- e.target.classList.add('active');
- renderModule(e.target.dataset.module);
- });
- document.getElementById('adminModuleTabs').addEventListener('click', (e) => {
- if (!e.target.classList.contains('module-tab')) return;
- document.querySelectorAll('#adminModuleTabs .module-tab').forEach(t => t.classList.remove('active'));
- e.target.classList.add('active');
- renderModule(e.target.dataset.module);
- });
- // ===================== 渲染模块面板 =====================
- function renderModule(module) {
- const panels = document.getElementById('panels');
- let html = '';
- switch (module) {
- // 用户侧模块
- case 'auth': html = renderAuthModule(); break;
- case 'crawler': html = renderCrawlerModule(); break;
- case 'invite': html = renderInviteModule(); break;
- case 'coupon': html = renderCouponModule(); break;
- case 'membership': html = renderMembershipModule(); break;
- case 'payment': html = renderPaymentModule(); break;
- case 'watchlist': html = renderWatchlistModule(); break;
- case 'trial-quota': html = renderTrialQuotaModule(); break;
- case 'upload': html = renderUploadModule(); break;
- case 'search': html = renderSearchModule(); break;
- case 'platform-account': html = renderPlatformAccountModule(); break;
- case 'business-license': html = renderBusinessLicenseModule(); break;
- case 'platform-config': html = renderPlatformConfigModule(); break;
- case 'search-record': html = renderSearchRecordModule(); break;
- case 'platform-session': html = renderPlatformSessionModule(); break;
- // 运营侧模块
- case 'admin-membership': html = renderAdminMembershipModule(); break;
- case 'admin-invite': html = renderAdminInviteModule(); break;
- case 'admin-crawler': html = renderAdminCrawlerModule(); break;
- case 'admin-coupon': html = renderAdminCouponModule(); break;
- case 'admin-license': html = renderAdminLicenseModule(); break;
- case 'admin-payment': html = renderAdminPaymentModule(); break;
- case 'admin-anxin': html = renderAdminAnxinModule(); break;
- case 'admin-trial-quota': html = renderAdminTrialQuotaModule(); break;
- case 'admin-user': html = renderAdminUserModule(); break;
- case 'admin-audit': html = renderAdminAuditModule(); break;
- }
- panels.innerHTML = html;
- // 修复含引号的默认值:从 data-raw 属性恢复正确的 input value
- panels.querySelectorAll('input[data-raw]').forEach(input => {
- input.value = input.getAttribute('data-raw');
- input.removeAttribute('data-raw');
- });
- }
- function toValidatorName(id) {
- const parts = id.split('-');
- let camel = parts[0];
- for (let i = 1; i < parts.length; i++) {
- camel += parts[i].charAt(0).toUpperCase() + parts[i].slice(1);
- }
- return 'validate_' + camel;
- }
- // ==================== 字段说明数据 ====================
- const FIELD_DESC = {
- 'resultWrapper': [
- ['code', 'Integer', '业务状态码(200=成功)'],
- ['message', 'String', '提示信息(如:操作成功)'],
- ['data', 'Object/Array/null', '返回数据(详见下方字段)'],
- ['timestamp', 'Long', '响应时间戳(毫秒级)']
- ],
- 'loginResponse': [
- ['userId', 'Long', '用户ID'],
- ['accessToken', 'String', 'JWT访问令牌'],
- ['refreshToken', 'String', '刷新令牌'],
- ['expiresIn', 'Long', '令牌过期时间(秒)'],
- ['userInfo', 'Object', '用户信息(嵌套对象,见下方子字段)'],
- ['userInfo.id', 'Long', '用户ID(主键)'],
- ['userInfo.userId', 'Long', '用户ID(兼容字段=id)'],
- ['userInfo.phone', 'String', '手机号(脱敏 187****6917)'],
- ['userInfo.nickname', 'String', '昵称(如:用户6917)'],
- ['userInfo.avatar', 'String', '头像URL(可能为null)'],
- ['userInfo.wechatBound', 'Boolean', '是否已绑定微信'],
- ['userInfo.levelInfo', 'Object', '等级信息(嵌套对象,见下方子字段)'],
- ['userInfo.levelInfo.id', 'Long', '等级ID'],
- ['userInfo.levelInfo.levelName', 'String', '等级名称(如:普通会员)'],
- ['userInfo.levelInfo.levelCode', 'String', '等级编码(如:PLUS)'],
- ['userInfo.levelInfo.levelWeight', 'Integer', '等级权重(越高越好)'],
- ['userInfo.levelInfo.crawlerQuota', 'Integer', '每日爬虫配额'],
- ['userInfo.levelInfo.monthlyQuota', 'Integer', '每月爬虫配额'],
- ['userInfo.levelInfo.maxConcurrent', 'Integer', '最大并发数'],
- ['userInfo.levelInfo.description', 'String', '等级描述'],
- ['userInfo.levelInfo.version', 'String', '配置版本号(可能为null)'],
- ['userInfo.levelInfo.membershipExpireAt', 'DateTime', '会员到期时间(null=永久或无会员)'],
- ['userInfo.levelInfo.membershipDaysRemaining', 'Integer', '会员剩余天数(-1=永久,0=已过期)'],
- ['userInfo.levelInfo.pricingPlans', 'Array', '当前等级套餐定价方案列表'],
- ['userInfo.levelInfo.pricingPlans[].planCode', 'String', '方案编码(MONTHLY_PLUS/YEARLY_PLUS等)'],
- ['userInfo.levelInfo.pricingPlans[].planName', 'String', '方案名称(普通会员·月卡)'],
- ['userInfo.levelInfo.pricingPlans[].price', 'BigDecimal', '价格(元,如9.90)'],
- ['userInfo.levelInfo.pricingPlans[].originalPrice', 'BigDecimal', '原价(元,如19.90)'],
- ['userInfo.levelInfo.pricingPlans[].durationDays', 'Integer', '会员有效天数(30/365)'],
- ['userInfo.levelInfo.pricingPlans[].description', 'String', '方案描述'],
- ['userInfo.createTime', 'DateTime', '注册时间'],
- ['userInfo.loginSource', 'String', '登录来源(SMS/PASSWORD/WECHAT,可能为null)'],
- ['userInfo.levelCode', 'String', '等级编码(冗余字段)'],
- ['userInfo.version', 'String', '等级版本(冗余字段,可能为null)'],
- ['needBindPhone', 'Boolean', '是否需要绑定手机号'],
- ['tempToken', 'String', '临时令牌(用于绑定手机号,通常为null)'],
- ['newUser', 'Boolean', '是否为新注册用户'],
- ['kickedPreviousSession', 'Boolean', '是否踢出了旧设备(true=旧设备已被强制下线)'],
- ['kickedDeviceInfo', 'String', '被踢出设备信息(如 "Windows / 192.168.1.1")']
- ],
- 'userInfo': [
- ['id', 'Long', '用户ID(主键)'],
- ['userId', 'Long', '用户ID(兼容字段=id)'],
- ['phone', 'String', '手机号(脱敏 187****6917)'],
- ['nickname', 'String', '昵称(如:用户6917)'],
- ['avatar', 'String', '头像URL(可能为null)'],
- ['wechatBound', 'Boolean', '是否已绑定微信'],
- ['levelInfo', 'Object', '等级信息(嵌套对象,见下方子字段)'],
- ['levelInfo.id', 'Long', '等级ID'],
- ['levelInfo.levelName', 'String', '等级名称(如:普通会员)'],
- ['levelInfo.levelCode', 'String', '等级编码(如:PLUS)'],
- ['levelInfo.levelWeight', 'Integer', '等级权重(越高越好)'],
- ['levelInfo.crawlerQuota', 'Integer', '每日爬虫配额'],
- ['levelInfo.monthlyQuota', 'Integer', '每月爬虫配额'],
- ['levelInfo.maxConcurrent', 'Integer', '最大并发数'],
- ['levelInfo.description', 'String', '等级描述'],
- ['levelInfo.version', 'String', '配置版本号(可能为null)'],
- ['levelInfo.membershipExpireAt', 'DateTime', '会员到期时间(null=永久或无会员)'],
- ['levelInfo.membershipDaysRemaining', 'Integer', '会员剩余天数(-1=永久,0=已过期)'],
- ['levelInfo.pricingPlans', 'Array', '当前等级套餐定价方案列表'],
- ['levelInfo.pricingPlans[].planCode', 'String', '方案编码(MONTHLY_PLUS/YEARLY_PLUS等)'],
- ['levelInfo.pricingPlans[].planName', 'String', '方案名称(普通会员·月卡)'],
- ['levelInfo.pricingPlans[].price', 'BigDecimal', '价格(元,如9.90)'],
- ['levelInfo.pricingPlans[].originalPrice', 'BigDecimal', '原价(元,如19.90)'],
- ['levelInfo.pricingPlans[].durationDays', 'Integer', '会员有效天数(30/365)'],
- ['levelInfo.pricingPlans[].description', 'String', '方案描述'],
- ['membershipLevel', 'String', '会员等级(PLUS/PRO/ULTRA)'],
- ['membershipLevelName', 'String', '会员等级中文名'],
- ['inviterId', 'Long', '邀请人ID(null=未被邀请)'],
- ['inviterNickname', 'String', '邀请人昵称(null=未被邀请)'],
- ['hasBoundInviteCode', 'Boolean', '是否已绑定邀请码'],
- ['createTime', 'DateTime', '注册时间'],
- ['loginSource', 'String', '登录来源(SMS/PASSWORD/WECHAT)'],
- ['levelCode', 'String', '等级编码(来自levelInfo)']
- ],
- 'crawlerStatus': [
- ['levelName', 'String', '等级名称(如:普通用户)'],
- ['levelCode', 'String', '等级编码(如:NORMAL)'],
- ['todayUsed', 'Integer', '今日已用次数'],
- ['todayQuota', 'Integer', '今日总配额(等级+额外)'],
- ['todayRemaining', 'Integer', '今日剩余次数'],
- ['monthUsed', 'Integer', '本月已用次数'],
- ['monthQuota', 'Integer', '本月总配额(等级+额外)'],
- ['monthRemaining', 'Integer', '本月剩余次数'],
- ['baseDailyQuota', 'Integer', '等级基础每日配额'],
- ['baseMonthlyQuota', 'Integer', '等级基础每月配额'],
- ['extraQuota', 'Integer', '额外配额总额(三种来源之和)'],
- ['extraQuotaBreakdown', 'Array', '额外配额来源明细(见下方)'],
- ['extraQuotaBreakdown[].sourceType', 'String', '来源:INVITE/PURCHASE/ADMIN'],
- ['extraQuotaBreakdown[].sourceName', 'String', '来源名称(邀请奖励/购买会员/运营发放)'],
- ['extraQuotaBreakdown[].available', 'Integer', '该来源可用次数'],
- ['extraQuotaBreakdown[].total', 'Integer', '该来源累计获得'],
- ['extraQuotaBreakdown[].used', 'Integer', '该来源已使用'],
- ['maxConcurrent', 'Integer', '最大并发数'],
- ['hasCoupon', 'Boolean', '是否有可用优惠券'],
- ['availableCouponCount', 'Integer', '可用优惠券数量']
- ],
- 'crawlerLog': [
- ['id', 'Long', '记录ID'],
- ['userId', 'Long', '用户ID'],
- ['usageDate', 'Date', '使用日期'],
- ['usageCount', 'Integer', '本次使用次数'],
- ['platform', 'String', '爬取平台代码'],
- ['source', 'String', '消耗来源(QUOTA/COUPON)'],
- ['remark', 'String', '备注'],
- ['createTime', 'DateTime', '创建时间']
- ],
- 'inviteCode': [
- ['code', 'String', '邀请码(8位)'],
- ['inviteLink', 'String', '完整邀请链接'],
- ['inviterNickname', 'String', '邀请人昵称'],
- ['usedCount', 'Integer', '已使用次数(注册)'],
- ['clickCount', 'Integer', '链接被打开次数'],
- ['maxUses', 'Integer', '最大使用次数'],
- ['expireTime', 'DateTime', '过期时间'],
- ['status', 'Integer', '状态'],
- ['createTime', 'DateTime', '创建时间'],
- ['shareTitle', 'String', '分享标题'],
- ['shareDescription', 'String', '分享描述'],
- ['copyText', 'String', '一键复制文本(格式:我是{昵称},在这里发现了一个药店采购神器——{appName}!...下载链接{inviteLink})'],
- ['rewardDescription', 'String', '奖励说明'],
- ['downloadUrl', 'String', '下载链接(与 inviteLink 一致,访问 /api/invite/{code} 服务端按设备流式返回安装包)']
- ],
- 'myInviter': [
- ['invited', 'Boolean', '是否被邀请过'],
- ['inviterId', 'Long', '邀请人ID(未邀请时为null)'],
- ['inviterNickname', 'String', '邀请人昵称'],
- ['inviterPhone', 'String', '邀请人手机号(脱敏)'],
- ['inviterAvatar', 'String', '邀请人头像'],
- ['pharmacyName', 'String', '邀请人药店名称'],
- ['inviteCode', 'String', '邀请码'],
- ['inviteTime', 'DateTime', '邀请时间']
- ],
- 'inviteStats': [
- ['myInviteCode', 'String', '我的邀请码'],
- ['inviteLink', 'String', '邀请链接'],
- ['clickedCount', 'Integer', '链接被打开总次数'],
- ['totalInvited', 'Integer', '已邀请人数'],
- ['registeredCount', 'Integer', '已注册人数'],
- ['totalReward', 'Integer', '累计获得奖励会员月数'],
- ['summaryText', 'String', '摘要文本'],
- ['todayInvited', 'Integer', '今日已邀请人数'],
- ['todayRemaining', 'Integer', '今日剩余可邀次数'],
- ['rewardPerInvite', 'Integer', '每次邀请奖励会员月数'],
- ['crawlerTodayRemaining', 'Integer', '今日爬虫剩余配额(与爬虫状态页一致)'],
- ['crawlerQuotaFromInvites', 'Integer', '邀请贡献的额外爬虫配额']
- ],
- 'inviteReward': [
- ['inviterId', 'Long', '邀请人ID'],
- ['inviterNickname', 'String', '邀请人昵称'],
- ['inviteeId', 'Long', '被邀请人ID'],
- ['inviteeNickname', 'String', '被邀请人昵称'],
- ['registered', 'Integer', '是否已注册(0仅点击/1已注册)'],
- ['registeredText', 'String', '注册状态文本'],
- ['rewardCount', 'Integer', '奖励会员月数'],
- ['inviteTime', 'String', '邀请时间']
- ],
- 'invitePage': [
- ['inviterNickname', 'String', '邀请人昵称'],
- ['inviterAvatar', 'String', '邀请人头像'],
- ['inviteCode', 'String', '邀请码'],
- ['channel', 'String', '渠道(windows/app/wechat/miniapp等,默认windows)'],
- ['openType', 'String', '打开方式(download/redirect等)'],
- ['appName', 'String', '应用名称'],
- ['landingTitle', 'String', '落地页标题'],
- ['landingDesc', 'String', '落地页描述'],
- ['appDownloadUrl', 'String', 'Windows下载入口URL(channel=windows/app),访问 /api/invite/{code} 会根据设备流式返回对应安装包'],
- ['miniappPath', 'String', '小程序路径'],
- ['miniappAppId', 'String', '小程序AppID'],
- ['wechatRedirectUrl', 'String', '微信跳转URL'],
- ['dingtalkAppId', 'String', '钉钉AppID'],
- ['feishuAppId', 'String', '飞书AppID'],
- ['rewardPerRegister', 'Integer', '每次注册奖励会员月数'],
- ['autoFillSupported', 'Boolean', '是否支持自动填入'],
- ['urlScheme', 'String', 'URL Scheme'],
- ['deepLink', 'String', '深度链接'],
- ['instructionText', 'String', '提示文案']
- ],
- 'couponIssue': [
- ['data', 'Integer', '成功发放的优惠券数量(如:2表示2张)']
- ],
- 'coupon': [
- ['id', 'Long', '优惠券ID'],
- ['couponName', 'String', '优惠券名称'],
- ['type', 'String', '类型(CRAWLER等)'],
- ['value', 'Integer', '券面值(次数)'],
- ['status', 'Integer', '状态(0未用/1已用/2过期)'],
- ['expireTime', 'DateTime', '过期时间'],
- ['usedTime', 'DateTime', '使用时间'],
- ['source', 'String', '来源'],
- ['createTime', 'DateTime', '领取时间']
- ],
- 'membership': [
- ['id', 'Long', '会员记录ID'],
- ['userId', 'Long', '用户ID'],
- ['phone', 'String', '用户手机号'],
- ['nickname', 'String', '用户昵称'],
- ['level', 'String', '会员等级(PLUS/PRO/ULTRA)'],
- ['levelName', 'String', '等级中文名'],
- ['source', 'String', '来源(ADMIN_GRANT/RECHARGE/SYSTEM)'],
- ['effectiveFrom', 'DateTime', '生效开始时间'],
- ['effectiveTo', 'DateTime', '生效结束时间(NULL表示永久)'],
- ['status', 'Integer', '状态(1有效/0过期停用)'],
- ['remark', 'String', '备注'],
- ['createTime', 'DateTime', '创建时间']
- ],
- 'membershipBenefit': [
- ['benefitType', 'String', '权益类型(DAILY_QUOTA/MONTHLY_QUOTA/MAX_CONCURRENT)'],
- ['benefitTypeName', 'String', '权益类型中文名'],
- ['benefitValue', 'Integer', '权益数值'],
- ['description', 'String', '权益说明']
- ],
- 'levelResponse': [
- ['id', 'Long', '等级ID'],
- ['levelName', 'String', '等级名称'],
- ['levelCode', 'String', '等级编码'],
- ['levelWeight', 'Integer', '等级权重(越高越好)'],
- ['crawlerQuota', 'Integer', '每日爬虫配额'],
- ['monthlyQuota', 'Integer', '每月爬虫配额'],
- ['maxConcurrent', 'Integer', '最大并发数'],
- ['description', 'String', '等级描述'],
- ['version', 'String', '配置版本号'],
- ['membershipExpireAt', 'DateTime', '会员到期时间(null=永久或无会员)'],
- ['membershipDaysRemaining', 'Integer', '会员剩余天数(-1=永久,0=已过期)'],
- ['pricingPlans', 'Array', '当前等级套餐定价方案列表'],
- ['pricingPlans[].planCode', 'String', '方案编码(MONTHLY_PLUS/YEARLY_PLUS等)'],
- ['pricingPlans[].planName', 'String', '方案名称(普通会员·月卡)'],
- ['pricingPlans[].price', 'BigDecimal', '价格(元,如9.90)'],
- ['pricingPlans[].originalPrice', 'BigDecimal', '原价(元,如19.90)'],
- ['pricingPlans[].durationDays', 'Integer', '会员有效天数(30/365)'],
- ['pricingPlans[].description', 'String', '方案描述']
- ],
- 'platformAccount': [
- ['id', 'Long', '记录ID'],
- ['userId', 'Long', '用户ID'],
- ['platformCode', 'String', '平台代码'],
- ['platformName', 'String', '平台名称'],
- ['accountEncrypted', 'String', '加密账号'],
- ['passwordEncrypted', 'String', '加密密码'],
- ['enabled', 'Boolean', '是否启用'],
- ['lastVerifyTime', 'DateTime', '最后验证时间'],
- ['verifyStatus', 'String', '验证状态(VERIFIED/FAILED/PENDING)'],
- ['failReason', 'String', '失败原因'],
- ['createTime', 'DateTime', '创建时间'],
- ['updateTime', 'DateTime', '更新时间']
- ],
- 'businessLicense': [
- ['id', 'Long', '记录ID'],
- ['userId', 'Long', '用户ID'],
- ['licenseImageUrl', 'String', '执照图片URL'],
- ['creditCode', 'String', '统一社会信用代码'],
- ['pharmacyName', 'String', '药店名称'],
- ['legalPerson', 'String', '法人代表'],
- ['registeredAddress', 'String', '注册地址'],
- ['businessScope', 'String', '经营范围'],
- ['validFrom', 'Date', '有效期开始'],
- ['validTo', 'Date', '有效期结束'],
- ['reviewStatus', 'String', '审核状态(PENDING/APPROVED/REJECTED)'],
- ['reviewerId', 'Long', '审核人ID'],
- ['reviewTime', 'DateTime', '审核时间'],
- ['rejectReason', 'String', '驳回原因'],
- ['showVerifiedBadge', 'Boolean', '是否展示认证徽章'],
- ['remark', 'String', '备注'],
- ['createTime', 'DateTime', '创建时间'],
- ['updateTime', 'DateTime', '更新时间']
- ],
- 'platformConfig': [
- ['id', 'Long', '配置ID'],
- ['platformCode', 'String', '平台代码(如yaoshibang)'],
- ['platformName', 'String', '平台名称'],
- ['enabled', 'Boolean', '是否启用'],
- ['requiresRecharge', 'Boolean', '是否需要充值'],
- ['queryCost', 'Integer', '每次查询消耗'],
- ['priority', 'Integer', '优先级'],
- ['underMaintenance', 'Boolean', '是否维护中'],
- ['maintenanceNotice', 'String', '维护公告'],
- ['description', 'String', '平台描述'],
- ['officialUrl', 'String', '官方网址'],
- ['bindingInstructions', 'String', '绑定说明'],
- ['sortOrder', 'Integer', '排序'],
- ['createTime', 'DateTime', '创建时间'],
- ['updateTime', 'DateTime', '更新时间'],
- ['createdBy', 'String', '创建人'],
- ['updatedBy', 'String', '更新人']
- ],
- 'trialQuota': [
- ['id', 'Long', '配置ID'],
- ['configName', 'String', '配置名称'],
- ['trialDays', 'Integer', '体验天数'],
- ['dailyQueryLimit', 'Integer', '每日查询限制'],
- ['monthlyWatchlistLimit', 'Integer', '月关注限制'],
- ['enabled', 'Boolean', '是否启用'],
- ['configType', 'String', '配置类型(DEFAULT/CUSTOM)'],
- ['applicableUserType', 'String', '适用用户类型(NEW_USER/ALL)'],
- ['effectiveFrom', 'DateTime', '生效开始时间'],
- ['effectiveTo', 'DateTime', '生效结束时间'],
- ['maxUsers', 'Integer', '最大用户数'],
- ['currentUsers', 'Integer', '当前用户数'],
- ['priority', 'Integer', '优先级'],
- ['description', 'String', '描述'],
- ['remark', 'String', '备注'],
- ['createdBy', 'String', '创建人'],
- ['updatedBy', 'String', '更新人'],
- ['createTime', 'DateTime', '创建时间'],
- ['updateTime', 'DateTime', '更新时间']
- ],
- 'inviteConfig': [
- ['id', 'Long', '配置ID'],
- ['rewardCrawlerCount', 'Integer', '每次邀请奖励爬虫次数(历史字段)'],
- ['rewardType', 'String', '奖励类型(MEMBERSHIP等)'],
- ['rewardMonths', 'Integer', '邀请人奖励会员时长(月)'],
- ['maxDailyReward', 'Integer', '每日最大奖励次数'],
- ['maxTotalInvites', 'Integer', '单个账号总邀请上限(默认30)'],
- ['inviteCodeExpireDays', 'Integer', '邀请码过期天数'],
- ['maxInvitePerDay', 'Integer', '每日最大邀请数'],
- ['appDownloadUrl', 'String', 'APP下载地址'],
- ['miniappPath', 'String', '小程序路径'],
- ['miniappAppId', 'String', '小程序AppID'],
- ['wechatRedirectUrl', 'String', '微信跳转URL'],
- ['dingtalkAppId', 'String', '钉钉AppID'],
- ['feishuAppId', 'String', '飞书AppID'],
- ['landingTitle', 'String', '落地页标题(支持{inviter}{pharmacy}占位符)'],
- ['landingDesc', 'String', '落地页描述'],
- ['appName', 'String', '应用名称'],
- ['shareTitleTemplate', 'String', '分享标题模板(支持{inviter}{appName})'],
- ['copyTextTemplate', 'String', '一键复制文案模板(支持{inviter}{appName}{inviteLink}{code})'],
- ['rewardDescription', 'String', '奖励说明文案(支持{rewardMonths})'],
- ['summaryTextTemplate', 'String', '统计摘要模板(支持{totalInvited}{registeredCount}{totalReward})'],
- ['instructionWindows', 'String', 'Windows渠道提示文案(支持{code})'],
- ['instructionApp', 'String', 'App渠道提示文案(支持{code})'],
- ['instructionMiniapp', 'String', '小程序渠道提示文案(支持{code})'],
- ['instructionWechat', 'String', '微信渠道提示文案(支持{code})'],
- ['instructionDingtalk', 'String', '钉钉渠道提示文案(支持{code}{appName})'],
- ['instructionFeishu', 'String', '飞书渠道提示文案(支持{code}{appName})'],
- ['instructionDefault', 'String', '默认渠道提示文案(支持{code})'],
- ['status', 'String', '状态(ACTIVE/INACTIVE)'],
- ['remark', 'String', '备注'],
- ['createTime', 'DateTime', '创建时间'],
- ['updateTime', 'DateTime', '更新时间']
- ],
- 'quotaGrant': [
- ['id', 'Long', '发放记录ID'],
- ['userId', 'Long', '用户ID'],
- ['grantType', 'String', '来源类型(ADMIN/INVITE/PURCHASE)'],
- ['quotaCount', 'Integer', '发放配额数'],
- ['usedCount', 'Integer', '已使用数'],
- ['expireTime', 'DateTime', '过期时间'],
- ['sourceUserId', 'Long', '来源用户ID(邀请人)'],
- ['remark', 'String', '备注'],
- ['status', 'String', '状态(ACTIVE/EXPIRED/USED_UP)'],
- ['createTime', 'DateTime', '创建时间'],
- ['updateTime', 'DateTime', '更新时间']
- ],
- 'crawlerUserDetail': [
- ['userId', 'Long', '用户ID'],
- ['nickname', 'String', '昵称'],
- ['phone', 'String', '手机号'],
- ['todayTotalUsage', 'Integer', '今日总使用次数'],
- ['monthlyTotalUsage', 'Integer', '本月总使用次数'],
- ['platformStats', 'Array', '各平台使用统计(见下方)'],
- ['platformStats[].platformCode', 'String', '平台代码'],
- ['platformStats[].platformName', 'String', '平台名称'],
- ['platformStats[].todayUsage', 'Integer', '今日使用'],
- ['platformStats[].monthlyUsage', 'Integer', '本月使用'],
- ['platformStats[].totalUsage', 'Integer', '累计使用']
- ],
- 'conversionStats': [
- ['inviteCode', 'String', '邀请码'],
- ['clickedCount', 'Integer', '链接点击次数'],
- ['totalInvited', 'Integer', '总邀请数'],
- ['registeredCount', 'Integer', '注册人数'],
- ['pendingCount', 'Integer', '待审核数'],
- ['conversionRate', 'Double', '转化率(%)'],
- ['totalReward', 'Integer', '总奖励数']
- ],
- 'auditLog': [
- ['id', 'Long', '日志ID'],
- ['operatorId', 'Long', '操作人ID'],
- ['operatorName', 'String', '操作人姓名'],
- ['operatorRole', 'String', '操作人角色'],
- ['operationTime', 'DateTime', '操作时间'],
- ['ipAddress', 'String', '操作IP'],
- ['userAgent', 'String', '浏览器UA'],
- ['module', 'String', '模块(USER/INVITE/CRAWLER等)'],
- ['operationType', 'String', '操作类型(CREATE/UPDATE/DELETE等)'],
- ['description', 'String', '操作描述'],
- ['targetType', 'String', '目标类型'],
- ['targetId', 'Long', '目标ID'],
- ['targetIdentifier', 'String', '目标标识'],
- ['beforeData', 'Object', '变更前数据'],
- ['afterData', 'Object', '变更后数据'],
- ['result', 'String', '操作结果(SUCCESS/FAILED)'],
- ['errorMessage', 'String', '错误信息'],
- ['requestUrl', 'String', '请求URL'],
- ['requestMethod', 'String', '请求方法'],
- ['requestParams', 'String', '请求参数'],
- ['responseCode', 'Integer', '响应码'],
- ['duration', 'Long', '耗时(ms)'],
- ['remark', 'String', '备注'],
- ['isSensitive', 'Boolean', '是否敏感操作'],
- ['createTime', 'DateTime', '创建时间']
- ],
- 'inviteLinkResolve': [
- ['inviteCode', 'String', '邀请码'],
- ['inviteLink', 'String', '完整邀请链接'],
- ['inviterNickname', 'String', '邀请人昵称'],
- ['inviterAvatar', 'String', '邀请人头像'],
- ['appName', 'String', '应用名称'],
- ['landingTitle', 'String', '落地页标题'],
- ['landingDesc', 'String', '落地页描述']
- ],
- 'anxinCard': [
- ['id', 'Long', '本地记录ID'],
- ['planId', 'Long', '关联支付方案ID'],
- ['planName', 'String', '支付方案名称'],
- ['cardId', 'String', '支付宝卡模板ID'],
- ['cardName', 'String', '卡模板名称'],
- ['cardType', 'String', '卡类型(PERIOD_PAY=周期卡)'],
- ['periodAmount', 'BigDecimal', '每期金额(元)'],
- ['periodDays', 'Integer', '周期天数'],
- ['totalPeriods', 'Integer', '总期数'],
- ['status', 'String', '状态(PENDING/EFFECTIVE/INVALID/FROZEN)'],
- ['appointmentUrl', 'String', '预约链接(小程序购买入口)'],
- ['createTime', 'DateTime', '创建时间'],
- ['updateTime', 'DateTime', '更新时间']
- ],
- 'agreementSignResponse': [
- ['agreementId', 'Long', '本地协议ID'],
- ['externalAgreementNo', 'String', '商家自定义协议号(AG开头)'],
- ['signUrl', 'String', '签约页URL(前端跳转/展示)'],
- ['qrContent', 'String', '二维码内容(扫码签约)'],
- ['hint', 'String', '提示文案']
- ],
- 'agreementResponse': [
- ['id', 'Long', '协议ID'],
- ['userId', 'Long', '用户ID'],
- ['planId', 'Long', '支付方案ID'],
- ['planName', 'String', '方案名称'],
- ['channel', 'String', '渠道(WECHAT)'],
- ['periodType', 'String', '周期类型(MONTH/YEAR)'],
- ['periodAmount', 'BigDecimal', '每期扣款金额'],
- ['status', 'String', '状态(PENDING/SIGNED/UNSIGNED/FAILED)'],
- ['agreementNo', 'String', '第三方协议号'],
- ['signTime', 'DateTime', '签约时间'],
- ['unsignTime', 'DateTime', '解约时间'],
- ['nextDeductTime', 'DateTime', '下次扣款时间'],
- ['lastDeductTime', 'DateTime', '上次扣款时间'],
- ['consecutiveFailCount', 'Integer', '连续失败次数(达3次自动解约)'],
- ['unsignReason', 'String', '解约原因'],
- ['createTime', 'DateTime', '创建时间']
- ],
- 'searchRecordResponse': [
- ['id', 'Long', '记录ID(雪花算法)'],
- ['drugName', 'String', '药品名称'],
- ['searchedAt', 'DateTime', '搜索时间(yyyy-MM-dd HH:mm:ss)'],
- ['statusText', 'String', '状态文案(如:已返回 2 个平台的可比价结果)'],
- ['platformCount', 'Integer', '涉及平台数量(后端自动从platformResults计算)'],
- ['quoteCount', 'Integer', '总报价条数(后端自动从platformResults计算)'],
- ['platformResults', 'Object/Array', '平台搜索结果(结构化JSON对象,仅详情接口返回,列表接口为null)'],
- ['searchLog', 'Object/null', '搜索日志(仅详情接口返回,列表接口为null)']
- ],
- };
- function renderFieldDesc(descKey) {
- const fields = FIELD_DESC[descKey];
- if (!fields) return '';
- let rows = fields.map(f => `<tr><td><code>${f[0]}</code></td><td>${f[1]}</td><td>${f[2]}</td></tr>`).join('');
- return `<div class="field-desc-box">
- <div class="field-desc-title">返回字段说明</div>
- <table class="field-desc-table"><tr><th>字段名</th><th>类型</th><th>说明</th></tr>${rows}</table>
- </div>`;
- }
- // ==================== 各模块HTML ====================
- function testSection(id, title, method, methodClass, path, authTag, authClass, fields, hasValidator, descKey, desc) {
- const authLabel = authTag === '需认证' ? '需认证' : '公开';
- const ac = authTag === '需认证' ? 'auth-required' : 'auth-public';
- const side = (path.includes('/api/admin') || path === '/api/coupon/issue') ? 'admin' : 'user';
- const sideClass = side === 'admin' ? 'side-admin' : 'side-user';
- const sideLabel = side === 'admin' ? '运营侧' : '用户侧';
- let fieldsHtml = '';
- if (fields) {
- fieldsHtml = '<div class="form-grid">';
- fields.forEach(f => {
- const escapedDefault = (f.default || '').replace(/"/g, '"');
- const escapedPlaceholder = (f.placeholder || '').replace(/"/g, '"');
- const hasRaw = f.default && f.default.includes('"');
- fieldsHtml += `<div class="form-group"><label>${f.label}</label><input type="text" id="${id}-${f.name}" placeholder="${escapedPlaceholder}" value="${escapedDefault}"${hasRaw ? ` data-raw="${escapedDefault}"` : ''}></div>`;
- });
- fieldsHtml += '</div>';
- }
- const descHtml = desc ? `<div class="section-desc">${desc}</div>` : '';
- const fieldDescHtml = descKey ? renderFieldDesc(descKey) : '';
- return `
- <div class="test-section">
- <div class="section-title">
- <span class="method method-${methodClass}">${method}</span>
- <span class="path">${path}</span>
- <span class="side-tag ${sideClass}">${sideLabel}</span>
- <span class="${ac} auth-tag">${authLabel}</span>
- </div>
- ${descHtml}
- ${fieldsHtml}
- <button class="btn btn-primary" onclick="runTest('${id}', '${method}', '${path}', ${buildBody(id, fields)}, ${authTag === '公开' ? 'false' : 'true'}, ${hasValidator ? `'${toValidatorName(id)}'` : 'null'})">执行测试</button>
- <div class="response-box" id="${id}-box" style="display:none;">
- <div class="response-header">
- <span class="response-title">响应 <span id="${id}-duration" style="color:#999;font-weight:400;"></span></span>
- <span class="status-badge" id="${id}-status"></span>
- </div>
- <div class="response-content" id="${id}-raw"></div>
- ${fieldDescHtml}
- </div>
- </div>`;
- }
- function buildBody(id, fields) {
- if (!fields || fields.length === 0) return 'null';
- let parts = [];
- fields.forEach(f => {
- const forceArray = f.type === 'array';
- const asJson = f.type === 'json';
- const forceString = f.type === 'string';
- if (asJson) {
- parts.push(`"${f.name}": safeJsonParse(document.getElementById('${id}-${f.name}').value)`);
- } else if (forceString) {
- parts.push(`"${f.name}": document.getElementById('${id}-${f.name}').value || null`);
- } else {
- parts.push(`"${f.name}": parseFieldValue(document.getElementById('${id}-${f.name}').value, ${forceArray})`);
- }
- });
- return `{${parts.join(',')}}`;
- }
- /**
- * 智能解析字段值:返回真实类型的JS值(非源代码字符串)
- * 逗号分隔→数组,true/false→布尔,数字(含小数)→数值,否则→字符串
- * @param forceArray 为true时,单个值也包装为数组(如userIds填1→[1])
- */
- function parseFieldValue(val, forceArray) {
- if (val === '' || val === undefined || val === null) return forceArray ? [] : null;
- // 布尔
- if (val === 'true') return forceArray ? [true] : true;
- if (val === 'false') return forceArray ? [false] : false;
- // 逗号分隔的数组
- if (val.includes(',')) {
- const items = val.split(',').map(v => v.trim()).filter(v => v !== '');
- return items.map(v => toNumberIfSafe(v));
- }
- // 数字(整数或小数,如 1, 9.90, 0.5);大整数(雪花ID)保持字符串
- if (/^\d+(\.\d+)?$/.test(val)) return forceArray ? [toNumberIfSafe(val)] : toNumberIfSafe(val);
- // 字符串
- return forceArray ? [val] : val;
- }
- /**
- * 尝试转为Number,但大整数(超出JS安全范围如雪花ID)保持字符串避免精度丢失
- */
- function toNumberIfSafe(v) {
- if (!/^\d+(\.\d+)?$/.test(v)) return v;
- const num = Number(v);
- // 整数且超出安全范围(如雪花ID 2069620495151230978) → 保持字符串
- if (Number.isInteger(num) && !Number.isSafeInteger(num)) return v;
- return num;
- }
- function renderAuthModule() {
- return `
- <div class="batch-buttons">
- <button class="btn btn-warning btn-sm" onclick="runAuthTests()">运行全部认证模块测试</button>
- </div>
- ${testSection('auth-send', '发送验证码', 'POST', 'post', '/api/auth/sms/send', '公开', 'auth-public',
- [{label:'手机号(phone)', name:'phone', placeholder:'18776156917', default:'18776156917'},
- {label:'场景(scene)', name:'scene', placeholder:'login', default:'login'}], true, null, '发送短信验证码,开发环境可能不实际发送')}
- ${testSection('auth-smslogin', '短信登录', 'POST', 'post', '/api/auth/sms/login', '公开', 'auth-public',
- [{label:'手机号(phone)', name:'phone', placeholder:'18776156917', default:'18776156917'},
- {label:'验证码(code)', name:'code', placeholder:'123456', default:'123456'},
- {label:'邀请码(inviteCode,可选)', name:'inviteCode', placeholder:'ABC123', default:''},
- {label:'场景(scene)', name:'scene', placeholder:'login', default:'login'}], true, 'loginResponse', '验证码登录/注册,支持附带邀请码自动绑定邀请关系')}
- ${testSection('auth-pwdlogin', '密码登录', 'POST', 'post', '/api/auth/password/login', '公开', 'auth-public',
- [{label:'手机号(phone)', name:'phone', placeholder:'18776156917', default:'18776156917'},
- {label:'密码(password)', name:'password', placeholder:'your_password', default:'123456'}], true, 'loginResponse', '使用手机号+密码登录,需先通过设置密码接口创建密码')}
- ${testSection('auth-wechat-url', '微信扫码URL', 'GET', 'get', '/api/auth/wechat/url?state=login', '公开', 'auth-public', null, false, null, '获取微信扫码登录跳转URL')}
- ${testSection('auth-wechat-login', '微信登录', 'POST', 'post', '/api/auth/wechat/login', '公开', 'auth-public',
- [{label:'微信code(code)', name:'code', placeholder:'微信授权code', default:'test_wechat_code'},
- {label:'state(state)', name:'state', placeholder:'login', default:'login'}], true, 'loginResponse', '微信扫码登录后回调,传入微信授权code')}
- ${testSection('auth-wechat-bind', '微信绑定手机', 'POST', 'post', '/api/auth/wechat/bind', '需认证', 'auth-required',
- [{label:'手机号(phone)', name:'phone', placeholder:'18776156917', default:'18776156917'},
- {label:'验证码(code)', name:'code', placeholder:'123456', default:'123456'},
- {label:'临时令牌(tempToken)', name:'tempToken', placeholder:'微信登录后获取的tempToken', default:''},
- {label:'邀请码(inviteCode,可选)', name:'inviteCode', placeholder:'ABC123', default:''}], true, 'loginResponse', '已有微信登录的用户绑定手机号')}
- ${testSection('auth-wechat-callback', '微信回调', 'GET', 'get', '/api/auth/wechat/callback?code=test_code', '公开', 'auth-public', null, false, null, '微信扫码登录前端跳转回调')}
- ${testSection('auth-refresh', '刷新Token', 'POST', 'post', '/api/auth/refresh', '公开', 'auth-public',
- [{label:'refreshToken(refreshToken)', name:'refreshToken', placeholder:'输入refreshToken', default:''}], false, 'loginResponse', '使用refreshToken获取新的accessToken(Refresh Token一次性旋转,重用即被踢)')}
- ${testSection('auth-heartbeat', '心跳保活', 'POST', 'post', '/api/auth/heartbeat', '需认证', 'auth-required', null, true, 'loginResponse', '客户端定时调用保持登录态(建议4分钟一次),返回新Token对。旧Token自然过期前仍可用')}
- ${testSection('auth-userinfo', '获取用户信息', 'GET', 'get', '/api/auth/user/info', '需认证', 'auth-required', null, true, 'userInfo', '获取当前登录用户的基本信息')}
- ${testSection('auth-setpwd', '设置密码', 'POST', 'post', '/api/auth/password/set', '需认证', 'auth-required',
- [{label:'旧密码(oldPassword,可选)', name:'oldPassword', placeholder:'首次设置可不填', default:''},
- {label:'新密码(newPassword)', name:'newPassword', placeholder:'new_password', default:'Abc123456'}], false, null, '首次设置密码或修改密码')}
- ${testSection('auth-change-phone', '修改手机号', 'POST', 'post', '/api/auth/phone/change', '需认证', 'auth-required',
- [{label:'新手机号(newPhone)', name:'newPhone', placeholder:'13900139000', default:'13900139000'},
- {label:'验证码(code)', name:'code', placeholder:'123456', default:'123456'}], false, null, '修改绑定手机号,需先点击发送验证码获取新手机验证码')}
- <div style="margin-top:-8px;margin-bottom:14px;">
- <button class="btn btn-primary btn-sm" onclick="sendCodeToField('auth-change-phone-newPhone','change_phone')">发送验证码到新手机</button>
- <span style="font-size:11px;color:#888;margin-left:8px;">开发环境固定验证码: 123456</span>
- </div>`;
- }
- function renderCrawlerModule() {
- return `
- <div class="batch-buttons">
- <button class="btn btn-warning btn-sm" onclick="runCrawlerTests()">运行全部爬虫配额测试</button>
- </div>
- ${testSection('crawler-status', '配额状态', 'GET', 'get', '/api/crawler/status', '需认证', 'auth-required', null, true, 'crawlerStatus', '获取当前用户的爬虫配额使用情况')}
- ${testSection('crawler-consume', '消耗配额', 'POST', 'post', '/api/crawler/consume', '需认证', 'auth-required',
- [{label:'次数(count)', name:'count', placeholder:'1', default:'1'},
- {label:'优先优惠券(useCouponFirst)', name:'useCouponFirst', placeholder:'true/false', default:'false'},
- {label:'平台代码(platform)', name:'platform', placeholder:'yaoshibang 或 yaoshibang,yiyaocheng', default:'yaoshibang', type:'string'}], true, null, '前端启动爬虫前调用,扣减配额次数。platform仅用于日志统计,不做限制,支持逗号分隔传入多平台')}
- ${testSection('crawler-logs', '使用记录', 'GET', 'get', '/api/crawler/logs?days=7', '需认证', 'auth-required', null, true, 'crawlerLog', '查询爬虫使用历史记录')}`;
- }
- function renderInviteModule() {
- return `
- <div class="batch-buttons">
- <button class="btn btn-warning btn-sm" onclick="runInviteTests()">运行全部邀请系统测试</button>
- </div>
- ${testSection('invite-code', '获取邀请码', 'GET', 'get', '/api/invite/code', '需认证', 'auth-required', null, true, 'inviteCode', '获取当前用户的邀请码和邀请链接')}
- ${testSection('invite-stats', '邀请统计', 'GET', 'get', '/api/invite/stats', '需认证', 'auth-required', null, true, 'inviteStats', '获取邀请人数、注册人数、累计奖励等统计')}
- ${testSection('invite-rewards', '邀请记录', 'GET', 'get', '/api/invite/rewards', '需认证', 'auth-required', null, true, 'inviteReward', '获取邀请记录列表,含被邀请人注册状态')}
- ${testSection('invite-extra-quota', '额外配额', 'GET', 'get', '/api/invite/extra-quota', '需认证', 'auth-required', null, false, null, '获取可用额外爬虫次数(邀请奖励+运营发放),返回Integer数值')}
- ${testSection('invite-my-inviter', '我的邀请人', 'GET', 'get', '/api/invite/my-inviter', '需认证', 'auth-required', null, true, 'myInviter', '查询是谁邀请了我(被邀请人视角),未被邀请返回invited=false')}
- ${testSection('invite-bind', '补填邀请码', 'POST', 'post', '/api/invite/bind', '需认证', 'auth-required',
- [{label:'邀请码/链接(inviteCode)', name:'inviteCode', placeholder:'A3K7M9P2 或 https://app.zhijiayun.com/invite/A3K7M9P2', default:''}], true, null, '注册时未填邀请码,注册后30天内可补填。补填后邀请人获赠1个月高级会员时长')}
- ${testSection('invite-page', '落地页', 'GET', 'get', '/api/invite/page/{code}?channel=windows', '公开', 'auth-public',
- [{label:'邀请码(code)', name:'code', placeholder:'输入邀请码', default:''},
- {label:'渠道(channel)', name:'channel', placeholder:'windows/app/wechat/miniapp/dingtalk/feishu', default:'windows'}], true, 'invitePage', '浏览器打开邀请链接时展示的落地页数据,默认渠道为windows(Windows桌面客户端)')}
- ${testSection('invite-link-entry', '邀请链接入口(直接下载)', 'GET', 'get', '/api/invite/{code}', '公开', 'auth-public',
- [{label:'邀请码(code)', name:'code', placeholder:'输入邀请码', default:''}], true, 'invitePage', '浏览器直接访问邀请链接,服务端根据 User-Agent 流式返回对应桌面客户端安装包(Windows x64 -> .exe / macOS ARM64 -> .dmg),响应头 Content-Disposition: attachment,并自动计入点击数')}
- ${testSection('invite-click', '点击追踪', 'POST', 'post', '/api/invite/click/{code}', '公开', 'auth-public',
- [{label:'邀请码(code)', name:'code', placeholder:'输入邀请码', default:''}], false, null, '追踪邀请链接被打开事件')}
- ${testSection('invite-resolve', '解析链接', 'GET', 'get', '/api/invite/resolve-link?link=', '公开', 'auth-public',
- [{label:'链接/邀请码(link)', name:'link', placeholder:'https://app.zhijiayun.com/invite/ABC123 或 ABC123', default:''}], false, 'inviteLinkResolve', '解析邀请链接或纯邀请码,识别出邀请人和邀请码')}`;
- }
- function renderCouponModule() {
- return `
- <div class="batch-buttons">
- <button class="btn btn-warning btn-sm" onclick="runCouponTests()">运行全部优惠券测试</button>
- </div>
- ${testSection('coupon-my', '我的优惠券', 'GET', 'get', '/api/coupon/my?status=0', '需认证', 'auth-required', null, true, 'coupon', '查询当前用户的优惠券列表,可按状态筛选')}
- ${testSection('coupon-available', '可用优惠券', 'GET', 'get', '/api/coupon/my/available?type=CRAWLER', '需认证', 'auth-required', null, true, 'coupon', '查询当前用户可用的优惠券,可按类型筛选')}`;
- }
- function renderMembershipModule() {
- return `
- <div class="batch-buttons">
- <button class="btn btn-warning btn-sm" onclick="runMembershipTests()">运行全部会员等级测试</button>
- </div>
- ${testSection('membership-my', '我的会员', 'GET', 'get', '/api/membership/my', '需认证', 'auth-required', null, true, 'membership', '获取当前登录用户的会员等级、有效期与配额信息')}
- ${testSection('membership-benefits', '会员权益', 'GET', 'get', '/api/membership/benefits?level=PLUS', '需认证', 'auth-required',
- [{label:'等级(level)', name:'level', placeholder:'PLUS/PRO/ULTRA', default:'PLUS'}], true, 'membershipBenefit', '查询指定会员等级的权益列表')}
- ${testSection('membership-levels', '等级列表(公开)', 'GET', 'get', '/api/membership/levels', '公开', 'auth-public', null, true, 'levelResponse', '获取所有会员等级列表(PLUS/PRO/ULTRA),含定价方案pricingPlans')}
- ${testSection('membership-level-detail', '等级详情(公开)', 'GET', 'get', '/api/membership/levels/{id}', '公开', 'auth-public',
- [{label:'等级ID(id)', name:'id', placeholder:'1', default:'1'}], true, 'levelResponse', '获取指定等级详情,含定价方案与配额')}`;
- }
- function renderWatchlistModule() {
- return `
- <div class="batch-buttons">
- <button class="btn btn-warning btn-sm" onclick="runWatchlistTests()">运行全部关注列表测试</button>
- </div>
- ${testSection('watchlist-add', '添加关注', 'POST', 'post', '/api/watchlist', '需认证', 'auth-required',
- [{label:'药品名称(drugName)', name:'drugName', placeholder:'阿莫西林胶囊', default:'阿莫西林胶囊'},
- {label:'规格(spec)', name:'spec', placeholder:'0.25g×24片', default:''},
- {label:'厂家(manufacturer)', name:'manufacturer', placeholder:'石药集团', default:''}], true, null, '添加药品到关注列表')}
- ${testSection('watchlist-list', '关注列表', 'GET', 'get', '/api/watchlist', '需认证', 'auth-required', null, true, null, '获取当前用户所有关注的药品列表')}
- ${testSection('watchlist-delete', '取消关注', 'DELETE', 'delete', '/api/watchlist/{id}', '需认证', 'auth-required',
- [{label:'关注ID(id)', name:'id', placeholder:'1', default:'1'}], false, null, '从关注列表中删除')}
- ${testSection('watchlist-stats', '关注统计', 'GET', 'get', '/api/watchlist/stats', '需认证', 'auth-required', null, true, null, '获取关注统计信息')}`;
- }
- function renderTrialQuotaModule() {
- return `
- <div class="batch-buttons">
- <button class="btn btn-warning btn-sm" onclick="runTrialQuotaTests()">运行全部体验配额测试</button>
- </div>
- ${testSection('trial-quota-active', '生效中的体验配额', 'GET', 'get', '/api/trial-quota/active', '公开', 'auth-public', null, true, 'trialQuotaConfig', '查询当前生效的体验配额配置')}`;
- }
- function renderPaymentModule() {
- return `
- <div class="batch-buttons">
- <button class="btn btn-warning btn-sm" onclick="runPaymentTests()">运行全部支付模块测试</button>
- </div>
- ${testSection('payment-plans', '支付方案列表', 'GET', 'get', '/api/payment/plans', '公开', 'auth-public', null, true, null, '获取所有可用的支付方案列表(高级会员月卡/年卡)')}
- ${testSection('payment-create-order', '创建支付订单', 'POST', 'post', '/api/payment/order/create', '需认证', 'auth-required',
- [{label:'方案ID(planId)', name:'planId', placeholder:'1', default:'1'},
- {label:'支付渠道(channel)', name:'channel', placeholder:'WECHAT/ALIPAY', default:'WECHAT'}], true, null, '创建支付订单,微信返回二维码(qrCodeBase64)供扫码支付,支付宝通过alipay.trade.precreate返回qr_code,前端自定义弹窗展示二维码并自动轮询支付状态。15分钟内未付款订单自动过期')}
- ${testSection('payment-query-order', '查询订单', 'GET', 'get', '/api/payment/order/{orderNo}', '需认证', 'auth-required',
- [{label:'订单号(orderNo)', name:'orderNo', placeholder:'OP...', default:''}], true, null, '根据订单号查询支付订单详情,待支付订单会返回当前渠道二维码qrCodeBase64和payUrl')}
- ${testSection('payment-switch-channel', '切换支付渠道', 'POST', 'post', '/api/payment/order/{orderNo}/switch-channel', '需认证', 'auth-required',
- [{label:'订单号(orderNo)', name:'orderNo', placeholder:'OP...', default:''},
- {label:'新渠道(channel)', name:'channel', placeholder:'WECHAT/ALIPAY', default:'ALIPAY'}], true, null, '订单详情页切换支付渠道,不关闭原渠道订单,重新生成新渠道二维码。15分钟内原渠道二维码仍有效,以第一个支付成功渠道为准')}
- ${testSection('payment-orders', '我的订单列表', 'GET', 'get', '/api/payment/orders', '需认证', 'auth-required', null, true, null, '查询当前用户的所有支付订单,待支付订单含expireHint过期提示')}
- ${testSection('payment-mock-pay', '手动确认支付', 'POST', 'post', '/api/payment/order/{orderNo}/pay', '需认证', 'auth-required',
- [{label:'订单号(orderNo)', name:'orderNo', placeholder:'OP...', default:''}], false, null, '开发测试用。mock模式=模拟支付自动激活会员;真实支付模式=手动确认支付(不实际扣款,用于测试回调流程)。需在15分钟过期前操作')}
- ${testSection('payment-callback-wechat', '微信回调', 'POST', 'post', '/api/payment/callback/wechat', '公开', 'auth-public',
- [{label:'订单号(out_trade_no)', name:'out_trade_no', placeholder:'OP...', default:''},
- {label:'交易号(transaction_id,可选)', name:'transaction_id', placeholder:'wx_', default:''}], false, null, '微信支付回调通知。mock模式从参数取订单号,真实支付模式需验签+解密(API v3)')}
- ${testSection('payment-callback-alipay', '支付宝回调', 'POST', 'post', '/api/payment/callback/alipay', '公开', 'auth-public',
- [{label:'订单号(out_trade_no)', name:'out_trade_no', placeholder:'OP...', default:''},
- {label:'交易号(trade_no,可选)', name:'trade_no', placeholder:'2024...', default:''}], false, null, '支付宝回调通知。mock模式从参数取订单号,真实支付模式需RSA2验签')}
- ${testSection('payment-agreement-sign', '开通微信委托代扣(签约)', 'POST', 'post', '/api/payment/agreement/sign', '需认证', 'auth-required',
- [{label:'方案ID(planId)', name:'planId', placeholder:'1', default:'1'},
- {label:'支付渠道(channel)', name:'channel', placeholder:'WECHAT', default:'WECHAT'},
- {label:'周期类型(periodType,可选)', name:'periodType', placeholder:'MONTH/YEAR', default:'MONTH'}], true, 'agreementSignResponse', '微信委托代扣签约。返回签约链接(signUrl)和二维码内容(qrContent),引导用户完成签约。mock模式自动签约成功,10秒后触发首次扣款。仅支持微信渠道(WECHAT),支付宝请使用安心付(小程序会员周期卡)')}
- ${testSection('payment-agreement-list', '我的自动续费协议', 'GET', 'get', '/api/payment/agreement/list', '需认证', 'auth-required', null, true, 'agreementResponse', '查询当前用户所有签约协议列表,含签约状态、下次扣款时间、连续失败次数等')}
- ${testSection('payment-agreement-detail', '协议详情', 'GET', 'get', '/api/payment/agreement/{agreementId}', '需认证', 'auth-required',
- [{label:'协议ID(agreementId)', name:'agreementId', placeholder:'签约后返回', default:''}], true, 'agreementResponse', '查询单个签约协议详情')}
- ${testSection('payment-agreement-unsign', '取消自动续费(解约)', 'POST', 'post', '/api/payment/agreement/{agreementId}/unsign', '需认证', 'auth-required',
- [{label:'协议ID(agreementId)', name:'agreementId', placeholder:'签约后返回', default:''}], false, null, '用户主动取消自动续费。会同步调用第三方解约接口(mock模式直接本地解约),解约后不再自动扣款')}
- ${testSection('payment-agreement-notify-wechat', '微信签约回调通知', 'POST', 'post', '/api/payment/agreement/notify/wechat?out_contract_code=AGxxx&contract_id=MOCK_xxx&change_type=ADD', '公开', 'auth-public',
- [{label:'协议号(out_contract_code)', name:'out_contract_code', placeholder:'签约后返回的externalAgreementNo', default:''},
- {label:'微信协议号(contract_id)', name:'contract_id', placeholder:'微信合同ID', default:'MOCK_AG_test'},
- {label:'变更类型(change_type)', name:'change_type', placeholder:'ADD/DELETE', default:'ADD'}], true, null, '微信委托代扣签约结果异步通知。mock模式返回success。真实模式为加密JSON,需WxPayService解密。ADD=签约成功,DELETE=解约')}
- ${testSection('payment-anxin-notify-deduct', '安心付扣款通知', 'POST', 'post', '/api/payment/anxin/notify/deduct?sub_order_id=AX_SUB_xxx&card_id=MOCK_CARD_xxx&deduct_status=SUCCESS&deduct_amount=9.90&deduct_time=2026-06-30 10:00:00&out_biz_no=1&user_id=2088xxx&order_id=AX_ORDER_xxx', '公开', 'auth-public',
- [{label:'核销子订单号(sub_order_id)', name:'sub_order_id', placeholder:'AX_SUB_...', default:'AX_SUB_TEST001'},
- {label:'卡模板ID(card_id)', name:'card_id', placeholder:'创建卡模板后返回', default:''},
- {label:'扣款状态(deduct_status)', name:'deduct_status', placeholder:'SUCCESS/FAIL', default:'SUCCESS'},
- {label:'扣款金额(deduct_amount)', name:'deduct_amount', placeholder:'9.90', default:'9.90'},
- {label:'扣款时间(deduct_time)', name:'deduct_time', placeholder:'yyyy-MM-dd HH:mm:ss', default:'2026-06-30 10:00:00'},
- {label:'系统用户ID(out_biz_no)', name:'out_biz_no', placeholder:'用户ID', default:'1'},
- {label:'支付宝用户ID(user_id)', name:'user_id', placeholder:'2088...', default:'2088000000000001'},
- {label:'售卖订单ID(order_id)', name:'order_id', placeholder:'AX_ORDER_...', default:'AX_ORDER_TEST001'}], true, null, '安心付周期扣款异步通知。支付宝每次周期扣款后发送,服务端收到后激活/续期会员。mock模式直接处理扣款并激活会员。需先创建卡模板')}
- ${testSection('payment-anxin-notify-card-change', '安心付卡变更通知', 'POST', 'post', '/api/payment/anxin/notify/card-change?card_id=MOCK_CARD_xxx&change_type=AUDIT&status=EFFECTIVE', '公开', 'auth-public',
- [{label:'卡模板ID(card_id)', name:'card_id', placeholder:'创建卡模板后返回', default:''},
- {label:'变更类型(change_type)', name:'change_type', placeholder:'AUDIT/STATUS_CHANGE', default:'AUDIT'},
- {label:'新状态(status)', name:'status', placeholder:'PENDING/EFFECTIVE/INVALID/FROZEN', default:'EFFECTIVE'}], true, null, '安心付卡模板审核结果通知。支付宝在卡模板审核通过/驳回/冻结/解冻时发送。mock模式直接更新本地状态')}`;
- }
- function renderUploadModule() {
- return `
- <div class="batch-buttons">
- <button class="btn btn-warning btn-sm" onclick="runUploadTests()">运行全部上传测试</button>
- </div>
- <div class="test-section">
- <div class="section-title">
- <span class="method method-post">POST</span>
- <span class="path">/api/upload</span>
- <span class="side-tag side-user">用户侧</span>
- <span class="auth-required auth-tag">需认证</span>
- </div>
- <div class="section-desc">上传单个文件,返回文件访问路径。使用 multipart/form-data 格式,字段名: file</div>
- <div class="form-grid">
- <div class="form-group">
- <label>选择文件</label>
- <input type="file" id="upload-file" accept="image/*,.pdf,.doc,.docx" style="padding:8px;">
- </div>
- </div>
- <button class="btn btn-primary" onclick="uploadFile()">上传文件</button>
- <div class="response-box" id="upload-result-box" style="display:none;">
- <div class="response-header">
- <span class="response-title">响应 <span id="upload-duration" style="color:#999;font-weight:400;"></span></span>
- <span class="status-badge" id="upload-status"></span>
- </div>
- <div class="response-content" id="upload-raw"></div>
- </div>
- </div>`;
- }
- function renderSearchModule() {
- return `
- <div class="batch-buttons">
- <button class="btn btn-warning btn-sm" onclick="runSearchTests()">运行全部搜索测试</button>
- </div>
- ${testSection('search-query', '药品搜索', 'POST', 'post', '/api/search/query', '需认证', 'auth-required',
- [{label:'关键词(keyword)', name:'keyword', placeholder:'阿莫西林胶囊', default:'阿莫西林胶囊'},
- {label:'平台列表(platforms,可选)', name:'platforms', placeholder:'yaoshibang,yaobangmang,yiyaocheng', default:'', type:'array'}], true, null, '搜索药品比价,消耗每日配额,返回各平台价格列表。platforms传多个平台代码用逗号分隔,不传则查询全部平台')}
- ${testSection('search-suggestions', '搜索建议', 'GET', 'get', '/api/search/suggestions', '公开', 'auth-public', null, true, null, '获取热门药品搜索建议列表(无需认证)')}`;
- }
- function renderPlatformAccountModule() {
- return `
- <div class="batch-buttons">
- <button class="btn btn-warning btn-sm" onclick="runPlatformAccountTests()">运行全部平台账号测试</button>
- </div>
- ${testSection('pa-bind', '绑定平台账号', 'POST', 'post', '/api/platform-account/bind', '需认证', 'auth-required',
- [{label:'平台代码(platformCode)', name:'platformCode', placeholder:'yaoshibang', default:'yaoshibang'},
- {label:'平台名称(platformName)', name:'platformName', placeholder:'药师帮', default:'药师帮'},
- {label:'账号(account)', name:'account', placeholder:'18776156917', default:'18776156917'},
- {label:'密码(password)', name:'password', placeholder:'your_password', default:'Abc123456'}], false, null, '绑定B2B药品平台账号')}
- ${testSection('pa-list', '已绑定列表', 'GET', 'get', '/api/platform-account/list', '需认证', 'auth-required', null, true, 'platformAccount', '查询已绑定的所有平台账号')}
- ${testSection('pa-verify', '验证账号', 'POST', 'post', '/api/platform-account/verify/{platformCode}', '需认证', 'auth-required',
- [{label:'平台代码(platformCode)', name:'platformCode', placeholder:'yaoshibang', default:'yaoshibang'}], false, null, '验证已绑定的平台账号是否有效')}
- ${testSection('pa-unbind', '解绑账号', 'DELETE', 'delete', '/api/platform-account/unbind/{platformCode}', '需认证', 'auth-required',
- [{label:'平台代码(platformCode)', name:'platformCode', placeholder:'yaoshibang', default:'yaoshibang'}], false, null, '解绑指定平台账号')}`;
- }
- function renderBusinessLicenseModule() {
- return `
- <div class="batch-buttons">
- <button class="btn btn-warning btn-sm" onclick="runBusinessLicenseTests()">运行全部营业执照测试</button>
- </div>
- ${testSection('bl-upload', '上传执照(JSON)', 'POST', 'post', '/api/business-license/upload', '需认证', 'auth-required',
- [{label:'图片URL(licenseImageUrl)', name:'licenseImageUrl', placeholder:'https://oss.example.com/license/xxx.jpg', default:'https://oss.example.com/license/test.jpg'},
- {label:'信用代码(creditCode)', name:'creditCode', placeholder:'91110000XXXXXXXXXX', default:'91110000TEST000001'},
- {label:'药店名称(pharmacyName)', name:'pharmacyName', placeholder:'XX大药房', default:'测试大药房'},
- {label:'法人(legalPerson,可选)', name:'legalPerson', placeholder:'张三', default:'张三'}], false, null, '通过JSON方式上传营业执照(传图片URL)')}
- ${testSection('bl-info', '查询执照信息', 'GET', 'get', '/api/business-license/info', '需认证', 'auth-required', null, true, 'businessLicense', '查询当前用户已上传的营业执照信息')}`;
- }
- function renderPlatformConfigModule() {
- return `
- <div class="batch-buttons">
- <button class="btn btn-warning btn-sm" onclick="runPlatformConfigTests()">运行全部平台配置测试</button>
- </div>
- ${testSection('pc-enabled', '启用平台列表', 'GET', 'get', '/api/platform-config/enabled', '公开', 'auth-public', null, true, 'platformConfig', '获取所有已启用的爬虫平台配置列表')}
- ${testSection('pc-detail', '平台详情', 'GET', 'get', '/api/platform-config/{platformCode}', '公开', 'auth-public',
- [{label:'平台代码(platformCode)', name:'platformCode', placeholder:'yaoshibang', default:'yaoshibang'}], true, 'platformConfig', '获取指定平台的详细配置信息')}`;
- }
- function renderSearchRecordModule() {
- return `
- <div class="batch-buttons">
- <button class="btn btn-warning btn-sm" onclick="runSearchRecordTests()">运行全部搜索记录测试</button>
- </div>
- ${testSection('sr-save', '保存搜索记录', 'POST', 'post', '/api/search-record/history', '需认证', 'auth-required',
- [{label:'药品名称(drugName)', name:'drugName', placeholder:'阿莫西林', default:'阿莫西林'},
- {label:'搜索时间(searchedAt)', name:'searchedAt', placeholder:'yyyy-MM-dd HH:mm:ss', default:'2026-06-26 08:30:00'},
- {label:'状态文案(statusText)', name:'statusText', placeholder:'已返回 2 个平台的可比价结果。', default:'已返回 2 个平台的可比价结果。'},
- {label:'平台结果(platformResults)', name:'platformResults', placeholder:'[{"platformId":"yiyaocheng","platformName":"壹药城"}]', default:'[{"platformId":"yiyaocheng","platformName":"壹药城","response":{"status":"ok","products":[{"drugName":"阿莫西林","price":12.5}]}}]', type:'json'},
- {label:'搜索日志(searchLog)', name:'searchLog', placeholder:'null', default:'null', type:'json'}], true, 'searchRecordResponse', '保存一次完整的比价搜索快照。platformResults为JSON数组,每个元素含platformId/platformName/response;后端会自动计算platformCount(平台数)和quoteCount(报价条数)。searchedAt格式:yyyy-MM-dd HH:mm:ss')}
- ${testSection('sr-list', '搜索记录列表', 'GET', 'get', '/api/search-record/history?pageNo={pageNo}&pageSize={pageSize}', '需认证', 'auth-required',
- [{label:'页码(pageNo)', name:'pageNo', placeholder:'1', default:'1'},
- {label:'每页条数(pageSize)', name:'pageSize', placeholder:'20', default:'20'}], true, 'searchRecordResponse', '分页查询当前用户搜索记录(轻量摘要,不含platformResults/searchLog大字段),返回分页对象含total/records/pages等字段')}
- ${testSection('sr-detail', '记录详情', 'GET', 'get', '/api/search-record/history/{id}', '需认证', 'auth-required',
- [{label:'记录ID(id)', name:'id', placeholder:'1', default:'1'}], true, 'searchRecordResponse', '读取单条搜索记录详情(含完整platformResults和searchLog)')}
- ${testSection('sr-delete', '删除记录', 'DELETE', 'delete', '/api/search-record/history/{id}', '需认证', 'auth-required',
- [{label:'记录ID(id)', name:'id', placeholder:'1', default:'1'}], false, null, '删除单条搜索记录(幂等,不存在也返回成功)')}
- ${testSection('sr-clear', '清空记录', 'DELETE', 'delete', '/api/search-record/history', '需认证', 'auth-required', null, false, null, '清空当前用户全部搜索记录')}`;
- }
- function renderPlatformSessionModule() {
- return `
- <div class="batch-buttons">
- <button class="btn btn-warning btn-sm" onclick="runPlatformSessionTests()">运行全部平台会话测试</button>
- </div>
- ${testSection('ps-upload', '上传平台会话', 'PUT', 'put', '/api/platform-session/{platformCode}', '需认证', 'auth-required',
- [{label:'平台代码(platformCode)', name:'platformCode', placeholder:'yiyaocheng', default:'yiyaocheng'},
- {label:'平台名称(platformName)', name:'platformName', placeholder:'壹药城', default:'壹药城'},
- {label:'账号标签(accountLabel)', name:'accountLabel', placeholder:'深圳大方无限科技有限公司', default:'测试账号'},
- {label:'登录URL(loginUrl)', name:'loginUrl', placeholder:'https://mall.yaoex.com/login', default:'https://mall.yaoex.com/login'},
- {label:'cookies(JSON数组)', name:'cookies', placeholder:'[{"name":"session","value":"xxx"}]', default:'[{"name":"mall_session","value":"test-cookie-value","domain":".mall.yaoex.com","path":"/","secure":true,"httpOnly":true,"expirationDate":1800000000}]', type:'json'}], true, null, '上传或覆盖当前用户某个平台的Cookie会话快照,Cookie会加密存储。platformCode必须与路径中的一致')}
- ${testSection('ps-list', '平台会话列表', 'GET', 'get', '/api/platform-session/list', '需认证', 'auth-required', null, true, null, '获取当前用户所有已保存的平台会话(Cookie已解密),用于启动时恢复登录态')}
- ${testSection('ps-delete', '删除平台会话', 'DELETE', 'delete', '/api/platform-session/{platformCode}', '需认证', 'auth-required',
- [{label:'平台代码(platformCode)', name:'platformCode', placeholder:'yiyaocheng', default:'yiyaocheng'}], false, null, '删除某个平台的远端会话(幂等,不存在也返回成功)')}`;
- }
- // ==================== 运营侧模块 ====================
- function renderAdminMembershipModule() {
- return `
- <div class="batch-buttons"><button class="btn btn-warning btn-sm" onclick="runAdminMembershipTests()">运行全部会员管理测试</button></div>
- ${testSection('admin-membership-grant', '发放/调整会员', 'POST', 'post', '/api/admin/membership/grant', '需认证', 'auth-required',
- [{label:'用户ID(userId)', name:'userId', placeholder:'1', default:'1'},
- {label:'会员等级(level)', name:'level', placeholder:'PLUS/PRO/ULTRA', default:'PRO'},
- {label:'来源(source)', name:'source', placeholder:'ADMIN_GRANT', default:'ADMIN_GRANT'},
- {label:'备注(remark)', name:'remark', placeholder:'运营发放', default:'测试发放'}], false, 'membership', '运营/超管给用户发放或调整会员等级')}
- ${testSection('admin-membership-list', '会员记录列表', 'GET', 'get', '/api/admin/membership/memberships?page=1&size=10', '需认证', 'auth-required', null, true, 'membership', '分页查询会员发放记录')}`;
- }
- function renderAdminInviteModule() {
- return `
- <div class="batch-buttons"><button class="btn btn-warning btn-sm" onclick="runAdminInviteTests()">运行全部邀请管理测试</button></div>
- ${testSection('admin-invite-config', '邀请配置查询', 'GET', 'get', '/api/admin/invite/config', '需认证', 'auth-required', null, true, 'inviteConfig', '获取当前激活的邀请配置')}
- ${testSection('admin-invite-configs', '全部邀请配置', 'GET', 'get', '/api/admin/invite/configs', '需认证', 'auth-required', null, true, 'inviteConfig', '获取所有邀请配置历史记录')}
- ${testSection('admin-invite-config-update', '更新邀请配置', 'PUT', 'put', '/api/admin/invite/config', '需认证', 'auth-required',
- [
- {label:'奖励月数(rewardMonths)', name:'rewardMonths', placeholder:'1', default:'1'},
- {label:'总邀请上限(maxTotalInvites)', name:'maxTotalInvites', placeholder:'30', default:'30'},
- {label:'日奖励上限(maxDailyReward)', name:'maxDailyReward', placeholder:'50', default:'50'},
- {label:'邀请码过期天(inviteCodeExpireDays)', name:'inviteCodeExpireDays', placeholder:'30', default:'30'},
- {label:'日邀请上限(maxInvitePerDay)', name:'maxInvitePerDay', placeholder:'10', default:'10'},
- {label:'APP下载URL(appDownloadUrl)', name:'appDownloadUrl', placeholder:'https://...', default:'https://download.example.com/app.exe'},
- {label:'小程序路径(miniappPath)', name:'miniappPath', placeholder:'/pages/download/index', default:''},
- {label:'小程序AppID(miniappAppId)', name:'miniappAppId', placeholder:'wx...', default:''},
- {label:'微信跳转URL(wechatRedirectUrl)', name:'wechatRedirectUrl', placeholder:'https://...', default:''},
- {label:'钉钉AppID(dingtalkAppId)', name:'dingtalkAppId', placeholder:'', default:''},
- {label:'飞书AppID(feishuAppId)', name:'feishuAppId', placeholder:'', default:''},
- {label:'落地页标题(landingTitle)', name:'landingTitle', placeholder:'邀请你加入智价云(药店版)', default:'邀请你加入智价云(药店版)'},
- {label:'落地页描述(landingDesc)', name:'landingDesc', placeholder:'登录智价云(药店版),聚合比价查低价', default:''},
- {label:'应用名称(appName)', name:'appName', placeholder:'智价云(药店版)', default:'智价云(药店版)'},
- {label:'分享标题模板(shareTitleTemplate)', name:'shareTitleTemplate', placeholder:'{inviter} 邀请你加入{appName}', default:'{inviter} 邀请你加入{appName}'},
- {label:'复制文案模板(copyTextTemplate)', name:'copyTextTemplate', placeholder:'我是{inviter},在这里发现了一个药店采购神器...', default:'我是{inviter},在这里发现了一个药店采购神器——{appName}!它聚合比价功能特别方便,能快速查到最低价,帮你节省采购成本。下载链接{inviteLink}填我的邀请码 {code} 完成注册,你也会获得会员权益!'},
- {label:'奖励说明(rewardDescription)', name:'rewardDescription', placeholder:'邀请人每成功邀请1家药店注册...', default:'邀请人每成功邀请1家药店注册,赠送1个月高级会员时长'},
- {label:'统计摘要模板(summaryTextTemplate)', name:'summaryTextTemplate', placeholder:'已邀请{totalInvited}家药店...', default:'已邀请{totalInvited}家药店,{registeredCount}家注册,累计获得{totalReward}个月高级会员'},
- {label:'Windows提示文案(instructionWindows)', name:'instructionWindows', placeholder:'下载安装Windows客户端后...', default:'下载安装Windows客户端后,在注册页面输入邀请码:{code} 或点击链接自动填入'},
- {label:'App提示文案(instructionApp)', name:'instructionApp', placeholder:'下载安装客户端后...', default:'下载安装客户端后,在注册页面输入邀请码:{code} 或点击链接自动填入'},
- {label:'小程序提示文案(instructionMiniapp)', name:'instructionMiniapp', placeholder:'点击按钮打开小程序...', default:'点击按钮打开小程序,邀请码已自动填入'},
- {label:'微信提示文案(instructionWechat)', name:'instructionWechat', placeholder:'关注公众号后...', default:'关注公众号后,在菜单中输入邀请码:{code} 完成注册'},
- {label:'钉钉提示文案(instructionDingtalk)', name:'instructionDingtalk', placeholder:'打开钉钉应用...', default:'打开钉钉应用,搜索"{appName}"并输入邀请码:{code}'},
- {label:'飞书提示文案(instructionFeishu)', name:'instructionFeishu', placeholder:'打开飞书应用...', default:'打开飞书应用,搜索"{appName}"并输入邀请码:{code}'},
- {label:'默认提示文案(instructionDefault)', name:'instructionDefault', placeholder:'下载客户端后...', default:'下载客户端后,在注册页面输入邀请码:{code} 即可享受奖励'},
- {label:'状态(status)', name:'status', placeholder:'0停用/1启用', default:'1'}
- ], false, 'inviteConfig', '更新邀请配置参数。文案模板支持占位符:{inviter}邀请人昵称、{appName}应用名称、{inviteLink}邀请链接、{code}邀请码、{rewardMonths}奖励月数、{totalInvited}总邀请数、{registeredCount}注册数、{totalReward}总奖励')}
- ${testSection('admin-invite-rewards', '邀请记录查询', 'GET', 'get', '/api/admin/invite/rewards?userId=1', '需认证', 'auth-required', null, true, 'inviteReward', '查看指定用户的邀请奖励记录')}
- ${testSection('admin-invite-conversion', '邀请转化统计', 'GET', 'get', '/api/admin/invite/conversion-stats?userId=1', '需认证', 'auth-required', null, true, 'conversionStats', '查看用户邀请点击-注册转化率')}`;
- }
- function renderAdminCrawlerModule() {
- return `
- <div class="batch-buttons"><button class="btn btn-warning btn-sm" onclick="runAdminCrawlerTests()">运行全部爬虫管理测试</button></div>
- ${testSection('admin-crawler-grant', '手动发放配额', 'POST', 'post', '/api/admin/crawler/grant', '需认证', 'auth-required',
- [{label:'用户ID(userId)', name:'userId', placeholder:'1', default:'1'},
- {label:'配额数(quotaCount)', name:'quotaCount', placeholder:'50', default:'10'},
- {label:'过期天数(expireDays)', name:'expireDays', placeholder:'30', default:'30'},
- {label:'备注(remark)', name:'remark', placeholder:'VIP升级奖励', default:'测试发放'}], false, 'quotaGrant', '运营手动给用户发放爬虫次数')}
- ${testSection('admin-crawler-grants', '发放记录', 'GET', 'get', '/api/admin/crawler/grants', '需认证', 'auth-required', null, true, 'quotaGrant', '查询爬虫配额发放记录')}
- ${testSection('admin-crawler-user-detail', '用户爬虫详情', 'GET', 'get', '/api/admin/crawler/user-detail?userId=1', '需认证', 'auth-required',
- [{label:'用户ID(userId)', name:'userId', placeholder:'1', default:'1'}], true, 'crawlerUserDetail', '查看用户爬虫使用详情及各平台分布')}`;
- }
- function renderAdminCouponModule() {
- return `
- <div class="batch-buttons"><button class="btn btn-warning btn-sm" onclick="runAdminCouponTests()">运行全部优惠券发放测试</button></div>
- ${testSection('admin-coupon-issue', '发放优惠券', 'POST', 'post', '/api/coupon/issue', '需认证', 'auth-required',
- [{label:'模板ID(couponId)', name:'couponId', placeholder:'1', default:'1'},
- {label:'用户ID列表(userIds)', name:'userIds', placeholder:'1,2,3', default:'1', type:'array'}], false, 'couponIssue', '管理员向指定用户发放优惠券,userIds统一用列表传入,单个用户填1即可')}`;
- }
- function renderAdminLicenseModule() {
- return `
- <div class="batch-buttons"><button class="btn btn-warning btn-sm" onclick="runAdminLicenseTests()">运行全部执照审核测试</button></div>
- ${testSection('admin-license-list', '审核列表', 'GET', 'get', '/api/admin/license/review?reviewStatus=PENDING', '需认证', 'auth-required', null, true, 'businessLicense', '查询待审核/已审核的营业执照列表')}
- ${testSection('admin-license-review', '审核执照', 'POST', 'post', '/api/admin/license/review', '需认证', 'auth-required',
- [{label:'执照ID(licenseId)', name:'licenseId', placeholder:'1', default:'1'},
- {label:'动作(action)', name:'action', placeholder:'APPROVED/REJECTED', default:'APPROVED'},
- {label:'驳回原因(rejectReason)', name:'rejectReason', placeholder:'驳回时填写', default:''}], false, null, '审核营业执照,通过或驳回')}`;
- }
- function renderAdminPaymentModule() {
- return `
- <div class="batch-buttons"><button class="btn btn-warning btn-sm" onclick="runAdminPaymentTests()">运行全部支付方案测试</button></div>
- ${testSection('admin-payment-list', '方案列表(含下架)', 'GET', 'get', '/api/admin/payment/plans', '需认证', 'auth-required', null, true, null, '查询所有支付方案(含已下架),管理端专用')}
- ${testSection('admin-payment-create', '创建方案', 'POST', 'post', '/api/admin/payment/plans', '需认证', 'auth-required',
- [{label:'方案编码(planCode)', name:'planCode', placeholder:'MONTHLY_PLUS', default:'MONTHLY_PLUS'},
- {label:'方案名称(planName)', name:'planName', placeholder:'普通会员·月卡', default:'普通会员·月卡'},
- {label:'价格(price)', name:'price', placeholder:'9.90', default:'9.90'},
- {label:'有效天数(durationDays)', name:'durationDays', placeholder:'30', default:'30'},
- {label:'会员等级(membershipLevel)', name:'membershipLevel', placeholder:'PLUS', default:'PLUS'}], false, null, '管理员创建新的支付方案')}
- ${testSection('admin-payment-update', '更新方案', 'PUT', 'put', '/api/admin/payment/plans/{id}', '需认证', 'auth-required',
- [{label:'方案ID(id)', name:'id', placeholder:'1', default:'1'},
- {label:'方案编码(planCode)', name:'planCode', placeholder:'MONTHLY_PLUS', default:'MONTHLY_PLUS'},
- {label:'方案名称(planName)', name:'planName', placeholder:'普通会员·月卡', default:'普通会员·月卡(更新)'},
- {label:'价格(price)', name:'price', placeholder:'9.90', default:'9.90'},
- {label:'有效天数(durationDays)', name:'durationDays', placeholder:'30', default:'30'},
- {label:'会员等级(membershipLevel)', name:'membershipLevel', placeholder:'PLUS', default:'PLUS'}], false, null, '管理员更新支付方案')}
- ${testSection('admin-payment-toggle', '切换方案状态', 'PUT', 'put', '/api/admin/payment/plans/{id}/status', '需认证', 'auth-required',
- [{label:'方案ID(id)', name:'id', placeholder:'1', default:'1'},
- {label:'状态(status)', name:'status', placeholder:'0下架/1上架', default:'0'}], false, null, '切换支付方案上架/下架状态')}`;
- }
- function renderAdminAnxinModule() {
- return `
- <div class="batch-buttons"><button class="btn btn-warning btn-sm" onclick="runAdminAnxinTests()">运行全部安心付管理测试</button></div>
- ${testSection('admin-anxin-create', '创建安心付卡模板', 'POST', 'post', '/api/admin/payment/anxin/card/create', '需认证', 'auth-required',
- [{label:'支付方案ID(planId)', name:'planId', placeholder:'1', default:'1'}], true, 'anxinCard', '为支付方案创建支付宝安心付周期卡模板。创建后状态为PENDING(待审核),支付宝审核通过后变为EFFECTIVE。mock模式直接返回EFFECTIVE状态。每个支付方案只能创建一次')}
- ${testSection('admin-anxin-list', '卡模板列表', 'GET', 'get', '/api/admin/payment/anxin/cards', '需认证', 'auth-required', null, true, 'anxinCard', '查询所有安心付卡模板列表,含卡模板ID、状态、周期金额等')}
- ${testSection('admin-anxin-sync', '同步卡模板状态', 'POST', 'post', '/api/admin/payment/anxin/card/{cardId}/sync', '需认证', 'auth-required',
- [{label:'卡模板ID(cardId)', name:'cardId', placeholder:'创建后返回', default:''}], true, 'anxinCard', '调用支付宝接口查询卡模板最新状态(审核结果等)。mock模式直接返回本地状态')}
- ${testSection('admin-anxin-status', '修改卡模板状态', 'PUT', 'put', '/api/admin/payment/anxin/card/{cardId}/status', '需认证', 'auth-required',
- [{label:'卡模板ID(cardId)', name:'cardId', placeholder:'创建后返回', default:''},
- {label:'状态(status)', name:'status', placeholder:'EFFECTIVE/INVALID/FROZEN', default:'EFFECTIVE'}], false, null, '修改卡模板状态:EFFECTIVE(上架)/ INVALID(下架)/ FROZEN(冻结)')}`;
- }
- function renderAdminTrialQuotaModule() {
- return `
- <div class="batch-buttons"><button class="btn btn-warning btn-sm" onclick="runAdminTrialQuotaTests()">运行全部体验配额测试</button></div>
- ${testSection('admin-tq-active', '当前体验配置', 'GET', 'get', '/api/trial-quota/active', '公开', 'auth-public', null, true, 'trialQuota', '获取当前激活的体验配额配置')}
- ${testSection('admin-tq-list', '所有配置', 'GET', 'get', '/api/admin/trial-quota/configs', '需认证', 'auth-required', null, true, 'trialQuota', '查询所有体验配额配置列表')}
- ${testSection('admin-tq-create', '创建配置', 'POST', 'post', '/api/admin/trial-quota/config', '需认证', 'auth-required',
- [{label:'配置名称(configName)', name:'configName', placeholder:'新用户默认体验', default:'测试体验配置'},
- {label:'体验天数(trialDays)', name:'trialDays', placeholder:'15', default:'15'},
- {label:'日查询限制(dailyQueryLimit)', name:'dailyQueryLimit', placeholder:'20', default:'20'},
- {label:'月关注限制(monthlyWatchlistLimit)', name:'monthlyWatchlistLimit', placeholder:'199', default:'199'},
- {label:'配置类型(configType)', name:'configType', placeholder:'DEFAULT', default:'DEFAULT'},
- {label:'用户类型(applicableUserType)', name:'applicableUserType', placeholder:'NEW_USER', default:'NEW_USER'}], false, 'trialQuota', '创建新的体验配额配置')}
- ${testSection('admin-tq-update', '更新配置', 'PUT', 'put', '/api/admin/trial-quota/config/{id}', '需认证', 'auth-required',
- [{label:'配置ID(id)', name:'id', placeholder:'1', default:'1'},
- {label:'配置名称(configName)', name:'configName', placeholder:'更新后名称', default:'更新后的体验配置'},
- {label:'体验天数(trialDays)', name:'trialDays', placeholder:'30', default:'30'},
- {label:'日查询限制(dailyQueryLimit)', name:'dailyQueryLimit', placeholder:'50', default:'50'},
- {label:'月关注限制(monthlyWatchlistLimit)', name:'monthlyWatchlistLimit', placeholder:'199', default:'199'}], false, 'trialQuota', '更新指定体验配额配置')}
- ${testSection('admin-tq-delete', '删除配置', 'DELETE', 'delete', '/api/admin/trial-quota/config/{id}', '需认证', 'auth-required',
- [{label:'配置ID(id)', name:'id', placeholder:'1', default:'1'}], false, null, '删除指定体验配额配置')}`;
- }
- function renderAdminUserModule() {
- return `
- <div class="batch-buttons"><button class="btn btn-warning btn-sm" onclick="runAdminUserTests()">运行全部用户管理测试</button></div>
- ${testSection('admin-user-list', '前端用户列表', 'GET', 'get', '/api/admin/users?page={page}&size={size}&phone={phone}&nickname={nickname}', '需认证', 'auth-required',
- [{label:'页码(page)', name:'page', placeholder:'1', default:'1'},
- {label:'每页条数(size)', name:'size', placeholder:'10', default:'10'},
- {label:'手机号(phone)', name:'phone', placeholder:'138', default:''},
- {label:'昵称(nickname)', name:'nickname', placeholder:'测试', default:''}], true, null, '查询前端注册用户列表(仅USER角色)')}
- ${testSection('admin-admins-list', '管理员列表', 'GET', 'get', '/api/admin/admins?page=1&size=10', '需认证', 'auth-required', null, true, null, '查询管理员列表(t_admin表)')}
- ${testSection('admin-user-role', '修改用户角色', 'PUT', 'put', '/api/admin/users/{userId}/role', '需认证', 'auth-required',
- [{label:'用户ID(userId)', name:'userId', placeholder:'1', default:'1'},
- {label:'目标角色(role)', name:'role', placeholder:'ADMIN/SUPER_ADMIN/USER', default:'ADMIN'}], false, null, '升级用户为管理员(写入t_admin)/ 降级管理员为用户(删除t_admin记录)')}
- ${testSection('admin-user-kick', '强制踢出用户', 'POST', 'post', '/api/admin/users/{userId}/kick', '需认证', 'auth-required',
- [{label:'用户ID(userId)', name:'userId', placeholder:'1', default:'1'}], false, null, '强制踢出前端用户:递增tokenVersion+清空RefreshToken')}
- ${testSection('admin-admin-kick', '强制踢出管理员', 'POST', 'post', '/api/admin/admins/{adminId}/kick', '需认证', 'auth-required',
- [{label:'管理员ID(adminId)', name:'adminId', placeholder:'100', default:'100'}], false, null, '强制踢出管理员:递增tokenVersion+清空RefreshToken')}`;
- }
- function renderAdminAuditModule() {
- return `
- <div class="batch-buttons"><button class="btn btn-warning btn-sm" onclick="runAdminAuditTests()">运行全部审计日志测试</button></div>
- ${testSection('admin-audit-query', '查询审计日志', 'GET', 'get', '/api/admin/audit-log?page=1&size=10', '需认证', 'auth-required', null, true, 'auditLog', '分页查询操作审计日志')}
- ${testSection('admin-audit-sensitive', '敏感操作日志', 'GET', 'get', '/api/admin/audit-log/sensitive', '需认证', 'auth-required', null, true, 'auditLog', '查询所有敏感操作日志')}
- ${testSection('admin-audit-user', '用户操作日志', 'GET', 'get', '/api/admin/audit-log/user/{userId}?limit=20', '需认证', 'auth-required',
- [{label:'用户ID(userId)', name:'userId', placeholder:'1', default:'1'}], true, 'auditLog', '查询指定用户的操作日志')}
- ${testSection('admin-audit-target', '目标操作历史', 'GET', 'get', '/api/admin/audit-log/target/{targetType}/{targetId}?limit=20', '需认证', 'auth-required',
- [{label:'目标类型(targetType)', name:'targetType', placeholder:'USER', default:'USER'},
- {label:'目标ID(targetId)', name:'targetId', placeholder:'1', default:'1'}], true, 'auditLog', '查询某个目标对象的操作历史')}
- ${testSection('admin-audit-stats', '模块操作统计', 'GET', 'get', '/api/admin/audit-log/stats?module=USER', '需认证', 'auth-required', null, true, null, '统计指定模块的操作次数')}`;
- }
- // ===================== 批量测试 =====================
- function getSectionsInModule(modulePrefix) {
- return Array.from(document.querySelectorAll('.test-section')).filter(s => {
- const btn = s.querySelector('button[onclick*="runTest"]');
- return btn && btn.getAttribute('onclick').includes("'" + modulePrefix + "-");
- });
- }
- const USER_MODULES = ['auth','crawler','invite','coupon','membership','payment','upload','search','platform-account','business-license','platform-config','search-record','platform-session','watchlist','trial-quota'];
- const ADMIN_MODULES = ['admin-membership','admin-invite','admin-crawler','admin-coupon','admin-license','admin-payment','admin-anxin','admin-trial-quota','admin-user','admin-audit','upload'];
- function countTestsInCurrentPanel() {
- return document.querySelectorAll('.test-section button[onclick*="runTest"]').length;
- }
- async function runBatchTests(modulePrefix) {
- const sections = getSectionsInModule(modulePrefix);
- // 将该模块中尚未运行过的测试计入total
- for (const section of sections) {
- const btn = section.querySelector('button[onclick*="runTest"]');
- if (btn) {
- const onclick = btn.getAttribute('onclick') || '';
- const match = onclick.match(/runTest\('([^']+)'/);
- if (match && !stats.hasOwnProperty(match[1])) {
- stats.total++;
- }
- }
- }
- stats.pending = Math.max(0, stats.total - stats.pass - stats.fail);
- document.getElementById('totalCount').textContent = stats.total;
- document.getElementById('pendingCount').textContent = stats.pending;
- for (const section of sections) {
- const btn = section.querySelector('button[onclick*="runTest"]');
- if (btn) {
- btn.click();
- await new Promise(r => setTimeout(r, 600));
- }
- }
- }
- async function runAllTests() {
- resetStats();
- // 先遍历用户侧模块
- for (const m of USER_MODULES) {
- const tab = document.querySelector(`#moduleTabs .module-tab[data-module="${m}"]`);
- if (tab) tab.click();
- await new Promise(r => setTimeout(r, 50));
- stats.total += countTestsInCurrentPanel();
- }
- // 再遍历运营侧模块
- for (const m of ADMIN_MODULES) {
- const tab = document.querySelector(`#adminModuleTabs .module-tab[data-module="${m}"]`);
- if (tab) tab.click();
- await new Promise(r => setTimeout(r, 50));
- stats.total += countTestsInCurrentPanel();
- }
- document.getElementById('totalCount').textContent = stats.total;
- stats.pending = stats.total;
- document.getElementById('pendingCount').textContent = stats.pending;
- // 逐模块执行 - 用户侧
- switchSide('user');
- for (const m of USER_MODULES) {
- try {
- const tab = document.querySelector(`#moduleTabs .module-tab[data-module="${m}"]`);
- if (tab) tab.click();
- await new Promise(r => setTimeout(r, 100));
- await runBatchTests(m);
- } catch (e) { console.error('模块 ' + m + ' 测试出错:', e); }
- }
- // 逐模块执行 - 运营侧
- switchSide('admin');
- for (const m of ADMIN_MODULES) {
- try {
- const tab = document.querySelector(`#adminModuleTabs .module-tab[data-module="${m}"]`);
- if (tab) tab.click();
- await new Promise(r => setTimeout(r, 100));
- await runBatchTests(m);
- } catch (e) { console.error('模块 ' + m + ' 测试出错:', e); }
- }
- }
- function resetStats() {
- const keys = Object.keys(stats);
- for (const k of keys) {
- if (k !== 'pass' && k !== 'fail' && k !== 'pending' && k !== 'total') {
- delete stats[k];
- }
- }
- stats.pass = 0;
- stats.fail = 0;
- stats.pending = 0;
- stats.total = 0;
- document.getElementById('totalCount').textContent = '0';
- document.getElementById('passCount').textContent = '0';
- document.getElementById('failCount').textContent = '0';
- document.getElementById('pendingCount').textContent = '0';
- }
- function clearAllResults() {
- stopAlipayPolling();
- closeAlipayModal();
- document.querySelectorAll('.response-box').forEach(b => b.style.display = 'none');
- document.querySelectorAll('.status-badge').forEach(b => { b.textContent = ''; b.className = 'status-badge'; });
- resetStats();
- }
- // ===================== 支付宝支付弹窗 =====================
- let currentAlipayModal = null;
- let currentAlipayOrderNo = null;
- function showAlipayModal(qrBase64, amount, orderNo) {
- closeAlipayModal();
- currentAlipayOrderNo = orderNo;
- const overlay = document.createElement('div');
- overlay.className = 'alipay-modal-overlay';
- overlay.id = 'alipay-modal-overlay';
- overlay.onclick = (e) => { if (e.target === overlay) closeAlipayModal(); };
- const amountText = amount ? '<span>¥</span>' + amount : '';
- overlay.innerHTML = '<div class="alipay-modal">'
- + '<button class="alipay-modal-close" onclick="closeAlipayModal()" title="关闭">×</button>'
- + '<div class="alipay-modal-logo">'
- + '<svg viewBox="0 0 1024 1024"><path fill="#1677ff" d="M876.8 128H147.2C114.9 128 89 154.4 89 186.7v650.6c0 32.3 25.9 58.7 58.2 58.7h729.6c32.3 0 58.2-26.4 58.2-58.7V186.7c.6-32.3-25.3-58.7-57.2-58.7zM512 714.7c-112 0-202.7-91.2-202.7-203.5 0-44.8 14.4-86.4 39.5-120.3C320 416 288 416 256 416V352c53.3 0 101.3-10.7 144-32 35.2-17.6 66.7-41.6 92.8-70.4 27.2-30.4 48-65.6 61.9-104h140.8v64h-64c8.5 30.9 13.3 63.5 13.3 97.1 0 48-9.6 92.8-27.7 133.3C690.7 464 729.6 480 768 480v64c-53.3 0-101.3-10.7-144-32-19.2 34.1-47.5 62.9-81.6 83.2 74.7 21.3 153.6 26.7 234.7 16v64c-93.9 10.7-185.6 3.2-270.9-26.7C464 693.3 432 704 400 704v-64c32 0 64-2.1 94.9-6.4C458.7 604.8 437.3 558.9 437.3 509.3c0-93.9 76.8-170.1 171.5-170.1 10.7 0 21.3 1.1 31.5 3.2-4.8-25.1-7.5-51.2-7.5-78.4 0-9.6.5-19.2 1.6-28.3-43.2 21.3-92.8 34.1-145.6 36.8V192c85.3 6.4 162.1 41.6 220.3 96 29.9-5.9 61.3-9.1 93.3-9.1V341.3c-26.7 0-52.8 2.7-77.9 7.5C755.2 384 768 416 768 448v32h-64v-32c0-53.3-43.7-96.5-97.1-96.5-42.7 0-78.9 27.7-92.3 65.6 36.3 15.5 61.9 51.7 61.9 94.1 0 56.5-46.1 102.4-102.9 102.4S370.7 565.3 370.7 509.3c0-71.5 61.3-129.6 136.5-129.6 4.3 0 8.5.5 12.8 1.1-8-29.9-12.3-61.3-12.3-93.9 0-4.3.5-8.5.5-12.8-52.3 4.8-100.3 22.4-140.3 49.6-34.1 23.5-62.4 53.3-83.2 88-18.7 31.5-29.3 68.3-29.3 107.2 0 112 90.7 203.5 202.7 203.5 62.9 0 118.9-28.8 156.3-73.6l48 42.7C660.5 677.3 590.9 714.7 512 714.7z"/></svg>'
- + '支付宝'
- + '</div>'
- + '<div class="alipay-modal-title">扫一扫付款</div>'
- + '<div class="alipay-modal-amount">' + amountText + '</div>'
- + '<div class="alipay-modal-qr"><img src="' + qrBase64 + '" alt="支付宝付款码"></div>'
- + '<div class="alipay-modal-status" id="alipay-modal-status">轮询支付状态中...</div>'
- + '<div class="alipay-modal-hint">二维码有效期 15 分钟<br>请尽快完成付款<br>支付成功后回调自动激活,也可手动点击“确认支付”测试回调</div>'
- + '</div>';
- document.body.appendChild(overlay);
- currentAlipayModal = overlay;
- if (orderNo) startAlipayPolling(orderNo);
- }
- function closeAlipayModal() {
- if (currentAlipayModal) {
- currentAlipayModal.remove();
- currentAlipayModal = null;
- currentAlipayOrderNo = null;
- }
- }
- // ===================== 支付宝支付轮询 =====================
- let alipayPollTimer = null;
- function startAlipayPolling(orderNo) {
- stopAlipayPolling();
- const statusEl = document.getElementById('alipay-modal-status');
- if (statusEl) statusEl.textContent = '轮询支付状态中...';
- alipayPollTimer = setInterval(async () => {
- try {
- const res = await api('GET', '/api/payment/order/' + orderNo, null, true);
- const status = res.data?.data?.status;
- const el = document.getElementById('alipay-modal-status');
- if (!el || !currentAlipayModal) { stopAlipayPolling(); return; }
- if (status === 'PAID') {
- stopAlipayPolling();
- el.innerHTML = '<span style="color:#2e7d32;font-weight:600;">✓ 支付成功!订单已激活</span>';
- } else if (status === 'EXPIRED') {
- stopAlipayPolling();
- el.innerHTML = '<span style="color:#c62828;">订单已过期</span>';
- } else {
- el.textContent = '轮询支付状态中... (当前: ' + (status || '未知') + ')';
- }
- } catch (e) {
- console.error('轮询支付状态异常:', e);
- }
- }, 3000);
- }
- function stopAlipayPolling() {
- if (alipayPollTimer) { clearInterval(alipayPollTimer); alipayPollTimer = null; }
- }
- // 用户侧快捷批量运行
- function runAuthTests() { runBatchTests('auth'); }
- function runCrawlerTests() { runBatchTests('crawler'); }
- function runInviteTests() { runBatchTests('invite'); }
- function runCouponTests() { runBatchTests('coupon'); }
- function runMembershipTests() { runBatchTests('membership'); }
- function runPaymentTests() { runBatchTests('payment'); }
- function runUploadTests() { uploadFile(); }
- function runSearchTests() { runBatchTests('search'); }
- function runPlatformAccountTests() { runBatchTests('pa'); }
- function runBusinessLicenseTests() { runBatchTests('bl'); }
- function runPlatformConfigTests() { runBatchTests('pc'); }
- function runSearchRecordTests() { runBatchTests('sr'); }
- function runPlatformSessionTests() { runBatchTests('ps'); }function runWatchlistTests() { runBatchTests('watchlist'); }
- function runTrialQuotaTests() { runBatchTests('trial-quota'); }
- // 运营侧快捷批量运行
- function runAdminMembershipTests() { runBatchTests('admin-membership'); }
- function runAdminInviteTests() { runBatchTests('admin-invite'); }
- function runAdminCrawlerTests() { runBatchTests('admin-crawler'); }
- function runAdminCouponTests() { runBatchTests('admin-coupon'); }
- function runAdminLicenseTests() { runBatchTests('admin-license'); }
- function runAdminPaymentTests() { runBatchTests('admin-payment'); }
- function runAdminAnxinTests() { runBatchTests('admin-anxin'); }
- function runAdminTrialQuotaTests() { runBatchTests('admin-tq'); }
- function runAdminUserTests() { runBatchTests('admin-user'); }
- function runAdminAuditTests() { runBatchTests('admin-audit'); }
- // ===================== 验证器 =====================
- function validate_authSend(result) { return result.data?.code === 200; }
- function validate_authSmslogin(result) { return result.data?.code === 200 && result.data?.data?.accessToken; }
- function validate_authPwdlogin(result) { return result.data?.code === 200 && result.data?.data?.accessToken; }
- function validate_authUserinfo(result) { return result.data?.code === 200 && result.data?.data?.userId; }
- function validate_crawlerStatus(result) { return result.data?.code === 200 && result.data?.data?.todayQuota !== undefined; }
- function validate_crawlerConsume(result) { return result.data?.code === 200; }
- function validate_crawlerLogs(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_inviteCode(result) { return result.data?.code === 200 && result.data?.data?.code; }
- function validate_inviteStats(result) { return result.data?.code === 200 && result.data?.data?.totalInvited !== undefined; }
- function validate_inviteRewards(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_invitePage(result) { return result.data?.code === 200 && result.data?.data?.inviteCode; }
- function validate_inviteLinkEntry(result) { return result.data?.code === 200 && result.data?.data?.inviteCode; }
- function validate_couponMy(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_couponAvailable(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_membershipMy(result) { return result.data?.code === 200; }
- function validate_membershipBenefits(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_membershipAdminGrant(result) { return result.data?.code === 200 && result.data?.data?.level; }
- function validate_membershipAdminList(result) { return result.data?.code === 200 && result.data?.data?.records !== undefined; }
- function validate_paList(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_blInfo(result) { return result.data?.code === 200; }
- function validate_blReviewList(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_pcEnabled(result) { return result.data?.code === 200 && Array.isArray(result.data?.data) && result.data?.data?.length >= 3; }
- function validate_pcDetail(result) { return result.data?.code === 200 && result.data?.data?.platformCode; }
- function validate_tqActive(result) { return result.data?.code === 200 && result.data?.data; }
- function validate_tqList(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_tqCreate(result) { return result.data?.code === 200 && result.data?.data; }
- function validate_tqUpdate(result) { return result.data?.code === 200 && result.data?.data; }
- function validate_adminInviteConfig(result) { return result.data?.code === 200; }
- function validate_adminInviteConfigs(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_adminGrants(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_adminInviteRewards(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_adminUserDetail(result) { return result.data?.code === 200; }
- function validate_adminConversionStats(result) { return result.data?.code === 200 && result.data?.data; }
- function validate_adminUserList(result) { return result.data?.code === 200 && result.data?.data?.records !== undefined; }
- function validate_adminUserRole(result) { return result.data?.code === 200; }
- function validate_auditQuery(result) { return result.data?.code === 200 && result.data?.data?.records !== undefined; }
- function validate_auditSensitive(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_auditUser(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_auditTarget(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_auditStats(result) { return result.data?.code === 200 && result.data?.data?.count !== undefined; }
- // 运营侧验证器
- function validate_adminMembershipGrant(result) { return result.data?.code === 200 && result.data?.data?.level; }
- function validate_adminMembershipList(result) { return result.data?.code === 200 && result.data?.data?.records !== undefined; }
- function validate_adminInviteConfig(result) { return result.data?.code === 200; }
- function validate_adminInviteConfigs(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_adminInviteRewards(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_adminInviteConversion(result) { return result.data?.code === 200 && result.data?.data; }
- function validate_adminCrawlerGrant(result) { return result.data?.code === 200 && result.data?.data; }
- function validate_adminCrawlerGrants(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_adminCrawlerUserDetail(result) { return result.data?.code === 200; }
- function validate_adminCouponIssue(result) { return result.data?.code === 200; }
- function validate_adminLicenseList(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_adminPaymentCreate(result) { return result.data?.code === 200; }
- function validate_adminPaymentUpdate(result) { return result.data?.code === 200; }
- function validate_adminPaymentToggle(result) { return result.data?.code === 200; }
- function validate_adminTqActive(result) { return result.data?.code === 200 && result.data?.data; }
- function validate_adminTqList(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_adminTqCreate(result) { return result.data?.code === 200 && result.data?.data; }
- function validate_adminTqUpdate(result) { return result.data?.code === 200 && result.data?.data; }
- function validate_adminUserList(result) { return result.data?.code === 200 && result.data?.data?.records !== undefined; }
- function validate_adminUserRole(result) { return result.data?.code === 200; }
- function validate_adminAuditQuery(result) { return result.data?.code === 200 && result.data?.data?.records !== undefined; }
- function validate_adminAuditSensitive(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_adminAuditUser(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_adminAuditTarget(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_adminAuditStats(result) { return result.data?.code === 200; }
- // 支付模块验证器
- function validate_paymentPlans(result) { return result.data?.code === 200 && Array.isArray(result.data?.data) && result.data?.data?.length >= 2; }
- function validate_paymentOrders(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- // 签约协议验证器
- function validate_paymentAgreementSign(result) { return result.data?.code === 200 && result.data?.data?.agreementId; }
- function validate_paymentAgreementList(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_paymentAgreementDetail(result) { return result.data?.code === 200 && result.data?.data?.id; }
- function validate_paymentAgreementNotifyWechat(result) { const raw = result.data?._raw || result.data; return result.ok && (raw === 'success' || raw === 'SUCCESS'); }
- function validate_paymentAnxinNotifyDeduct(result) { const raw = result.data?._raw || result.data; return result.ok && (raw === 'success' || raw === 'SUCCESS'); }
- function validate_paymentAnxinNotifyCardChange(result) { const raw = result.data?._raw || result.data; return result.ok && (raw === 'success' || raw === 'SUCCESS'); }
- // 安心付管理验证器
- function validate_adminAnxinCreate(result) { return result.data?.code === 200 && result.data?.data?.cardId; }
- function validate_adminAnxinList(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_adminAnxinSync(result) { return result.data?.code === 200 && result.data?.data?.cardId; }
- function validate_adminAnxinStatus(result) { return result.data?.code === 200; }
- function validate_searchQuery(result) { return result.data?.code === 200 && result.data?.data?.prices !== undefined; }
- function validate_searchSuggestions(result) { return result.data?.code === 200 && Array.isArray(result.data?.data) && result.data?.data?.length > 0; }
- function validate_searchHistory(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- function validate_searchSave(result) { return result.data?.code === 200 && result.data?.data?.keyword; }
- function validate_searchRecords(result) { return result.data?.code === 200 && result.data?.data?.records !== undefined; }
- // 搜索记录模块验证器
- function validate_srSave(result) { return result.data?.code === 200 && result.data?.data?.id && result.data?.data?.drugName; }
- function validate_srList(result) { return result.data?.code === 200 && result.data?.data?.records !== undefined; }
- function validate_srDetail(result) { return result.data?.code === 200 && result.data?.data?.id; }
- // 平台会话模块验证器
- function validate_psUpload(result) { return result.data?.code === 200 && result.data?.data?.platformCode; }
- function validate_psList(result) { return result.data?.code === 200 && Array.isArray(result.data?.data); }
- // ===================== 初始化 =====================
- window.addEventListener('DOMContentLoaded', () => {
- const params = new URLSearchParams(window.location.search);
- if (params.get('baseUrl')) document.getElementById('baseUrl').value = params.get('baseUrl');
- // 渲染默认模块
- renderModule('auth');
- });
- </script>
- </body>
- </html>
|