From 3bc96be89b7b81ba9efee9ca8dd1dfae43956123 Mon Sep 17 00:00:00 2001
From: liangzai <2440983361@qq.com>
Date: Sat, 28 Feb 2026 17:57:40 +0800
Subject: [PATCH] f
---
src/App.vue | 8 +-
src/api/agent.js | 65 ++++
src/components/BaseReport.vue | 383 ++++++++++++++++++-----
src/components/WhitelistModuleDialog.vue | 227 ++++++++++++++
src/composables/useWeixinShare.js | 63 ++--
src/router/index.js | 6 +-
src/views/Example.vue | 16 +
src/views/Inquire.vue | 31 +-
src/views/PaymentResult.vue | 49 ++-
vite.config.js | 4 +-
10 files changed, 734 insertions(+), 118 deletions(-)
create mode 100644 src/components/WhitelistModuleDialog.vue
diff --git a/src/App.vue b/src/App.vue
index 1b4ed20..f399c3a 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -41,17 +41,17 @@ onMounted(async () => {
}
getWeixinAuthUrl();
- // 延迟配置微信分享
+ // 延迟配置微信分享(带上当前路由信息)
setTimeout(async () => {
if (isWeChat.value && window.jWeixin) {
- await setDynamicShare();
+ await setDynamicShare(route);
}
}, 1000);
// 监听路由变化更新分享配置
- router.afterEach(async () => {
+ router.afterEach(async (to) => {
if (isWeChat.value && window.jWeixin && !authStore.isWeixinAuthing) {
- await setDynamicShare();
+ await setDynamicShare(to);
}
});
});
diff --git a/src/api/agent.js b/src/api/agent.js
index 14391a4..9f838b7 100644
--- a/src/api/agent.js
+++ b/src/api/agent.js
@@ -278,3 +278,68 @@ export function getInviteLink(params) {
const queryString = buildQueryString(params || {});
return useApiFetch(`/agent/invite_link${queryString}`).get().json();
}
+
+// ==================== 白名单相关接口 ====================
+
+/**
+ * 获取可屏蔽的feature列表(带价格)
+ */
+export function getWhitelistFeatures() {
+ return useApiFetch("/agent/whitelist/features").get().json();
+}
+
+/**
+ * 创建白名单订单
+ * @param {object} params - 创建参数
+ * @param {string} params.id_card - 身份证号
+ * @param {string[]} params.feature_ids - 要屏蔽的feature ID列表
+ * @param {string} params.order_id - 关联的查询订单ID(可选)
+ */
+export function createWhitelistOrder(params) {
+ return useApiFetch("/agent/whitelist/order/create").post(params).json();
+}
+
+/**
+ * 查询白名单列表
+ * @param {object} params - 查询参数
+ * @param {number} params.page - 页码
+ * @param {number} params.page_size - 每页数量
+ * @param {string} params.id_card - 身份证号(可选,用于筛选)
+ */
+export function getWhitelistList(params) {
+ const queryString = buildQueryString(params || {});
+ return useApiFetch(`/agent/whitelist/list${queryString}`).get().json();
+}
+
+/**
+ * 检查模块是否已下架
+ * @param {object} params - 查询参数
+ * @param {string} params.id_card - 身份证号
+ * @param {string} params.feature_api_id - Feature的API标识
+ */
+export function checkFeatureWhitelistStatus(params) {
+ const queryString = buildQueryString(params || {});
+ return useApiFetch(`/agent/whitelist/check${queryString}`).get().json();
+}
+
+/**
+ * 下架单个模块(创建订单并支付/或免费下架)
+ * @param {object} params - 下架参数
+ * @param {string} params.id_card - 身份证号
+ * @param {string} params.feature_api_id - Feature的API标识
+ * @param {string} params.order_id - 关联的查询订单ID(可选)
+ * @param {string} params.query_id - 查询记录ID(用于后端删除报告数据)
+ */
+export function offlineFeature(params) {
+ return useApiFetch("/agent/whitelist/offline").post(params).json();
+}
+
+/**
+ * 检查订单是否属于当前代理推广
+ * @param {object} params - 查询参数
+ * @param {string} params.order_id - 订单ID
+ */
+export function checkOrderAgent(params) {
+ const queryString = buildQueryString(params || {});
+ return useApiFetch(`/agent/order/agent${queryString}`).get().json();
+}
diff --git a/src/components/BaseReport.vue b/src/components/BaseReport.vue
index c95416a..2d5efa1 100644
--- a/src/components/BaseReport.vue
+++ b/src/components/BaseReport.vue
@@ -3,12 +3,18 @@ import ShareReportButton from "./ShareReportButton.vue";
import TitleBanner from "./TitleBanner.vue";
import VerificationCard from "./VerificationCard.vue";
import StyledTabs from "./StyledTabs.vue";
+import WhitelistModuleDialog from "./WhitelistModuleDialog.vue";
+import Payment from "./Payment.vue";
import { splitDWBG8B4DForTabs } from '@/ui/CDWBG8B4D/utils/simpleSplitter.js';
import { splitDWBG6A2CForTabs } from '@/ui/DWBG6A2C/utils/simpleSplitter.js';
import { splitJRZQ7F1AForTabs } from '@/ui/JRZQ7F1A/utils/simpleSplitter.js';
import { splitCJRZQ5E9FForTabs } from '@/ui/CJRZQ5E9F/utils/simpleSplitter.js';
import { splitCQYGL3F8EForTabs } from '@/ui/CQYGL3F8E/utils/simpleSplitter.js';
import { useAppStore } from "@/stores/appStore";
+import { useAgentStore } from "@/stores/agentStore";
+import { storeToRefs } from 'pinia';
+import { showFailToast } from 'vant';
+import { checkFeatureWhitelistStatus, offlineFeature, checkOrderAgent } from '@/api/agent';
// 动态导入产品背景图片的函数
const loadProductBackground = async (productType) => {
@@ -51,6 +57,11 @@ const props = defineProps({
type: String,
default: "",
},
+ queryId: {
+ type: String,
+ required: false,
+ default: "",
+ },
feature: {
type: String,
required: true,
@@ -96,8 +107,26 @@ const {
isEmpty,
isDone,
isExample,
+ orderId,
+ orderNo,
+ queryId,
} = toRefs(props);
+// 代理信息
+const agentStore = useAgentStore()
+const { isDiamond } = storeToRefs(agentStore)
+
+// 屏蔽模块弹窗(已废弃,保留用于兼容)
+const showWhitelistDialog = ref(false)
+
+// 订单是否属于当前代理推广
+const isAgentOrder = ref(false)
+
+// 获取身份证号(从 reportParams 中,用于展示与接口)
+const idCard = computed(() => {
+ return reportParams.value?.id_card || ''
+})
+
const active = ref(null);
const backgroundContainerRef = ref(null); // 背景容器的引用
@@ -108,15 +137,272 @@ const imageAspectRatio = ref(0); // 缓存图片宽高比
const MAX_BACKGROUND_HEIGHT = 211; // 最大背景高度,防止图片过高变形
const trapezoidBgImage = ref(''); // 牌匾背景图片
+// 模块下架状态映射:主模块ID -> { isOfflined, whitelistPrice, isSubmitting }
+const featureOfflineStatus = ref(new Map())
+
+// 提取主模块ID(去掉下划线后的部分)
+const getMainApiId = (apiId) => {
+ if (!apiId) return ''
+ const index = apiId.indexOf('_')
+ return index > 0 ? apiId.substring(0, index) : apiId
+}
+
+// 检查模块下架状态
+const checkFeatureStatus = async (featureApiId, forceRefresh = false) => {
+ if (!idCard.value || !featureApiId) return
+
+ const mainApiId = getMainApiId(featureApiId)
+ if (!mainApiId) return
+
+ if (!forceRefresh && featureOfflineStatus.value.has(mainApiId)) return
+
+ try {
+ const { data, error } = await checkFeatureWhitelistStatus({
+ id_card: idCard.value,
+ feature_api_id: mainApiId,
+ query_id: queryId.value || '',
+ })
+ if (data.value && !error.value && data.value.code === 200) {
+ const isWhitelisted = data.value.data.is_whitelisted || false
+ const dataDeleted = data.value.data.data_deleted !== undefined ? data.value.data.data_deleted : true
+ const status = {
+ isOfflined: isWhitelisted && dataDeleted,
+ whitelistPrice: data.value.data.whitelist_price || 0,
+ isSubmitting: false,
+ }
+ featureOfflineStatus.value.set(mainApiId, status)
+ }
+ } catch (err) {
+ console.error('检查模块状态失败:', err)
+ }
+}
+
+// 批量检查所有模块的下架状态
+const checkAllFeaturesStatus = async () => {
+ if (!idCard.value || !isAgentOrder.value || isExample.value) return
+
+ const featureApiIds = processedReportData.value.map(item => item.data.apiID)
+ const mainApiIds = [...new Set(featureApiIds.map(id => getMainApiId(id)))]
+
+ for (const mainApiId of mainApiIds) {
+ if (mainApiId) {
+ await checkFeatureStatus(mainApiId)
+ }
+ }
+}
+
+// 获取模块下架状态
+const getFeatureStatus = (featureApiId) => {
+ const mainApiId = getMainApiId(featureApiId)
+ const status = featureOfflineStatus.value.get(mainApiId) || {
+ isOfflined: false,
+ whitelistPrice: 0,
+ isSubmitting: false,
+ }
+ return status
+}
+
+// 当前正在下架的模块信息(用于支付确认弹窗)
+const currentOfflineFeature = ref(null)
+const showOfflineConfirmDialog = ref(false)
+
+// 处理下架按钮点击
+const handleOfflineClick = async (featureApiId, featureName) => {
+ const mainApiId = getMainApiId(featureApiId)
+ const status = getFeatureStatus(mainApiId)
+
+ // 如果已下架,不允许再次点击
+ if (status.isOfflined) {
+ showFailToast('该模块已下架')
+ return
+ }
+
+ // 如果 whitelistPrice = 0,直接下架(免费),不需要支付确认
+ if (status.whitelistPrice <= 0) {
+ await confirmOfflineDirectly(mainApiId, featureName)
+ return
+ }
+
+ // 如果 whitelistPrice > 0,需要支付确认
+ currentOfflineFeature.value = {
+ featureApiId: mainApiId,
+ featureName,
+ whitelistPrice: status.whitelistPrice,
+ }
+ showOfflineConfirmDialog.value = true
+}
+
+// 白名单下架支付弹窗相关状态
+const showWhitelistPayment = ref(false)
+const whitelistPaymentData = ref({ product_name: '', sell_price: 0 })
+const whitelistPaymentId = ref('')
+const whitelistPaymentType = ref('whitelist')
+
+// 获取当前报告页面的 URL(用于支付成功后返回)
+const getCurrentReportUrl = () => {
+ if (orderNo.value) {
+ return `/report?orderNo=${orderNo.value}`
+ } else if (orderId.value) {
+ return `/report?orderId=${orderId.value}`
+ }
+ return ''
+}
+
+// 直接下架(免费)
+const confirmOfflineDirectly = async (mainApiId, featureName) => {
+ if (!idCard.value || !mainApiId) return
+
+ const status = getFeatureStatus(mainApiId)
+ status.isSubmitting = true
+ featureOfflineStatus.value.set(mainApiId, { ...status })
+
+ try {
+ if (!queryId.value) {
+ showFailToast('缺少查询记录ID,无法下架')
+ const currentStatus = getFeatureStatus(mainApiId)
+ currentStatus.isSubmitting = false
+ featureOfflineStatus.value.set(mainApiId, { ...currentStatus })
+ return
+ }
+
+ const { data, error } = await offlineFeature({
+ query_id: queryId.value,
+ feature_api_id: mainApiId,
+ })
+
+ if (!data.value || error.value || data.value.code !== 200) {
+ showFailToast(data.value?.msg || '下架失败')
+ const currentStatus = getFeatureStatus(mainApiId)
+ currentStatus.isSubmitting = false
+ featureOfflineStatus.value.set(mainApiId, { ...currentStatus })
+ return
+ }
+
+ const resp = data.value.data || {}
+ if (resp.need_pay) {
+ const currentStatus = getFeatureStatus(mainApiId)
+ currentStatus.isSubmitting = false
+ currentStatus.whitelistPrice = resp.amount || 0
+ featureOfflineStatus.value.set(mainApiId, { ...currentStatus })
+
+ whitelistPaymentData.value = {
+ product_name: `${featureName || '模块'} 下架`,
+ sell_price: resp.amount || 0,
+ }
+ whitelistPaymentId.value = `${idCard.value}|${mainApiId}`
+ whitelistPaymentType.value = 'whitelist'
+ showWhitelistPayment.value = true
+ return
+ }
+
+ showFailToast('下架成功')
+
+ const updatedStatus = getFeatureStatus(mainApiId)
+ updatedStatus.isSubmitting = false
+ updatedStatus.isOfflined = true
+ featureOfflineStatus.value.set(mainApiId, { ...updatedStatus })
+
+ if (queryId.value || orderId.value) {
+ window.location.reload()
+ }
+ } catch (err) {
+ console.error('下架模块失败:', err)
+ showFailToast('下架模块失败')
+ const currentStatus = getFeatureStatus(mainApiId)
+ currentStatus.isSubmitting = false
+ featureOfflineStatus.value.set(mainApiId, { ...currentStatus })
+ }
+}
+
+// 确认下架(付费场景)
+const confirmOffline = async () => {
+ if (!currentOfflineFeature.value) return
+
+ const { featureApiId } = currentOfflineFeature.value
+ const mainApiId = featureApiId
+
+ if (!queryId.value) {
+ showFailToast('缺少查询记录ID,无法下架')
+ return
+ }
+
+ const status = getFeatureStatus(mainApiId)
+ status.isSubmitting = true
+ featureOfflineStatus.value.set(mainApiId, { ...status })
+
+ try {
+ const { data, error } = await offlineFeature({
+ query_id: queryId.value,
+ feature_api_id: mainApiId,
+ })
+
+ if (!data.value || error.value || data.value.code !== 200) {
+ showFailToast(data.value?.msg || '下架失败')
+ const currentStatus = getFeatureStatus(mainApiId)
+ currentStatus.isSubmitting = false
+ featureOfflineStatus.value.set(mainApiId, { ...currentStatus })
+ return
+ }
+
+ const resp = data.value.data || {}
+ if (resp.need_pay) {
+ showOfflineConfirmDialog.value = false
+
+ whitelistPaymentData.value = {
+ product_name: `${currentOfflineFeature.value?.featureName || '模块'} 下架`,
+ sell_price: resp.amount || 0,
+ }
+ whitelistPaymentId.value = `${idCard.value}|${mainApiId}`
+ whitelistPaymentType.value = 'whitelist'
+ showWhitelistPayment.value = true
+
+ const currentStatus = getFeatureStatus(mainApiId)
+ currentStatus.isSubmitting = false
+ featureOfflineStatus.value.set(mainApiId, { ...currentStatus })
+ return
+ }
+
+ showFailToast('下架成功')
+ showOfflineConfirmDialog.value = false
+ currentOfflineFeature.value = null
+
+ const updatedStatus = getFeatureStatus(mainApiId)
+ updatedStatus.isSubmitting = false
+ featureOfflineStatus.value.set(mainApiId, { ...updatedStatus })
+
+ if (queryId.value || orderId.value) {
+ window.location.reload()
+ }
+ } catch (err) {
+ console.error('下架模块失败:', err)
+ showFailToast('下架模块失败')
+ const currentStatus = getFeatureStatus(mainApiId)
+ currentStatus.isSubmitting = false
+ featureOfflineStatus.value.set(mainApiId, { ...currentStatus })
+ }
+}
+
+// 打开屏蔽模块弹窗(已废弃,保留用于兼容)
+const openWhitelistDialog = () => {
+ if (!idCard.value) {
+ console.error('无法获取身份证号')
+ return
+ }
+ showWhitelistDialog.value = true
+}
+
+// 屏蔽成功后的回调(已废弃,保留用于兼容)
+const onWhitelistSuccess = () => {
+ console.log('模块已屏蔽')
+}
+
// 计算背景高度
const calculateBackgroundHeight = () => {
if (imageAspectRatio.value > 0) {
- // 获取容器的实际宽度,而不是整个窗口宽度
const containerWidth = backgroundContainerRef.value
? backgroundContainerRef.value.offsetWidth
: window.innerWidth;
const calculatedHeight = containerWidth * imageAspectRatio.value;
- // 限制最大高度,防止图片过高
backgroundHeight.value = Math.min(calculatedHeight, MAX_BACKGROUND_HEIGHT);
}
};
@@ -126,13 +412,10 @@ const loadBackgroundImage = async () => {
const background = await loadProductBackground(feature.value);
productBackground.value = background || '';
- // 加载图片后计算高度
if (background) {
const img = new Image();
img.onload = () => {
- // 缓存图片宽高比
imageAspectRatio.value = img.height / img.width;
- // 图片加载完成后,等待下一帧再计算高度,确保容器已渲染
nextTick(() => {
calculateBackgroundHeight();
});
@@ -149,8 +432,23 @@ onMounted(async () => {
await loadBackgroundImage();
await loadTrapezoidBackground();
- // 监听窗口大小变化,重新计算高度
window.addEventListener('resize', handleResize);
+
+ // 检查订单是否属于当前代理推广
+ if (!isExample.value && orderId.value) {
+ try {
+ const { data, error } = await checkOrderAgent({ order_id: orderId.value })
+ if (data.value && !error.value && data.value.code === 200) {
+ isAgentOrder.value = data.value.data.is_agent_order
+ }
+ } catch (err) {
+ console.error('检查订单代理状态失败:', err)
+ }
+ }
+
+ if (isAgentOrder.value && idCard.value && !isExample.value) {
+ checkAllFeaturesStatus()
+ }
});
// 处理窗口大小变化(带防抖)
@@ -160,7 +458,7 @@ const handleResize = () => {
}
resizeTimer = setTimeout(() => {
calculateBackgroundHeight();
- }, 100); // 100ms 防抖延迟
+ }, 100);
};
// 组件卸载时移除监听器
@@ -171,23 +469,13 @@ onUnmounted(() => {
window.removeEventListener('resize', handleResize);
});
-// 处理数据拆分(支持DWBG8B4D、DWBG6A2C、CJRZQ5E9F和CQYGL3F8E)
+// 处理数据拆分
const processedReportData = computed(() => {
let data = reportData.value;
- // 拆分DWBG8B4D数据
data = splitDWBG8B4DForTabs(data);
-
- // 拆分DWBG6A2C数据
data = splitDWBG6A2CForTabs(data);
-
- // 拆分JRZQ7F1A数据
data = splitJRZQ7F1AForTabs(data);
- // // 拆分CJRZQ5E9F数据
- // data = splitCJRZQ5E9FForTabs(data);
-
- // 拆分CQYGL3F8E数据
data = splitCQYGL3F8EForTabs(data);
- // 过滤掉在featureMap中没有对应的项
return data.filter(item => featureMap[item.data.apiID]);
});
@@ -202,7 +490,7 @@ const backgroundContainerStyle = computed(() => {
};
}
return {
- height: '180px', // 默认高度
+ height: '180px',
};
});
@@ -211,10 +499,10 @@ const backgroundImageStyle = computed(() => {
if (getProductBackground.value) {
return {
backgroundImage: `url(${getProductBackground.value})`,
- backgroundSize: '100% auto', // 宽度100%,高度自动保持比例
- backgroundPosition: 'center', // 向上偏移20px
+ backgroundSize: '100% auto',
+ backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
- overflow: 'hidden', // 超出部分裁剪
+ overflow: 'hidden',
};
}
return {};
@@ -752,10 +1040,8 @@ const calculateScore = () => {
const apiID = item.data?.apiID;
if (!apiID) return;
- // 获取风险权重(如果不在配置中,默认为 3)
const weight = featureRiskLevels[apiID] ?? 3;
- // 跳过权重为 0 的复合报告主模块(它们由子模块计算)
if (weight === 0) return;
presentFeatures.push({
@@ -765,49 +1051,21 @@ const calculateScore = () => {
});
});
- if (presentFeatures.length === 0) return 100; // 无有效特征时返回满分(最安全)
+ if (presentFeatures.length === 0) return 100;
- // 累计总风险分数
let totalRiskScore = 0;
- const riskDetails = []; // 用于调试
presentFeatures.forEach(({ apiID, index, weight }) => {
- // 从组件风险评分中获取评分(0-100分,分数越高越安全)
const key = `${apiID}_${index}`;
- const componentScore = componentRiskScores.value[key] ?? 100; // 默认100分(最安全)
-
- // 将组件评分转换为风险分数(0-100 -> 100-0)
+ const componentScore = componentRiskScores.value[key] ?? 100;
const componentRisk = 100 - componentScore;
-
- // 计算该模块的风险贡献(固定分值,不按占比)
- // 使用权重系数放大高风险模块的影响
- // 高风险模块(权重10)如果风险分数是0,扣20分(权重10 × 系数2)
- // 中风险模块(权重7)如果风险分数是0,扣14分(权重7 × 系数2)
- // 低风险模块(权重3)如果风险分数是0,扣6分(权重3 × 系数2)
- const weightMultiplier = 1.5; // 权重系数,可以调整这个值来控制影响程度
+ const weightMultiplier = 1.5;
const riskContribution = (componentRisk / 100) * weight * weightMultiplier;
-
- riskDetails.push({
- apiID,
- index,
- weight,
- componentScore,
- componentRisk,
- riskContribution,
- hasStatus: key in componentRiskScores.value
- });
-
- // 累加风险分数
totalRiskScore += riskContribution;
});
- // 将总风险分数限制在 0-90 范围内(确保最低分为10分)
const finalRiskScore = Math.max(0, Math.min(90, Math.round(totalRiskScore)));
-
- // 转换为安全分数:分数越高越安全(100 - 风险分数)
- // 最终分数范围:10-100分
const safetyScore = 100 - finalRiskScore;
-
return safetyScore;
};
@@ -815,21 +1073,6 @@ const calculateScore = () => {
watch([reportData, componentRiskScores], () => {
reportScore.value = calculateScore();
- // 将评分系统数据整理到一个对象中
- const scoreData = {
- timestamp: new Date().toISOString(),
- finalScore: reportScore.value,
- reportModules: processedReportData.value.map((item, index) => ({
- apiID: item.data.apiID,
- name: featureMap[item.data.apiID]?.name || '未知',
- index: index,
- riskScore: componentRiskScores.value[`${item.data.apiID}_${index}`] ?? '未上报',
- weight: featureRiskLevels[item.data.apiID] ?? 0
- })),
- componentScores: componentRiskScores.value,
- riskLevels: featureRiskLevels
- };
-
}, { immediate: true, deep: true });
// 从环境变量获取配置
diff --git a/src/components/WhitelistModuleDialog.vue b/src/components/WhitelistModuleDialog.vue
new file mode 100644
index 0000000..d398644
--- /dev/null
+++ b/src/components/WhitelistModuleDialog.vue
@@ -0,0 +1,227 @@
+
+ 屏蔽模块
+