| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784 |
- <!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">管理员短信登录</span>
- <label>手机号</label>
- <label for="adminPhone"></label><input type="text" id="adminPhone" value="" placeholder="管理员手机号" style="width:150px;">
- <label>验证码</label>
- <label for="adminSmsCode"></label><input type="text" id="adminSmsCode" placeholder="123456" value="123456" style="width:100px;">
- <button class="btn btn-primary btn-sm" onclick="sendSms()">发送验证码</button>
- <button class="btn btn-warning btn-sm" onclick="smsLogin()">管理员短信登录</button>
- </div>
- <div class="login-section">
- <span class="login-title">管理员密码登录</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="passwordLogin()">管理员密码登录</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-trial-quota">7.体验配额</div>
- <div class="module-tab" data-module="admin-user">8.用户管理</div>
- <div class="module-tab" data-module="admin-audit">9.审计日志</div>
- <div class="module-tab" data-module="upload">10.文件上传</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) {
- 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; });
- }
- 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);
- }
- 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;">微信扫码付款后回调自动激活,也可手动确认支付测试回调</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';
- }
- }
- }
- 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 '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-trial-quota': html = renderAdminTrialQuotaModule(); break;
- case 'admin-user': html = renderAdminUserModule(); break;
- case 'admin-audit': html = renderAdminAuditModule(); break;
- }
- panels.innerHTML = html;
- }
- 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', '是否为新注册用户']
- ],
- '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,可能为null)'],
- ['membershipLevelName', 'String', '会员等级中文名(普通会员/高级会员/超级会员)'],
- ['createTime', 'DateTime', '注册时间'],
- ['loginSource', 'String', '登录来源(SMS/PASSWORD/WECHAT,可能为null)'],
- ['levelCode', 'String', '等级编码(冗余字段,来自levelInfo,如:PLUS)']
- ],
- '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', '一键复制文本'],
- ['rewardDescription', 'String', '奖励说明']
- ],
- 'inviteStats': [
- ['myInviteCode', 'String', '我的邀请码'],
- ['inviteLink', 'String', '邀请链接'],
- ['clickedCount', 'Integer', '链接被打开总次数'],
- ['totalInvited', 'Integer', '已邀请人数'],
- ['registeredCount', 'Integer', '已注册人数'],
- ['totalReward', 'Integer', '累计获得奖励会员月数'],
- ['summaryText', 'String', '摘要文本'],
- ['todayInvited', 'Integer', '今日已邀请人数'],
- ['todayRemaining', 'Integer', '今日剩余可邀次数'],
- ['rewardPerInvite', '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下载地址(channel=windows/app)'],
- ['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'],
- ['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', '落地页标题'],
- ['landingDesc', 'String', '落地页描述'],
- ['appName', 'String', '应用名称'],
- ['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', '落地页描述']
- ],
- };
- 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' || path.includes('/api/level/user/')) ? '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 => {
- fieldsHtml += `<div class="form-group"><label>${f.label}</label><input type="text" id="${id}-${f.name}" placeholder="${f.placeholder || ''}" value="${f.default || ''}"></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';
- if (asJson) {
- parts.push(`"${f.name}": safeJsonParse(document.getElementById('${id}-${f.name}').value)`);
- } 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')}
- ${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', default:'yaoshibang'}], true, null, '前端启动爬虫前调用,扣减配额次数')}
- ${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-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}?channel=windows', '公开', 'auth-public',
- [{label:'邀请码(code)', name:'code', placeholder:'输入邀请码', default:''}], true, 'invitePage', '浏览器直接访问邀请链接入口,默认windows渠道,自动计入点击数')}
- ${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', '查询指定会员等级的权益列表')}`;
- }
- 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, '根据订单号查询支付订单详情,响应中expireHint含过期提示')}
- ${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验签')}`;
- }
- 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:'2026-06-26T08:30:00.000Z', default:'2026-06-26T08:30:00.000Z'},
- {label:'状态文案(statusText)', name:'statusText', placeholder:'已返回 2 个平台的可比价结果。', default:'已返回 2 个平台的可比价结果。'},
- {label:'平台结果(platformResults)', name:'platformResults', placeholder:'[{"platformId":"yiyaocheng","platformName":"壹药城"}]', default:'[{"platformId":"yiyaocheng","platformName":"壹药城","response":{"status":"ok","products":[]}}]', type:'json'},
- {label:'搜索日志(searchLog)', name:'searchLog', placeholder:'null', default:'null', type:'json'}], true, null, '保存一次完整的比价搜索快照,包含平台响应数据。platformResults和searchLog支持任意JSON结构')}
- ${testSection('sr-list', '搜索记录列表', 'GET', 'get', '/api/search-record/history?limit={limit}', '需认证', 'auth-required',
- [{label:'条数(limit)', name:'limit', placeholder:'20', default:'20'}], true, null, '获取当前用户最近的搜索记录,默认20条,最多50条')}
- ${testSection('sr-detail', '记录详情', 'GET', 'get', '/api/search-record/history/{id}', '需认证', 'auth-required',
- [{label:'记录ID(id)', name:'id', placeholder:'1', default:'1'}], true, null, '读取单条搜索记录详情(包含完整的platformResults)')}
- ${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:'落地页标题(landingTitle)', name:'landingTitle', placeholder:'标题', default:'邀请你加入智价云药店'}], false, 'inviteConfig', '更新邀请配置参数,rewardMonths=邀请人每次获赠高级会员月数,maxTotalInvites=单个账号总邀请上限')}
- ${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-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 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}&role={role}', '需认证', '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:'角色(role)', name:'role', placeholder:'USER/ADMIN/SUPER_ADMIN', default:''}], true, null, '超级管理员查询用户列表')}
- ${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:'USER/ADMIN/SUPER_ADMIN', default:'ADMIN'}], false, null, '超级管理员修改用户角色')}`;
- }
- 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'];
- const ADMIN_MODULES = ['admin-membership','admin-invite','admin-crawler','admin-coupon','admin-license','admin-payment','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>请尽快完成付款</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 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 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_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 && Array.isArray(result.data?.data); }
- 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>
|