app.html 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. <!-- Login Modal -->
  2. <app-login-modal [visible]="needLogin" (loginSuccess)="onLoginSuccess($event)"></app-login-modal>
  3. <!-- Header -->
  4. <div class="page-header">
  5. <div class="logo">BRAIN<span>HACK</span></div>
  6. <div class="subtitle">API 服务充值</div>
  7. <div class="user-bar" *ngIf="loggedInUser">
  8. <span class="user-badge">👤 {{loggedInUser}}</span>
  9. <button class="token-copy-btn" (click)="copyToken()" *ngIf="sessionToken"
  10. title="复制到终端执行,自动将 token 写入 OpenClaw">
  11. {{tokenCopied ? '✅ 已复制命令' : '📋 复制 OpenClaw 授权命令'}}
  12. </button>
  13. <button class="logout-btn" (click)="logout()">退出</button>
  14. </div>
  15. </div>
  16. <!-- Main -->
  17. <div class="main-container">
  18. <!-- API Info Card -->
  19. <div class="api-info-card neu-raised" *ngIf="!showSuccess && !needLogin && apigId">
  20. <div class="api-title" *ngIf="!apig">
  21. <span class="skeleton" style="display:inline-block;width:200px;height:24px;">&nbsp;</span>
  22. </div>
  23. <div class="api-title" *ngIf="apig">
  24. {{apig.title}}<span class="badge">HTTPS</span>
  25. </div>
  26. <div class="api-desc" *ngIf="!apig">
  27. <span class="skeleton" style="display:inline-block;width:100%;height:16px;margin-top:8px;">&nbsp;</span>
  28. </div>
  29. <div class="api-desc" *ngIf="apig">
  30. {{apig.content || '专业API数据服务'}}
  31. </div>
  32. <div class="api-stats" *ngIf="apig">
  33. <div class="stat-item">
  34. <span class="stat-label">当前余额</span>
  35. <span class="stat-value">{{apig.count != null ? apig.count + ' 次' : '—'}}</span>
  36. </div>
  37. <div class="stat-item">
  38. <span class="stat-label">服务状态</span>
  39. <span class="stat-value" style="color:var(--success);">正常</span>
  40. </div>
  41. </div>
  42. </div>
  43. <!-- APIG Switcher (always visible after login) -->
  44. <div class="apig-list-section" *ngIf="loggedInUser && !needLogin && !showSuccess">
  45. <h3 class="apig-list-title">选择要充值的 API 服务</h3>
  46. <!-- Loading -->
  47. <div class="apig-list-loading" *ngIf="apigListLoading && apigList.length === 0">
  48. <span class="polling-dot"></span> 加载服务列表...
  49. </div>
  50. <!-- List -->
  51. <div class="apig-list-grid" [class.compact]="!!apigId" *ngIf="apigList.length > 0">
  52. <div class="apig-list-card neu-raised"
  53. *ngFor="let item of apigList"
  54. [class.current]="isCurrentApig(item.objectId)"
  55. (click)="navigateToApig(item.objectId)">
  56. <div class="apig-list-card-row">
  57. <div class="apig-list-card-title">
  58. {{item.title}}
  59. <span class="current-badge" *ngIf="isCurrentApig(item.objectId)">当前</span>
  60. </div>
  61. <div class="apig-list-card-balance"
  62. [class.zero]="(item.userBalance || 0) === 0"
  63. [class.low]="(item.userBalance || 0) > 0 && (item.userBalance || 0) < 100">
  64. <span class="balance-label">余额</span>
  65. <span class="balance-value">{{item.userBalance != null ? item.userBalance : '—'}}</span>
  66. <span class="balance-unit">次</span>
  67. </div>
  68. </div>
  69. <div class="apig-list-card-desc" *ngIf="!apigId">{{item.content || '专业API数据服务'}}</div>
  70. <div class="apig-list-card-footer" *ngIf="item.priceStep && item.priceStep.length > 0">
  71. <span class="apig-list-card-price">
  72. <span class="symbol">¥</span>{{item.priceStep[0].price}}
  73. <span class="price-suffix">起</span>
  74. </span>
  75. <span class="apig-list-card-action" *ngIf="!isCurrentApig(item.objectId)">点击切换 →</span>
  76. </div>
  77. </div>
  78. </div>
  79. <!-- Empty -->
  80. <div class="apig-list-empty" *ngIf="!apigListLoading && apigList.length === 0">
  81. 暂无可用的 API 服务
  82. </div>
  83. </div>
  84. <!-- Error -->
  85. <div class="error-bar" *ngIf="errorMsg">{{errorMsg}}</div>
  86. <!-- Tier Selection -->
  87. <ng-container *ngIf="!showSuccess && !needLogin && apigId">
  88. <div class="section-title">选择套餐</div>
  89. <div class="tier-grid" *ngIf="!apig || !apig.priceStep">
  90. <div class="tier-card neu-raised skeleton" style="height:100px;"></div>
  91. <div class="tier-card neu-raised skeleton" style="height:100px;"></div>
  92. <div class="tier-card neu-raised skeleton" style="height:100px;"></div>
  93. </div>
  94. <div class="tier-grid" *ngIf="apig && apig.priceStep">
  95. <div
  96. *ngFor="let tier of apig.priceStep; let i = index"
  97. class="tier-card"
  98. [class.neu-raised]="selectedIndex !== i"
  99. [class.neu-pressed]="selectedIndex === i"
  100. [class.active]="selectedIndex === i"
  101. (click)="selectTier(i)">
  102. <div class="tier-price"><span class="symbol">¥</span>{{tier.price}}</div>
  103. <div class="tier-count">含 {{tier.count}} {{unitLabel}}</div>
  104. <div class="tier-unit-price">≈ {{(tier.price / tier.count).toFixed(3)}} 元/{{unitLabel}}</div>
  105. </div>
  106. </div>
  107. <!-- Summary -->
  108. <div class="summary-bar neu-groove" *ngIf="selectedTier">
  109. <div>
  110. <div class="label">套餐配额</div>
  111. <div style="font-size:16px;font-weight:600;color:var(--text-bright);margin-top:2px;">
  112. {{selectedTier.count}} {{unitLabel}}
  113. </div>
  114. <div class="validity">有效期 730 天(自购买之日起)</div>
  115. </div>
  116. <div style="text-align:right;">
  117. <div class="label">应付金额</div>
  118. <div class="total"><span class="symbol">¥</span>{{selectedTier.price}}</div>
  119. </div>
  120. </div>
  121. <!-- Pay Button -->
  122. <button class="pay-btn" [disabled]="!apig || paying" (click)="startPayment()">
  123. <span *ngIf="!paying"><span class="btn-icon">💳</span> 微信支付</span>
  124. <span *ngIf="paying">正在生成支付码...</span>
  125. </button>
  126. </ng-container>
  127. <!-- Order History -->
  128. <div class="order-history" *ngIf="apig && !showSuccess">
  129. <div class="section-header">
  130. <div class="section-title">历史订单</div>
  131. <button class="refresh-btn" [disabled]="ordersLoading" (click)="loadOrderHistory()">刷新</button>
  132. </div>
  133. <div class="neu-groove" style="padding:2px 16px;overflow-x:auto;">
  134. <!-- Loading -->
  135. <div class="order-loading" *ngIf="ordersLoading && orders.length === 0">
  136. <span class="polling-dot"></span> 加载中...
  137. </div>
  138. <!-- Empty -->
  139. <div class="order-empty" *ngIf="!ordersLoading && orders.length === 0">
  140. <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
  141. <path d="M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2"/>
  142. <rect x="9" y="3" width="6" height="4" rx="1"/>
  143. </svg>
  144. 暂无订单记录
  145. </div>
  146. <!-- Table -->
  147. <table class="order-table" *ngIf="orders.length > 0">
  148. <thead>
  149. <tr><th>时间</th><th>套餐</th><th>金额</th><th>状态</th></tr>
  150. </thead>
  151. <tbody>
  152. <tr *ngFor="let o of orders">
  153. <td class="col-time">{{formatTime(o.createdAt)}}</td>
  154. <td class="col-count">{{formatCount(o)}}</td>
  155. <td class="col-amount">{{formatAmount(o)}}</td>
  156. <td><span class="order-status" [ngClass]="getStatusClass(o)">{{getStatusText(o)}}</span></td>
  157. </tr>
  158. </tbody>
  159. </table>
  160. <!-- Load more -->
  161. <button class="order-more-btn" *ngIf="hasMoreOrders" (click)="loadOrderHistory(true)">加载更多...</button>
  162. <!-- Error -->
  163. <div class="order-error" *ngIf="orderError">订单加载失败: {{orderError}}</div>
  164. </div>
  165. </div>
  166. <!-- Success Screen -->
  167. <div class="success-screen" [class.show]="showSuccess">
  168. <div class="success-icon">
  169. <svg viewBox="0 0 24 24" fill="none" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
  170. <polyline points="20 6 9 17 4 12"></polyline>
  171. </svg>
  172. </div>
  173. <div class="success-title">充值成功</div>
  174. <div class="success-detail" [innerHTML]="successDetailHtml"></div>
  175. <button class="back-btn" (click)="handleDone()">完成</button>
  176. </div>
  177. </div>
  178. <!-- QR Modal -->
  179. <div class="modal-overlay" [class.show]="showQrModal">
  180. <div class="modal-box neu-raised">
  181. <button class="modal-close" (click)="cancelPayment()">&times;</button>
  182. <div class="modal-title">微信扫码支付</div>
  183. <div class="modal-amount"><span class="symbol">¥</span>{{selectedTier?.price || 0}}</div>
  184. <div class="qr-wrapper">
  185. <canvas #qrCanvas></canvas>
  186. </div>
  187. <div class="scan-hint">
  188. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  189. <path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"></path>
  190. <circle cx="12" cy="13" r="4"></circle>
  191. </svg>
  192. 打开微信扫一扫
  193. </div>
  194. <div class="polling-status">
  195. <span *ngIf="!paySuccess" class="polling-dot"></span>
  196. <span *ngIf="!paySuccess">等待支付中...</span>
  197. <span *ngIf="paySuccess" style="color:var(--success);">✓ 支付成功,正在充值...</span>
  198. </div>
  199. </div>
  200. </div>