优化资产订阅页面,新增用户类型选择功能,支持在开通服务时选择租户模块类型,同时优化开通成功后的跳转逻辑,增加5秒内防重复触发402状态码的保护机制,避免开通完成后立即跳转又触发未开通提示的循环问题

This commit is contained in:
WUSIJIAN
2026-01-21 16:27:54 +08:00
parent af17d81422
commit b9795c2cc4
2 changed files with 190 additions and 21 deletions

View File

@@ -86,6 +86,64 @@
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);
@@ -292,7 +350,7 @@
<!-- 导航 -->
<nav class="auth-navbar">
<div class="auth-navbar-container">
<a href="index.html" class="auth-back-link">
<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>
@@ -326,8 +384,17 @@
</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-list" class="sku-grid" style="display: none;"></div>
<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;">
@@ -350,13 +417,16 @@
<script>
// API 基础地址
const API_BASE = 'http://192.168.3.11:8000';
const API_BASE_NEW = 'http://192.168.3.11:8000'; // 新功能服务(资产相关)
const API_BASE_MAIN = 'http://192.168.3.11:8808'; // 主服务(系统相关)
// 页面状态
let assetId = '';
let returnUrl = '';
let selectedSku = null;
let selectedType = null;
let assetData = null;
let tenantModuleTypes = [];
// 初始化
window.addEventListener('DOMContentLoaded', () => {
@@ -387,7 +457,7 @@
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(`${API_BASE}/assets/asset/getAssetAndSku?assetId=${assetId}`, {
const response = await fetch(`${API_BASE_NEW}/assets/asset/getAssetAndSku?assetId=${assetId}`, {
headers: headers
});
@@ -407,7 +477,9 @@
}
assetData = result.data;
tenantModuleTypes = assetData.tenantModuleType || [];
renderAssetInfo(assetData);
renderTypeList(tenantModuleTypes);
renderSkuList(assetData.skus || []);
} catch (error) {
@@ -422,12 +494,57 @@
function renderAssetInfo(data) {
document.getElementById('asset-name').textContent = data.name || '服务';
document.getElementById('asset-description').innerHTML = data.description || '';
document.getElementById('asset-info').style.display = 'block';
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 => `
<div class="type-card" data-type-key="${type.key}" onclick="selectType('${type.key}')">
<div class="type-name">${type.value}</div>
</div>
`).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');
@@ -451,7 +568,7 @@
</div>
`).join('');
container.style.display = 'grid';
section.style.display = 'block';
actions.style.display = 'flex';
// 默认选中第一个
@@ -477,7 +594,18 @@
selectedSku = assetData?.skus?.find(s => s.id === skuId) || null;
// 更新按钮状态
document.getElementById('btn-submit').disabled = !selectedSku;
updateSubmitButton();
}
// 更新提交按钮状态
function updateSubmitButton() {
const btn = document.getElementById('btn-submit');
// 如果有用户类型选项,则需要同时选择类型和套餐
if (tenantModuleTypes.length > 0) {
btn.disabled = !selectedSku || !selectedType;
} else {
btn.disabled = !selectedSku;
}
}
// 开通服务
@@ -487,22 +615,36 @@
return;
}
// 如果有用户类型选项但未选择
if (tenantModuleTypes.length > 0 && !selectedType) {
alert('请选择用户类型');
return;
}
const btn = document.getElementById('btn-submit');
btn.disabled = true;
btn.textContent = '开通中...';
try {
const token = getToken();
const response = await fetch(`${API_BASE}/assets/asset/subscribe`, {
// 构建请求参数
const requestBody = {
assetSkuId: selectedSku.id
};
// 如果选择了用户类型,添加到请求参数
if (selectedType) {
requestBody.tenantModuleType = selectedType.key;
}
const response = await fetch(`${API_BASE_MAIN}/api/v1/system/moduleTenant/add`, {
method: 'POST',
headers: {
'Authorization': token ? `Bearer ${token}` : '',
'Content-Type': 'application/json'
},
body: JSON.stringify({
skuId: selectedSku.id,
assetId: selectedSku.assetId
})
body: JSON.stringify(requestBody)
});
const result = await response.json();
@@ -512,15 +654,37 @@
}
// 显示成功
document.getElementById('sku-list').style.display = 'none';
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);
console.log('[subscribe] 开通成功,即将跳转到:', targetUrl);
console.log('[subscribe] 原始 returnUrl:', returnUrl);
setTimeout(() => {
window.location.href = decodeURIComponent(returnUrl);
}, 1500);
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);
@@ -530,13 +694,9 @@
}
}
// 取消
// 取消 - 跳转到后台首页避免循环触发402
function handleCancel() {
if (returnUrl) {
window.location.href = decodeURIComponent(returnUrl);
} else {
window.location.href = 'index.html';
}
window.location.href = '/index.html#/home';
}
// 获取Token从Cookie获取

View File

@@ -210,6 +210,15 @@ const responseErrorHandler = (error: any) => {
case 402:
// 模块未开通处理跳过SKU相关接口避免循环
if (!requestUrl.includes('/assets/asset/sku/') && !requestUrl.includes('getAssetAndSku')) {
// 检查是否刚从开通页面返回5秒内不再跳转
const lastSubscribeTime = sessionStorage.getItem('lastSubscribeTime');
const now = Date.now();
if (lastSubscribeTime && now - parseInt(lastSubscribeTime) < 5000) {
console.log('[responseErrorHandler] 刚完成开通跳过402处理');
showErrorMessage(responseMessage || '服务开通中,请稍后刷新页面');
return Promise.reject(new Error('模块开通中'));
}
const currentPath = window.location.hash.replace('#', '') || window.location.pathname;
console.log('[responseErrorHandler] 检测到HTTP 402错误当前路径:', currentPath);
handleModuleNotEnabled(currentPath);