f
This commit is contained in:
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
|
||||
// 从环境变量获取配置
|
||||
|
||||
227
src/components/WhitelistModuleDialog.vue
Normal file
227
src/components/WhitelistModuleDialog.vue
Normal file
@@ -0,0 +1,227 @@
|
||||
<template>
|
||||
<van-popup v-model:show="show" position="bottom" round :style="{ height: '70%' }" class="whitelist-module-dialog">
|
||||
<div class="flex flex-col h-full">
|
||||
<!-- 标题栏 -->
|
||||
<div class="flex items-center justify-between p-4 border-b">
|
||||
<h3 class="text-lg font-bold">屏蔽模块</h3>
|
||||
<van-icon name="cross" size="20" @click="close" />
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="flex-1 overflow-y-auto p-4">
|
||||
<div v-if="loading" class="flex items-center justify-center h-40">
|
||||
<van-loading type="spinner" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="featureList.length === 0" class="flex items-center justify-center h-40 text-gray-500">
|
||||
暂无可屏蔽的模块
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<div class="mb-4 text-sm text-gray-600">
|
||||
选择要屏蔽的模块,屏蔽后该身份证号查询时将不显示这些模块的数据
|
||||
</div>
|
||||
|
||||
<!-- 模块列表 -->
|
||||
<van-checkbox-group v-model="selectedFeatureIds">
|
||||
<div v-for="feature in featureList" :key="feature.feature_id" class="mb-3">
|
||||
<van-cell :title="feature.feature_name" :label="`价格:¥${feature.whitelist_price.toFixed(2)}`"
|
||||
clickable @click="toggleFeature(feature.feature_id)">
|
||||
<template #right-icon>
|
||||
<van-checkbox :name="feature.feature_id" />
|
||||
</template>
|
||||
</van-cell>
|
||||
</div>
|
||||
</van-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<div class="p-4 border-t bg-gray-50">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<span class="text-sm text-gray-600">已选择:{{ selectedFeatureIds.length }} 个模块</span>
|
||||
<span class="text-lg font-bold text-red-500">
|
||||
总计:¥{{ totalAmount.toFixed(2) }}
|
||||
</span>
|
||||
</div>
|
||||
<van-button type="primary" block round :loading="isSubmitting"
|
||||
:disabled="selectedFeatureIds.length === 0" @click="handleConfirm">
|
||||
确认屏蔽
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 支付弹窗 -->
|
||||
<Payment v-model="showPayment" :data="paymentData"
|
||||
:id="paymentId" :type="paymentType" />
|
||||
</van-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { getWhitelistFeatures, createWhitelistOrder } from '@/api/agent'
|
||||
import { showSuccessToast, showFailToast, showConfirmDialog } from 'vant'
|
||||
import { useAgentStore } from '@/stores/agentStore'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import Payment from './Payment.vue'
|
||||
|
||||
const props = defineProps({
|
||||
idCard: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
orderId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
const show = defineModel('show', { type: Boolean, default: false })
|
||||
|
||||
const agentStore = useAgentStore()
|
||||
const { isDiamond } = storeToRefs(agentStore)
|
||||
|
||||
const loading = ref(false)
|
||||
const featureList = ref([])
|
||||
const selectedFeatureIds = ref([])
|
||||
const isSubmitting = ref(false)
|
||||
|
||||
// 支付相关状态
|
||||
const showPayment = ref(false)
|
||||
const paymentData = ref({
|
||||
product_name: '',
|
||||
sell_price: 0,
|
||||
})
|
||||
const paymentId = ref('')
|
||||
const paymentType = ref('whitelist')
|
||||
const currentOrderId = ref('')
|
||||
|
||||
// 计算总金额
|
||||
const totalAmount = computed(() => {
|
||||
return featureList.value
|
||||
.filter(f => selectedFeatureIds.value.includes(f.feature_id))
|
||||
.reduce((sum, f) => sum + f.whitelist_price, 0)
|
||||
})
|
||||
|
||||
// 切换选择
|
||||
const toggleFeature = (featureId) => {
|
||||
const index = selectedFeatureIds.value.indexOf(featureId)
|
||||
if (index > -1) {
|
||||
selectedFeatureIds.value.splice(index, 1)
|
||||
} else {
|
||||
selectedFeatureIds.value.push(featureId)
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const close = () => {
|
||||
show.value = false
|
||||
selectedFeatureIds.value = []
|
||||
}
|
||||
|
||||
// 加载可屏蔽的模块列表
|
||||
const loadFeatures = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const { data, error } = await getWhitelistFeatures()
|
||||
if (data.value && !error.value && data.value.code === 200) {
|
||||
featureList.value = data.value.data.list || []
|
||||
} else {
|
||||
showFailToast(data.value?.msg || '获取模块列表失败')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取模块列表失败:', err)
|
||||
showFailToast('获取模块列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 确认屏蔽
|
||||
const handleConfirm = async () => {
|
||||
if (selectedFeatureIds.value.length === 0) {
|
||||
showFailToast('请至少选择一个模块')
|
||||
return
|
||||
}
|
||||
|
||||
// 确认对话框
|
||||
try {
|
||||
await showConfirmDialog({
|
||||
title: '确认屏蔽',
|
||||
message: `确定要屏蔽 ${selectedFeatureIds.value.length} 个模块吗?总费用:¥${totalAmount.value.toFixed(2)}`,
|
||||
})
|
||||
} catch {
|
||||
return // 用户取消
|
||||
}
|
||||
|
||||
isSubmitting.value = true
|
||||
try {
|
||||
// 1. 创建订单
|
||||
const { data: orderData, error: orderError } = await createWhitelistOrder({
|
||||
id_card: props.idCard,
|
||||
feature_ids: selectedFeatureIds.value,
|
||||
order_id: props.orderId || undefined,
|
||||
})
|
||||
|
||||
if (!orderData.value || orderError.value || orderData.value.code !== 200) {
|
||||
showFailToast(orderData.value?.msg || '创建订单失败')
|
||||
return
|
||||
}
|
||||
|
||||
const orderId = orderData.value.data.order_id
|
||||
const orderNo = orderData.value.data.order_no
|
||||
const totalAmount = orderData.value.data.total_amount
|
||||
|
||||
// 2. 使用统一支付组件进行支付
|
||||
// PaymentReq.Id 约定格式:白名单订单使用 "{idCard}|{featureApiId}" 格式
|
||||
// 但批量订单使用订单号,需要根据后端接口调整
|
||||
// 这里先使用订单号,如果后端需要特定格式,需要调整
|
||||
paymentData.value = {
|
||||
product_name: `模块屏蔽(${selectedFeatureIds.value.length}个模块)`,
|
||||
sell_price: totalAmount,
|
||||
}
|
||||
paymentId.value = orderNo // 使用订单号作为支付ID
|
||||
paymentType.value = 'whitelist'
|
||||
currentOrderId.value = orderId
|
||||
showPayment.value = true
|
||||
} catch (err) {
|
||||
console.error('屏蔽模块失败:', err)
|
||||
showFailToast('屏蔽模块失败')
|
||||
} finally {
|
||||
isSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 监听支付弹窗关闭,如果支付成功会跳转到支付结果页面
|
||||
watch(showPayment, (newVal) => {
|
||||
if (!newVal && currentOrderId.value) {
|
||||
// 支付弹窗关闭,可能是支付完成(会跳转到结果页面)或用户取消
|
||||
// 这里不做特殊处理,因为支付成功会跳转到结果页面
|
||||
currentOrderId.value = ''
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
// 监听弹窗显示,加载数据
|
||||
watch(show, (newVal) => {
|
||||
if (newVal) {
|
||||
// 验证是否为钻石代理
|
||||
if (!isDiamond.value) {
|
||||
showFailToast('只有钻石代理可以操作白名单')
|
||||
close()
|
||||
return
|
||||
}
|
||||
loadFeatures()
|
||||
selectedFeatureIds.value = []
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.whitelist-module-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -185,43 +185,38 @@ export function useWeixinShare() {
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据当前页面动态设置分享内容
|
||||
* 根据当前路由动态设置分享内容
|
||||
* @param {object} routeInfo - vue-router 的 route 对象,可选
|
||||
*/
|
||||
const setDynamicShare = async () => {
|
||||
const route = window.location.pathname;
|
||||
let shareConfig = {};
|
||||
|
||||
// 根据不同的路由设置不同的分享内容
|
||||
if (route.includes("/example")) {
|
||||
shareConfig = {
|
||||
title: import.meta.env.VITE_SHARE_TITLE,
|
||||
desc: import.meta.env.VITE_SHARE_DESC,
|
||||
link: window.location.href.split("#")[0],
|
||||
imgUrl: import.meta.env.VITE_SHARE_IMG,
|
||||
};
|
||||
} else if (route.includes("/agent")) {
|
||||
shareConfig = {
|
||||
title: import.meta.env.VITE_SHARE_TITLE,
|
||||
desc: import.meta.env.VITE_SHARE_DESC,
|
||||
link: window.location.href.split("#")[0],
|
||||
imgUrl: import.meta.env.VITE_SHARE_IMG,
|
||||
};
|
||||
} else if (route.includes("/help")) {
|
||||
shareConfig = {
|
||||
title: import.meta.env.VITE_SHARE_TITLE,
|
||||
desc: import.meta.env.VITE_SHARE_DESC,
|
||||
link: window.location.href.split("#")[0],
|
||||
imgUrl: import.meta.env.VITE_SHARE_IMG,
|
||||
};
|
||||
} else {
|
||||
shareConfig = {
|
||||
title: import.meta.env.VITE_SHARE_TITLE,
|
||||
desc: import.meta.env.VITE_SHARE_DESC,
|
||||
link: window.location.href.split("#")[0],
|
||||
imgUrl: import.meta.env.VITE_SHARE_IMG,
|
||||
};
|
||||
const setDynamicShare = async (routeInfo) => {
|
||||
// 某些页面(如 inquire / promotionInquire / example)有自定义分享逻辑
|
||||
if (routeInfo && routeInfo.meta && routeInfo.meta.shareCustom) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fullPath = window.location.href.split("#")[0];
|
||||
const baseShareTitle = import.meta.env.VITE_SHARE_TITLE || "";
|
||||
const appName = import.meta.env.VITE_APP_NAME || "";
|
||||
|
||||
const routeMetaTitle = routeInfo && routeInfo.meta && routeInfo.meta.title;
|
||||
const routeName = routeInfo && routeInfo.name;
|
||||
|
||||
let title = baseShareTitle;
|
||||
|
||||
// 除首页之外,如果有路由标题,则加到分享标题中
|
||||
if (routeMetaTitle && routeName !== "index") {
|
||||
title = appName
|
||||
? `${routeMetaTitle}|${appName}`
|
||||
: `${routeMetaTitle}|${baseShareTitle}`;
|
||||
}
|
||||
|
||||
const shareConfig = {
|
||||
title,
|
||||
desc: import.meta.env.VITE_SHARE_DESC,
|
||||
link: fullPath,
|
||||
imgUrl: import.meta.env.VITE_SHARE_IMG,
|
||||
};
|
||||
|
||||
await configWeixinShare(shareConfig);
|
||||
};
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ const router = createRouter({
|
||||
path: "/example",
|
||||
name: "example",
|
||||
component: () => import("@/views/Example.vue"),
|
||||
meta: { title: "示例报告", notNeedBindPhone: true },
|
||||
meta: { title: "示例报告", notNeedBindPhone: true, shareCustom: true },
|
||||
},
|
||||
{
|
||||
path: "/vant-theme-test",
|
||||
@@ -173,7 +173,7 @@ const router = createRouter({
|
||||
path: "/inquire/:feature",
|
||||
name: "inquire",
|
||||
component: () => import("@/views/Inquire.vue"),
|
||||
meta: { title: "查询报告" },
|
||||
meta: { title: "查询报告", shareCustom: true },
|
||||
},
|
||||
{
|
||||
path: "/authorization",
|
||||
@@ -398,7 +398,7 @@ const router = createRouter({
|
||||
path: "/agent/promotionInquire/:linkIdentifier",
|
||||
name: "promotionInquire",
|
||||
component: () => import("@/views/PromotionInquire.vue"),
|
||||
meta: { notNeedBindPhone: true },
|
||||
meta: { notNeedBindPhone: true, shareCustom: true },
|
||||
},
|
||||
{
|
||||
path: "/agent/invitationAgentApply/:linkIdentifier",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
<script setup>
|
||||
import { aesDecrypt } from "@/utils/crypto";
|
||||
import { useWeixinShare } from "@/composables/useWeixinShare";
|
||||
|
||||
const AES_KEY = import.meta.env.VITE_INQUIRE_AES_KEY;
|
||||
|
||||
@@ -20,6 +21,18 @@ const isEmpty = ref(false);
|
||||
const isDone = ref(false);
|
||||
const active = ref(0);
|
||||
|
||||
const { configWeixinShare } = useWeixinShare();
|
||||
|
||||
const updateExampleWeixinShare = async () => {
|
||||
if (!reportName.value) return;
|
||||
const baseTitle = reportName.value;
|
||||
const title = `${baseTitle}(示例报告)`;
|
||||
|
||||
await configWeixinShare({
|
||||
title,
|
||||
desc: import.meta.env.VITE_SHARE_DESC,
|
||||
});
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
@@ -72,6 +85,9 @@ const getReport = async () => {
|
||||
reportParams.value = decryptedData.query_params || {};
|
||||
reportName.value = decryptedData.product_name || "";
|
||||
reportDateTime.value = decryptedData.create_time || null;
|
||||
|
||||
// 更新示例报告的微信分享文案
|
||||
await updateExampleWeixinShare();
|
||||
} else if (data.value.code === 200003) {
|
||||
isEmpty.value = true;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from "vue";
|
||||
import { ref, onMounted } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useAgentStore } from "@/stores/agentStore";
|
||||
import { useUserStore } from "@/stores/userStore";
|
||||
import { useWeixinShare } from "@/composables/useWeixinShare";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { showToast } from "vant";
|
||||
|
||||
@@ -18,6 +19,31 @@ const feature = ref(route.params.feature);
|
||||
// 获取产品信息
|
||||
const featureData = ref({});
|
||||
|
||||
// 微信分享
|
||||
const { configWeixinShare } = useWeixinShare();
|
||||
|
||||
// 去掉富文本 HTML 标签,保留纯文本
|
||||
const stripHtml = (html) => {
|
||||
if (!html) return "";
|
||||
return html.replace(/<[^>]*>/g, "").replace(/\s+/g, " ").trim();
|
||||
};
|
||||
|
||||
const updateWeixinShareForProduct = async () => {
|
||||
if (!featureData.value || !featureData.value.product_name) return;
|
||||
|
||||
const appName = import.meta.env.VITE_APP_NAME || "";
|
||||
const baseTitle = featureData.value.product_name;
|
||||
const title = appName ? `${baseTitle}|${appName}风险报告` : baseTitle;
|
||||
const desc =
|
||||
stripHtml(featureData.value.description) ||
|
||||
import.meta.env.VITE_SHARE_DESC;
|
||||
|
||||
await configWeixinShare({
|
||||
title,
|
||||
desc,
|
||||
});
|
||||
};
|
||||
|
||||
// 检查登录状态
|
||||
onMounted(async () => {
|
||||
// 检查支付回调
|
||||
@@ -64,6 +90,9 @@ async function getProduct() {
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
console.log('featureData', featureData.value);
|
||||
// 更新微信分享文案
|
||||
await updateWeixinShareForProduct();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -31,19 +31,26 @@
|
||||
<span class="text-gray-800">{{
|
||||
paymentType === "agent_upgrade"
|
||||
? "代理升级"
|
||||
: "查询服务"
|
||||
: paymentType === "whitelist"
|
||||
? "模块下架"
|
||||
: "查询服务"
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="paymentType === 'agent_upgrade'" class="text-center text-gray-600 mb-4">恭喜你升级代理等级成功,享受更多权益
|
||||
</div>
|
||||
<div v-else-if="paymentType === 'whitelist'" class="text-center text-gray-600 mb-4">
|
||||
模块下架成功,该身份证号查询时将不显示此模块的数据
|
||||
</div>
|
||||
|
||||
<div class="action-buttons grid grid-cols-1 gap-4">
|
||||
<van-button block type="primary" class="rounded-lg" @click="handleNavigation">
|
||||
{{
|
||||
paymentType === "agent_upgrade"
|
||||
? "查看代理信息"
|
||||
: "查看查询结果"
|
||||
: paymentType === "whitelist"
|
||||
? "返回报告"
|
||||
: "查看查询结果"
|
||||
}}
|
||||
</van-button>
|
||||
</div>
|
||||
@@ -74,7 +81,9 @@
|
||||
<span class="text-gray-800">{{
|
||||
paymentType === "agent_upgrade"
|
||||
? "代理升级"
|
||||
: "查询服务"
|
||||
: paymentType === "whitelist"
|
||||
? "模块下架"
|
||||
: "查询服务"
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
@@ -137,7 +146,9 @@
|
||||
<span class="text-gray-800">{{
|
||||
paymentType === "agent_upgrade"
|
||||
? "代理升级"
|
||||
: "查询服务"
|
||||
: paymentType === "whitelist"
|
||||
? "模块下架"
|
||||
: "查询服务"
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
@@ -329,6 +340,25 @@ const checkPaymentStatus = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 对于白名单类型,如果状态是已支付,跳转回报告页面
|
||||
if (
|
||||
paymentType.value === "whitelist" &&
|
||||
newStatus === "paid"
|
||||
) {
|
||||
stopPolling();
|
||||
// 优先使用 returnUrl,否则返回上一页
|
||||
const returnUrl = route.query.returnUrl;
|
||||
if (returnUrl) {
|
||||
router.replace(returnUrl);
|
||||
} else if (window.history.length > 1) {
|
||||
router.go(-1);
|
||||
} else {
|
||||
// 如果没有历史记录,跳转到首页
|
||||
router.replace("/");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果状态不是 pending,停止轮询
|
||||
if (newStatus !== "pending") {
|
||||
stopPolling();
|
||||
@@ -414,6 +444,17 @@ function handleNavigation() {
|
||||
router.replace("/agent");
|
||||
agentStore.fetchAgentStatus();
|
||||
userStore.fetchUserInfo();
|
||||
} else if (paymentType.value === "whitelist") {
|
||||
// 白名单支付:优先使用 returnUrl,否则返回上一页
|
||||
const returnUrl = route.query.returnUrl;
|
||||
if (returnUrl) {
|
||||
router.replace(returnUrl);
|
||||
} else if (window.history.length > 1) {
|
||||
router.go(-1);
|
||||
} else {
|
||||
// 如果没有历史记录,跳转到首页
|
||||
router.replace("/");
|
||||
}
|
||||
} else {
|
||||
// 跳转到查询结果页面
|
||||
router.replace({
|
||||
|
||||
@@ -15,8 +15,8 @@ export default defineConfig({
|
||||
strictPort: true, // 如果端口被占用则抛出错误而不是使用下一个可用端口
|
||||
proxy: {
|
||||
"/api/v1": {
|
||||
target: "http://127.0.0.1:8888", // 本地接口地址
|
||||
// target: "https://yuyuecha.com", // 本地接口地址
|
||||
// target: "http://127.0.0.1:8888", // 本地接口地址
|
||||
target: "https://www.yuyuecha.com", // 本地接口地址
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path, // 可选:确保路径不被修改
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user