2026-04-02 14:14:26 +08:00
|
|
|
|
<!doctype html>
|
2026-01-21 14:02:42 +08:00
|
|
|
|
<html lang="zh-CN">
|
2026-04-02 14:14:26 +08:00
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="UTF-8" />
|
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
|
|
|
|
<title>服务开通 - 智能营销服务平台</title>
|
|
|
|
|
|
<link rel="stylesheet" href="main.css" />
|
|
|
|
|
|
<link rel="stylesheet" href="style.css" />
|
|
|
|
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700;800&display=swap" rel="stylesheet" />
|
|
|
|
|
|
<style>
|
|
|
|
|
|
* {
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.subscribe-container {
|
|
|
|
|
|
max-width: 800px;
|
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
|
padding: 32px 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.subscribe-header {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
margin-bottom: 32px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.subscribe-header h1 {
|
|
|
|
|
|
font-size: 1.75rem;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
color: #1e293b;
|
|
|
|
|
|
margin: 0 0 8px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.subscribe-header p {
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font-size: 0.95rem;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.asset-info {
|
|
|
|
|
|
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
padding: 20px 24px;
|
|
|
|
|
|
margin-bottom: 28px;
|
|
|
|
|
|
border: 1px solid #bae6fd;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.asset-info .icon-wrapper {
|
|
|
|
|
|
width: 44px;
|
|
|
|
|
|
height: 44px;
|
|
|
|
|
|
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.asset-info .icon-wrapper svg {
|
|
|
|
|
|
width: 24px;
|
|
|
|
|
|
height: 24px;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.asset-info .info-content {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.asset-info .asset-name {
|
|
|
|
|
|
font-size: 1.1rem;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #0c4a6e;
|
|
|
|
|
|
margin: 0 0 6px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.asset-info .description {
|
|
|
|
|
|
color: #0369a1;
|
|
|
|
|
|
font-size: 0.875rem;
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.asset-info .description p {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
|
|
font-size: 0.875rem;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #475569;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(3, 1fr);
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 600px) {
|
|
|
|
|
|
.type-grid {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-card {
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border: 2px solid #e2e8f0;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
padding: 14px 16px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-card:hover {
|
|
|
|
|
|
border-color: #10b981;
|
|
|
|
|
|
background: #f0fdf4;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-card.selected {
|
|
|
|
|
|
border-color: #10b981;
|
|
|
|
|
|
background: #ecfdf5;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-card.selected::after {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 8px;
|
|
|
|
|
|
right: 8px;
|
|
|
|
|
|
width: 18px;
|
|
|
|
|
|
height: 18px;
|
|
|
|
|
|
background: #10b981
|
|
|
|
|
|
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E")
|
|
|
|
|
|
center/12px no-repeat;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-card .type-name {
|
|
|
|
|
|
font-size: 0.95rem;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: #334155;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sku-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
margin-bottom: 28px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 600px) {
|
|
|
|
|
|
.sku-grid {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sku-card {
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border: 2px solid #e2e8f0;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sku-card:hover {
|
|
|
|
|
|
border-color: #3b82f6;
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.12);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sku-card.selected {
|
|
|
|
|
|
border-color: #3b82f6;
|
|
|
|
|
|
background: #f8faff;
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sku-card.selected::after {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 12px;
|
|
|
|
|
|
right: 12px;
|
|
|
|
|
|
width: 22px;
|
|
|
|
|
|
height: 22px;
|
|
|
|
|
|
background: #3b82f6
|
|
|
|
|
|
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E")
|
|
|
|
|
|
center/14px no-repeat;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sku-name {
|
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #334155;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
padding-right: 30px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sku-content {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: flex-end;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sku-specs {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: baseline;
|
|
|
|
|
|
gap: 2px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sku-specs .count {
|
|
|
|
|
|
font-size: 2rem;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
color: #3b82f6;
|
|
|
|
|
|
line-height: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sku-specs .unit {
|
|
|
|
|
|
font-size: 0.875rem;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
margin-left: 2px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sku-price {
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sku-price .amount {
|
|
|
|
|
|
color: #ef4444;
|
|
|
|
|
|
font-size: 1.25rem;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sku-price .symbol {
|
|
|
|
|
|
font-size: 0.875rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.subscribe-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
padding-top: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn-subscribe {
|
|
|
|
|
|
padding: 12px 36px;
|
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn-subscribe.primary {
|
|
|
|
|
|
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn-subscribe.primary:hover {
|
|
|
|
|
|
background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn-subscribe.primary:disabled {
|
|
|
|
|
|
background: #94a3b8;
|
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn-subscribe.secondary {
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
border: 1px solid #e2e8f0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn-subscribe.secondary:hover {
|
|
|
|
|
|
border-color: #cbd5e1;
|
|
|
|
|
|
background: #f8fafc;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.loading-container {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
padding: 48px 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.loading-spinner {
|
|
|
|
|
|
width: 40px;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
border: 3px solid #e2e8f0;
|
|
|
|
|
|
border-top-color: #3b82f6;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
animation: spin 0.8s linear infinite;
|
|
|
|
|
|
margin: 0 auto 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes spin {
|
|
|
|
|
|
to {
|
|
|
|
|
|
transform: rotate(360deg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.empty-container {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
padding: 48px 20px;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.empty-container svg {
|
|
|
|
|
|
width: 56px;
|
|
|
|
|
|
height: 56px;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
color: #cbd5e1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.error-message {
|
|
|
|
|
|
background: #fef2f2;
|
|
|
|
|
|
border: 1px solid #fecaca;
|
|
|
|
|
|
color: #dc2626;
|
|
|
|
|
|
padding: 14px 20px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
font-size: 0.95rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.success-message {
|
|
|
|
|
|
background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
|
|
|
|
|
|
border: 1px solid #86efac;
|
|
|
|
|
|
color: #166534;
|
|
|
|
|
|
padding: 32px;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.success-message h3 {
|
|
|
|
|
|
font-size: 1.25rem;
|
|
|
|
|
|
margin: 0 0 8px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.success-message p {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: #15803d;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
</head>
|
|
|
|
|
|
<body class="auth-body">
|
|
|
|
|
|
<!-- 导航 -->
|
|
|
|
|
|
<nav class="auth-navbar">
|
|
|
|
|
|
<div class="auth-navbar-container">
|
|
|
|
|
|
<a href="/index.html#/home" class="auth-back-link">
|
|
|
|
|
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
返回首页
|
|
|
|
|
|
</a>
|
|
|
|
|
|
<span class="auth-title">服务开通</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
|
|
<main class="subscribe-container">
|
|
|
|
|
|
<div class="subscribe-header">
|
|
|
|
|
|
<h1>开通服务</h1>
|
|
|
|
|
|
<p>选择适合您的套餐,立即开通使用</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 加载中 -->
|
|
|
|
|
|
<div id="loading" class="loading-container">
|
|
|
|
|
|
<div class="loading-spinner"></div>
|
|
|
|
|
|
<p>正在加载套餐信息...</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 错误信息 -->
|
|
|
|
|
|
<div id="error" class="error-message" style="display: none"></div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 资产信息 -->
|
|
|
|
|
|
<div id="asset-info" class="asset-info" style="display: none">
|
|
|
|
|
|
<div class="icon-wrapper">
|
|
|
|
|
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="info-content">
|
|
|
|
|
|
<h3 id="asset-name" class="asset-name"></h3>
|
|
|
|
|
|
<div id="asset-description" class="description"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 用户类型选择 -->
|
|
|
|
|
|
<div id="type-section" style="display: none">
|
|
|
|
|
|
<div class="section-title">选择类型</div>
|
|
|
|
|
|
<div id="type-list" class="type-grid"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- SKU 列表 -->
|
|
|
|
|
|
<div id="sku-section" style="display: none">
|
|
|
|
|
|
<div class="section-title">选择套餐</div>
|
|
|
|
|
|
<div id="sku-list" class="sku-grid"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 空状态 -->
|
|
|
|
|
|
<div id="empty" class="empty-container" style="display: none">
|
|
|
|
|
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
|
<path
|
|
|
|
|
|
stroke-linecap="round"
|
|
|
|
|
|
stroke-linejoin="round"
|
|
|
|
|
|
stroke-width="2"
|
|
|
|
|
|
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
|
|
|
|
|
|
></path>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
<p>暂无可用套餐</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 成功信息 -->
|
|
|
|
|
|
<div id="success" class="success-message" style="display: none">
|
|
|
|
|
|
<h3>🎉 开通成功!</h3>
|
|
|
|
|
|
<p>正在返回...</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 操作按钮 -->
|
|
|
|
|
|
<div id="actions" class="subscribe-actions" style="display: none">
|
|
|
|
|
|
<button class="btn-subscribe secondary" onclick="handleCancel()">取消</button>
|
|
|
|
|
|
<button id="btn-submit" class="btn-subscribe primary" onclick="handleSubscribe()" disabled>立即开通</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
// API 基础地址配置
|
|
|
|
|
|
// 切换环境时,修改下面的 SERVER_IP 即可
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
const SERVER_IP = '192.168.3.11'; // 后端服务器IP(备用: 192.168.3.200)
|
|
|
|
|
|
const API_BASE_NEW = `http://${SERVER_IP}:8000`; // 新功能服务(端口8000)- 资产、SKU查询
|
|
|
|
|
|
const API_BASE_MAIN = `http://${SERVER_IP}:8808`; // 主服务(端口8808)- 模块开通
|
|
|
|
|
|
|
|
|
|
|
|
// 页面状态
|
|
|
|
|
|
let assetId = '';
|
|
|
|
|
|
let returnUrl = '';
|
|
|
|
|
|
let selectedSku = null;
|
|
|
|
|
|
let selectedType = null;
|
|
|
|
|
|
let assetData = null;
|
|
|
|
|
|
let tenantModuleTypes = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化
|
|
|
|
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
|
const params = new URLSearchParams(window.location.search);
|
|
|
|
|
|
assetId = params.get('assetId') || '';
|
|
|
|
|
|
returnUrl = params.get('returnUrl') || '/index.html';
|
|
|
|
|
|
|
|
|
|
|
|
if (!assetId) {
|
|
|
|
|
|
showError('缺少资产ID参数');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
loadAssetAndSku();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 加载资产和SKU信息
|
|
|
|
|
|
async function loadAssetAndSku() {
|
|
|
|
|
|
showLoading(true);
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const token = getToken();
|
|
|
|
|
|
|
|
|
|
|
|
const headers = {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
};
|
|
|
|
|
|
if (token) {
|
|
|
|
|
|
headers['Authorization'] = `Bearer ${token}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const response = await fetch(`${API_BASE_NEW}/assets/asset/getAssetAndSku?assetId=${assetId}`, {
|
|
|
|
|
|
headers: headers,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 先获取文本,检查是否为有效JSON
|
|
|
|
|
|
const text = await response.text();
|
|
|
|
|
|
|
|
|
|
|
|
let result;
|
|
|
|
|
|
try {
|
|
|
|
|
|
result = JSON.parse(text);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
throw new Error('服务器返回数据格式错误');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (result.code !== 0) {
|
|
|
|
|
|
throw new Error(result.message || '加载失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
assetData = result.data;
|
|
|
|
|
|
tenantModuleTypes = assetData.tenantModuleType || [];
|
|
|
|
|
|
renderAssetInfo(assetData);
|
|
|
|
|
|
renderTypeList(tenantModuleTypes);
|
|
|
|
|
|
renderSkuList(assetData.skus || []);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
showError(error.message || '加载套餐信息失败,请稍后重试');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
showLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染资产信息
|
|
|
|
|
|
function renderAssetInfo(data) {
|
|
|
|
|
|
document.getElementById('asset-name').textContent = data.name || '服务';
|
|
|
|
|
|
document.getElementById('asset-description').innerHTML = data.description || '';
|
|
|
|
|
|
document.getElementById('asset-info').style.display = 'flex';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染用户类型列表
|
|
|
|
|
|
function renderTypeList(types) {
|
|
|
|
|
|
const container = document.getElementById('type-list');
|
|
|
|
|
|
const section = document.getElementById('type-section');
|
|
|
|
|
|
|
|
|
|
|
|
if (!types || types.length === 0) {
|
|
|
|
|
|
section.style.display = 'none';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
container.innerHTML = types
|
|
|
|
|
|
.map(
|
|
|
|
|
|
(type) => `
|
2026-01-21 16:27:54 +08:00
|
|
|
|
<div class="type-card" data-type-key="${type.key}" onclick="selectType('${type.key}')">
|
|
|
|
|
|
<div class="type-name">${type.value}</div>
|
|
|
|
|
|
</div>
|
2026-04-02 14:14:26 +08:00
|
|
|
|
`
|
|
|
|
|
|
)
|
|
|
|
|
|
.join('');
|
|
|
|
|
|
|
|
|
|
|
|
section.style.display = 'block';
|
|
|
|
|
|
|
|
|
|
|
|
// 默认选中第一个
|
|
|
|
|
|
if (types.length > 0) {
|
|
|
|
|
|
selectType(types[0].key);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 选择用户类型
|
|
|
|
|
|
function selectType(typeKey) {
|
|
|
|
|
|
// 移除之前的选中状态
|
|
|
|
|
|
document.querySelectorAll('.type-card').forEach((card) => {
|
|
|
|
|
|
card.classList.remove('selected');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 添加选中状态
|
|
|
|
|
|
const card = document.querySelector(`.type-card[data-type-key="${typeKey}"]`);
|
|
|
|
|
|
if (card) {
|
|
|
|
|
|
card.classList.add('selected');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 保存选中的类型
|
|
|
|
|
|
selectedType = tenantModuleTypes.find((t) => t.key === typeKey) || null;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新按钮状态
|
|
|
|
|
|
updateSubmitButton();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染SKU列表
|
|
|
|
|
|
function renderSkuList(skus) {
|
|
|
|
|
|
const container = document.getElementById('sku-list');
|
|
|
|
|
|
const section = document.getElementById('sku-section');
|
|
|
|
|
|
const actions = document.getElementById('actions');
|
|
|
|
|
|
const empty = document.getElementById('empty');
|
|
|
|
|
|
|
|
|
|
|
|
if (!skus || skus.length === 0) {
|
|
|
|
|
|
empty.style.display = 'block';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
container.innerHTML = skus
|
|
|
|
|
|
.map(
|
|
|
|
|
|
(sku) => `
|
2026-01-21 14:02:42 +08:00
|
|
|
|
<div class="sku-card" data-sku-id="${sku.id}" onclick="selectSku('${sku.id}')">
|
|
|
|
|
|
<div class="sku-name">${sku.skuName}</div>
|
|
|
|
|
|
<div class="sku-content">
|
|
|
|
|
|
<div class="sku-specs">
|
|
|
|
|
|
<span class="count">${sku.specsCount}</span>
|
|
|
|
|
|
<span class="unit">${sku.specsUnit?.value || ''}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="sku-price">
|
|
|
|
|
|
<span class="amount"><span class="symbol">¥</span>${(sku.price / 100).toFixed(2)}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-04-02 14:14:26 +08:00
|
|
|
|
`
|
|
|
|
|
|
)
|
|
|
|
|
|
.join('');
|
|
|
|
|
|
|
|
|
|
|
|
section.style.display = 'block';
|
|
|
|
|
|
actions.style.display = 'flex';
|
|
|
|
|
|
|
|
|
|
|
|
// 默认选中第一个
|
|
|
|
|
|
if (skus.length > 0) {
|
|
|
|
|
|
selectSku(skus[0].id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 选择SKU
|
|
|
|
|
|
function selectSku(skuId) {
|
|
|
|
|
|
// 移除之前的选中状态
|
|
|
|
|
|
document.querySelectorAll('.sku-card').forEach((card) => {
|
|
|
|
|
|
card.classList.remove('selected');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 添加选中状态
|
|
|
|
|
|
const card = document.querySelector(`.sku-card[data-sku-id="${skuId}"]`);
|
|
|
|
|
|
if (card) {
|
|
|
|
|
|
card.classList.add('selected');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 保存选中的SKU
|
|
|
|
|
|
selectedSku = assetData?.skus?.find((s) => s.id === skuId) || null;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新按钮状态
|
|
|
|
|
|
updateSubmitButton();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新提交按钮状态
|
|
|
|
|
|
function updateSubmitButton() {
|
|
|
|
|
|
const btn = document.getElementById('btn-submit');
|
|
|
|
|
|
// 如果有用户类型选项,则需要同时选择类型和套餐
|
|
|
|
|
|
if (tenantModuleTypes.length > 0) {
|
|
|
|
|
|
btn.disabled = !selectedSku || !selectedType;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
btn.disabled = !selectedSku;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 开通服务
|
|
|
|
|
|
async function handleSubscribe() {
|
|
|
|
|
|
if (!selectedSku) {
|
|
|
|
|
|
alert('请选择套餐');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有用户类型选项但未选择
|
|
|
|
|
|
if (tenantModuleTypes.length > 0 && !selectedType) {
|
|
|
|
|
|
alert('请选择用户类型');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const btn = document.getElementById('btn-submit');
|
|
|
|
|
|
btn.disabled = true;
|
|
|
|
|
|
btn.textContent = '开通中...';
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const token = getToken();
|
|
|
|
|
|
|
|
|
|
|
|
// 构建请求参数
|
|
|
|
|
|
const requestBody = {
|
|
|
|
|
|
assetSkuId: selectedSku.id,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 如果选择了用户类型,添加到请求参数
|
|
|
|
|
|
if (selectedType) {
|
|
|
|
|
|
requestBody.tenantModuleType = selectedType.key;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const response = await fetch(`${API_BASE_MAIN}/admin-go/api/v1/system/moduleTenant/add`, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
Authorization: token ? `Bearer ${token}` : '',
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify(requestBody),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
if (result.code !== 0) {
|
|
|
|
|
|
throw new Error(result.message || '开通失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示成功
|
|
|
|
|
|
document.getElementById('type-section').style.display = 'none';
|
|
|
|
|
|
document.getElementById('sku-section').style.display = 'none';
|
|
|
|
|
|
document.getElementById('actions').style.display = 'none';
|
|
|
|
|
|
document.getElementById('asset-info').style.display = 'none';
|
|
|
|
|
|
document.getElementById('success').style.display = 'block';
|
|
|
|
|
|
|
|
|
|
|
|
// 设置开通时间标记,防止跳转后立即又触发402
|
|
|
|
|
|
sessionStorage.setItem('lastSubscribeTime', Date.now().toString());
|
|
|
|
|
|
|
|
|
|
|
|
// 延迟跳转回原页面
|
|
|
|
|
|
const targetUrl = decodeURIComponent(returnUrl);
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
let finalUrl;
|
|
|
|
|
|
// 如果 returnUrl 包含当前开通页面路径,则跳转到首页
|
|
|
|
|
|
if (targetUrl.includes('/web/subscribe') || targetUrl.includes('subscribe.html')) {
|
|
|
|
|
|
console.log('[subscribe] returnUrl 指向开通页面,改为跳转首页');
|
|
|
|
|
|
finalUrl = '/index.html#/home';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
finalUrl = targetUrl;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用 replace 跳转,然后强制刷新
|
|
|
|
|
|
window.location.replace(finalUrl);
|
|
|
|
|
|
// 延迟一点再刷新,确保 URL 已经改变
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
window.location.reload(true);
|
|
|
|
|
|
}, 100);
|
|
|
|
|
|
}, 2000);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('开通失败:', error);
|
|
|
|
|
|
alert(error.message || '开通失败,请稍后重试');
|
|
|
|
|
|
btn.disabled = false;
|
|
|
|
|
|
btn.textContent = '立即开通';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 取消 - 跳转到后台首页,避免循环触发402
|
|
|
|
|
|
function handleCancel() {
|
|
|
|
|
|
window.location.href = '/index.html#/home';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取Token(从Cookie获取)
|
|
|
|
|
|
function getToken() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const cookies = document.cookie.split(';');
|
|
|
|
|
|
for (let cookie of cookies) {
|
|
|
|
|
|
const parts = cookie.trim().split('=');
|
|
|
|
|
|
const name = parts[0];
|
|
|
|
|
|
// token 值可能包含 = 号,所以用 slice 获取剩余部分
|
|
|
|
|
|
const value = parts.slice(1).join('=');
|
|
|
|
|
|
if (name === 'token' && value) {
|
|
|
|
|
|
// Cookie 中的值可能被 URL 编码,需要解码
|
|
|
|
|
|
try {
|
|
|
|
|
|
return decodeURIComponent(value);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
return value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('获取token失败:', e);
|
|
|
|
|
|
}
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示/隐藏加载
|
|
|
|
|
|
function showLoading(show) {
|
|
|
|
|
|
document.getElementById('loading').style.display = show ? 'block' : 'none';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示错误
|
|
|
|
|
|
function showError(message) {
|
|
|
|
|
|
const errorEl = document.getElementById('error');
|
|
|
|
|
|
errorEl.textContent = message;
|
|
|
|
|
|
errorEl.style.display = 'block';
|
|
|
|
|
|
document.getElementById('loading').style.display = 'none';
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
</body>
|
2026-01-21 14:02:42 +08:00
|
|
|
|
</html>
|