From aec7f3a0174b1ee151f4246ade4093b3545ded70 Mon Sep 17 00:00:00 2001 From: WUSIJIAN <13825895+wsj0228@user.noreply.gitee.com> Date: Tue, 20 Jan 2026 17:54:29 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=B5=84=E4=BA=A7=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E8=AE=A2=E9=98=85=E5=8A=9F=E8=83=BD,=E5=9C=A8?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E4=B8=AD=E9=9B=86=E6=88=90=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E6=9C=AA=E5=BC=80=E9=80=9A=E5=BC=B9=E7=AA=97=E7=BB=84=E4=BB=B6?= =?UTF-8?q?,=E5=BD=93=E6=A3=80=E6=B5=8B=E5=88=B0402=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E7=A0=81=E6=97=B6=E8=87=AA=E5=8A=A8=E5=BC=B9=E5=87=BA=E8=AE=A2?= =?UTF-8?q?=E9=98=85=E5=AF=B9=E8=AF=9D=E6=A1=86=E5=BC=95=E5=AF=BC=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=BC=80=E9=80=9A=E6=9C=8D=E5=8A=A1,=E5=90=8C?= =?UTF-8?q?=E6=97=B6=E6=96=B0=E5=A2=9E=E8=B5=84=E4=BA=A7=E8=AE=A2=E9=98=85?= =?UTF-8?q?=E7=9B=B8=E5=85=B3API=E6=8E=A5=E5=8F=A3=E5=8C=85=E6=8B=AC?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=B5=84=E4=BA=A7SKU=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E5=92=8C=E8=AE=A2=E9=98=85=E6=9C=8D=E5=8A=A1=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?,=E5=9C=A8=E8=AF=B7=E6=B1=82=E6=8B=A6=E6=88=AA=E5=99=A8?= =?UTF-8?q?=E4=B8=AD=E6=B7=BB=E5=8A=A0402=E7=8A=B6=E6=80=81=E7=A0=81?= =?UTF-8?q?=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=E5=B9=B6=E8=BF=87=E6=BB=A4?= =?UTF-8?q?SKU=E6=9F=A5=E8=AF=A2=E6=8E=A5=E5=8F=A3=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E5=BE=AA=E7=8E=AF=E8=A7=A6=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.vue | 11 +- src/api/assets/asset/index.ts | 24 ++ src/components/assetSubscribe/index.vue | 277 ++++++++++++++++++++++++ src/utils/assetSubscribe.ts | 83 +++++++ src/utils/request.ts | 28 ++- 5 files changed, 420 insertions(+), 3 deletions(-) create mode 100644 src/components/assetSubscribe/index.vue create mode 100644 src/utils/assetSubscribe.ts diff --git a/src/App.vue b/src/App.vue index c06763c..61073d9 100644 --- a/src/App.vue +++ b/src/App.vue @@ -4,6 +4,12 @@ + + @@ -19,10 +25,12 @@ import setIntroduction from '/@/utils/setIconfont'; import LockScreen from '/@/layout/lockScreen/index.vue'; import Setings from '/@/layout/navBars/breadcrumb/setings.vue'; import CloseFull from '/@/layout/navBars/breadcrumb/closeFull.vue'; +import AssetSubscribeDialog from '/@/components/assetSubscribe/index.vue'; +import { assetSubscribeState } from '/@/utils/assetSubscribe'; export default defineComponent({ name: 'app', - components: { LockScreen, Setings, CloseFull }, + components: { LockScreen, Setings, CloseFull, AssetSubscribeDialog }, setup() { const { proxy } = getCurrentInstance(); const setingsRef = ref(); @@ -89,6 +97,7 @@ export default defineComponent({ themeConfig, setingsRef, getGlobalComponentSize, + assetSubscribeState, ...toRefs(state), }; }, diff --git a/src/api/assets/asset/index.ts b/src/api/assets/asset/index.ts index 8fc2218..f36e3f9 100644 --- a/src/api/assets/asset/index.ts +++ b/src/api/assets/asset/index.ts @@ -119,6 +119,15 @@ export function listAssetSkus(params: SkuQueryParams) { }); } +// 根据assetId获取资产和SKU信息(用于套餐开通弹窗) +export function getAssetAndSku(params: { assetId: string }) { + return newService({ + url: '/assets/asset/getAssetAndSku', + method: 'get', + params, + }); +} + // 创建 SKU export function createAssetSku(data: CreateSkuParams) { return newService({ @@ -220,3 +229,18 @@ export function listLogs(params: LogQueryParams) { params, }); } + +// 订阅/开通资产服务参数 +export interface SubscribeAssetParams { + skuId: string; + assetId?: string; +} + +// 订阅/开通资产服务 +export function subscribeAsset(data: SubscribeAssetParams) { + return newService({ + url: '/assets/asset/subscribe', + method: 'post', + data, + }); +} diff --git a/src/components/assetSubscribe/index.vue b/src/components/assetSubscribe/index.vue new file mode 100644 index 0000000..c4ce99d --- /dev/null +++ b/src/components/assetSubscribe/index.vue @@ -0,0 +1,277 @@ + + + + + + {{ serviceName }} + 服务未开通,请选择套餐进行开通 + + + + + + + + + + + + + + {{ sku.skuName }} + 不限库存 + + + + {{ sku.specsCount }} + {{ sku.specsUnit?.value || '' }} + + + ¥ + {{ (sku.price / 100).toFixed(2) }} + + + + + + + + 取消 + + 立即开通 + + + + + + + + diff --git a/src/utils/assetSubscribe.ts b/src/utils/assetSubscribe.ts new file mode 100644 index 0000000..1df0289 --- /dev/null +++ b/src/utils/assetSubscribe.ts @@ -0,0 +1,83 @@ +import { ref } from 'vue'; + +// 路由路径与 assetId 的映射关系 +const ROUTE_ASSET_MAP: Record = { + // CID广告业务(聚合广告) + '/cidService': { assetId: '696f423705e496ba4ccbe665', serviceName: '聚合广告' }, + + // AI客服业务 + '/customerService': { assetId: '696f421205e496ba4ccbe662', serviceName: 'AI客服' }, + + // 聚合电商业务(资产管理) + '/assets': { assetId: '696b4acd1be1c8b76c4b4c15', serviceName: '资产管理' }, + + // 订单 + // '/order': { assetId: '696b4acd1be1c8b76c4b4c15', serviceName: '资产管理' }, + + // AI数字人 + // '/digitalHuman': { assetId: '696f421205e496ba4ccbe662', serviceName: 'AI客服' }, +}; + +// 当前弹窗状态(响应式,供组件使用) +export const assetSubscribeState = ref({ + visible: false, + assetId: '', + serviceName: '', +}); + +/** + * 根据路由路径获取对应的 assetId 和服务名称 + */ +export function getAssetInfoByRoute(routePath: string): { assetId: string; serviceName: string } | null { + // 精确匹配 + if (ROUTE_ASSET_MAP[routePath]) { + return ROUTE_ASSET_MAP[routePath]; + } + + // 前缀匹配 + for (const [prefix, info] of Object.entries(ROUTE_ASSET_MAP)) { + if (routePath.startsWith(prefix)) { + return info; + } + } + + return null; +} + +/** + * 显示服务开通弹窗 + */ +export function showAssetSubscribeDialog(assetId: string, serviceName: string) { + console.log('[showAssetSubscribeDialog] 显示弹窗:', { assetId, serviceName }); + console.log('[showAssetSubscribeDialog] 修改前状态:', JSON.stringify(assetSubscribeState.value)); + assetSubscribeState.value.visible = true; + assetSubscribeState.value.assetId = assetId; + assetSubscribeState.value.serviceName = serviceName; + console.log('[showAssetSubscribeDialog] 修改后状态:', JSON.stringify(assetSubscribeState.value)); +} + +/** + * 关闭服务开通弹窗 + */ +export function closeAssetSubscribeDialog() { + assetSubscribeState.value.visible = false; +} + +/** + * 处理 403 错误码(模块未开通) + */ +export function handleModuleNotEnabled(routePath: string): boolean { + console.log('[模块未开通] 当前路由路径:', routePath); + const assetInfo = getAssetInfoByRoute(routePath); + console.log('[模块未开通] 匹配到的资产信息:', assetInfo); + + if (assetInfo) { + showAssetSubscribeDialog(assetInfo.assetId, assetInfo.serviceName); + return true; + } + + // 如果没有匹配到路由,尝试使用默认的资产管理 + console.warn('[模块未开通] 未匹配到路由,使用默认资产管理'); + showAssetSubscribeDialog('696b4acd1be1c8b76c4b4c15', '资产管理'); + return true; +} diff --git a/src/utils/request.ts b/src/utils/request.ts index cad63b3..5ca7d7a 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -3,6 +3,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'; import { Session } from '/@/utils/storage'; import qs from 'qs'; import { getChangedFields } from '/@/utils/diffUtils'; +import { handleModuleNotEnabled } from '/@/utils/assetSubscribe'; // 标记是否正在处理 token 过期,避免重复弹窗 let isHandlingTokenExpired = false; @@ -156,8 +157,20 @@ const responseInterceptor = (response: AxiosResponse) => { return Promise.reject(new Error('登录状态已过期')); } - // 业务逻辑错误处理 - if (code !== undefined && code !== 0 && code !== 200) { + // 处理模块未开通错误 (403) + // 跳过资产SKU查询接口,避免弹窗内部请求触发循环 + const requestUrl = response.config.url || ''; + if (code === 402 && !requestUrl.includes('/assets/asset/sku/')) { + // 获取当前路由路径 + const currentPath = window.location.hash.replace('#', '') || window.location.pathname; + console.log('[request.ts] 检测到403错误,当前路径:', currentPath); + handleModuleNotEnabled(currentPath); + // 直接返回,不再显示错误消息 + return Promise.reject(new Error('模块未开通')); + } + + // 业务逻辑错误处理(排除403,因为上面已处理) + if (code !== undefined && code !== 0 && code !== 200 && code !== 403) { const errorMsg = message || `请求失败(${code})`; showErrorMessage(errorMsg); return Promise.reject(new Error(errorMsg)); @@ -189,10 +202,21 @@ const responseErrorHandler = (error: any) => { const responseMessage = error.response.data?.message; // 处理 HTTP 错误状态 + const requestUrl = error.response.config?.url || ''; switch (httpStatus) { case 401: handleTokenExpired(); break; + case 402: + // 模块未开通处理,跳过SKU相关接口避免循环 + if (!requestUrl.includes('/assets/asset/sku/') && !requestUrl.includes('getAssetAndSku')) { + const currentPath = window.location.hash.replace('#', '') || window.location.pathname; + console.log('[responseErrorHandler] 检测到HTTP 402错误,当前路径:', currentPath); + handleModuleNotEnabled(currentPath); + return Promise.reject(new Error('模块未开通')); + } + showErrorMessage(responseMessage || '服务未开通'); + break; case 403: showErrorMessage(responseMessage || '没有权限访问该资源'); break;