f
This commit is contained in:
@@ -6,6 +6,7 @@ import { useUserStore } from "@/stores/userStore";
|
|||||||
import { useDialogStore } from "@/stores/dialogStore";
|
import { useDialogStore } from "@/stores/dialogStore";
|
||||||
import { useAuthStore } from "@/stores/authStore";
|
import { useAuthStore } from "@/stores/authStore";
|
||||||
import { useWeixinShare } from "@/composables/useWeixinShare";
|
import { useWeixinShare } from "@/composables/useWeixinShare";
|
||||||
|
import { useAppConfig } from "@/composables/useAppConfig";
|
||||||
import WechatOverlay from "@/components/WechatOverlay.vue";
|
import WechatOverlay from "@/components/WechatOverlay.vue";
|
||||||
// import MaintenanceDialog from "@/components/MaintenanceDialog.vue";
|
// import MaintenanceDialog from "@/components/MaintenanceDialog.vue";
|
||||||
|
|
||||||
@@ -15,8 +16,11 @@ const userStore = useUserStore();
|
|||||||
const dialogStore = useDialogStore();
|
const dialogStore = useDialogStore();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const { configWeixinShare, setDynamicShare } = useWeixinShare();
|
const { configWeixinShare, setDynamicShare } = useWeixinShare();
|
||||||
|
const { loadAppConfig } = useAppConfig();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
void loadAppConfig();
|
||||||
|
|
||||||
// 检查token版本,如果版本不匹配则清除旧token
|
// 检查token版本,如果版本不匹配则清除旧token
|
||||||
checkTokenVersion()
|
checkTokenVersion()
|
||||||
|
|
||||||
@@ -218,7 +222,7 @@ const h5WeixinGetCode = () => {
|
|||||||
<template>
|
<template>
|
||||||
<RouterView />
|
<RouterView />
|
||||||
|
|
||||||
<WechatOverlay />
|
<!-- <WechatOverlay /> -->
|
||||||
<BindPhoneDialog />
|
<BindPhoneDialog />
|
||||||
<!-- <MaintenanceDialog /> -->
|
<!-- <MaintenanceDialog /> -->
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
1
src/auto-imports.d.ts
vendored
1
src/auto-imports.d.ts
vendored
@@ -120,6 +120,7 @@ declare global {
|
|||||||
const useAliyunCaptcha: typeof import('./composables/useAliyunCaptcha.js')['default']
|
const useAliyunCaptcha: typeof import('./composables/useAliyunCaptcha.js')['default']
|
||||||
const useAnimate: typeof import('@vueuse/core')['useAnimate']
|
const useAnimate: typeof import('@vueuse/core')['useAnimate']
|
||||||
const useApiFetch: typeof import('./composables/useApiFetch.js')['default']
|
const useApiFetch: typeof import('./composables/useApiFetch.js')['default']
|
||||||
|
const useAppConfig: typeof import('./composables/useAppConfig.js')['useAppConfig']
|
||||||
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
|
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
|
||||||
const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
|
const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
|
||||||
const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
|
const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
|
||||||
|
|||||||
@@ -7,6 +7,23 @@ import { splitDWBG8B4DForTabs } from '@/ui/CDWBG8B4D/utils/simpleSplitter.js';
|
|||||||
import { splitDWBG6A2CForTabs } from '@/ui/DWBG6A2C/utils/simpleSplitter.js';
|
import { splitDWBG6A2CForTabs } from '@/ui/DWBG6A2C/utils/simpleSplitter.js';
|
||||||
import { splitCJRZQ5E9FForTabs } from '@/ui/CJRZQ5E9F/utils/simpleSplitter.js';
|
import { splitCJRZQ5E9FForTabs } from '@/ui/CJRZQ5E9F/utils/simpleSplitter.js';
|
||||||
import { splitCQYGL3F8EForTabs } from '@/ui/CQYGL3F8E/utils/simpleSplitter.js';
|
import { splitCQYGL3F8EForTabs } from '@/ui/CQYGL3F8E/utils/simpleSplitter.js';
|
||||||
|
import { useAppConfig } from '@/composables/useAppConfig';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
// 与首页/查询表一致:报告保留天数由 App.vue 拉取的 /app/config 中 query.retention_days
|
||||||
|
const { appConfig } = useAppConfig();
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
/**
|
||||||
|
* App 内嵌 WebView 走 /app/report、/app/example(无 PageLayout 顶栏),sticky 的 offset-top 若为 46
|
||||||
|
* 会与「为 van-nav-bar 预留」一致,在顶栏不存在时 Tab 上方会多出一块空白。
|
||||||
|
* 浏览器内 /report、/example 仍带顶栏,保持 46。
|
||||||
|
*/
|
||||||
|
const tabsStickyOffsetTop = computed(() => {
|
||||||
|
if (route.path.startsWith('/app/') || route.meta?.embedForApp)
|
||||||
|
return 0;
|
||||||
|
return 46;
|
||||||
|
});
|
||||||
|
|
||||||
// 动态导入产品背景图片的函数
|
// 动态导入产品背景图片的函数
|
||||||
const loadProductBackground = async (productType) => {
|
const loadProductBackground = async (productType) => {
|
||||||
@@ -782,7 +799,7 @@ watch([reportData, componentRiskScores], () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tabs 区域 -->
|
<!-- Tabs 区域 -->
|
||||||
<StyledTabs v-model:active="active" scrollspy sticky :offset-top="46">
|
<StyledTabs v-model:active="active" scrollspy sticky :offset-top="tabsStickyOffsetTop">
|
||||||
<div class="flex flex-col gap-y-4 p-4">
|
<div class="flex flex-col gap-y-4 p-4">
|
||||||
<LEmpty v-if="isEmpty" />
|
<LEmpty v-if="isEmpty" />
|
||||||
<van-tab title="分析指数">
|
<van-tab title="分析指数">
|
||||||
@@ -837,8 +854,7 @@ watch([reportData, componentRiskScores], () => {
|
|||||||
1、本份报告是在取得您个人授权后,我们才向合法存有您以上个人信息的机构去调取相关内容,我们不会以任何形式对您的报告进行存储,除您和您授权的人外不会提供给任何人和机构进行查看。
|
1、本份报告是在取得您个人授权后,我们才向合法存有您以上个人信息的机构去调取相关内容,我们不会以任何形式对您的报告进行存储,除您和您授权的人外不会提供给任何人和机构进行查看。
|
||||||
</p>
|
</p>
|
||||||
<p class="text-[#999999]">
|
<p class="text-[#999999]">
|
||||||
2、本报告自生成之日起,有效期 30
|
2、本报告自生成之日起,有效期 {{ appConfig.query.retention_days }} 天,过期自动删除。如果您对本份报告存有异议,可能是合作机构数据有延迟或未能获取到您的相关数据,出于合作平台数据隐私的保护,本平台将不做任何解释。
|
||||||
天,过期自动删除。如果您对本份报告存有异议,可能是合作机构数据有延迟或未能获取到您的相关数据,出于合作平台数据隐私的保护,本平台将不做任何解释。
|
|
||||||
</p>
|
</p>
|
||||||
<p class="text-[#999999]">
|
<p class="text-[#999999]">
|
||||||
3、若以上数据有错误,请联系平台客服。
|
3、若以上数据有错误,请联系平台客服。
|
||||||
|
|||||||
@@ -37,6 +37,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { calculatePromotionPricing, safeTruncate } from '@/utils/promotionPricing'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
defaultPrice: {
|
defaultPrice: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@@ -58,31 +60,12 @@ watch(show, () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const costPrice = computed(() => {
|
const pricingResult = computed(() => {
|
||||||
if (!productConfig.value) return 0.00
|
return calculatePromotionPricing(price.value, productConfig.value)
|
||||||
// 平台定价成本
|
|
||||||
let platformPricing = 0
|
|
||||||
platformPricing += productConfig.value.cost_price
|
|
||||||
if (price.value > productConfig.value.p_pricing_standard) {
|
|
||||||
platformPricing += (price.value - productConfig.value.p_pricing_standard) * productConfig.value.p_overpricing_ratio
|
|
||||||
}
|
|
||||||
|
|
||||||
if (productConfig.value.a_pricing_standard > platformPricing && productConfig.value.a_pricing_end > platformPricing && productConfig.value.a_overpricing_ratio > 0) {
|
|
||||||
if (price.value > productConfig.value.a_pricing_standard) {
|
|
||||||
if (price.value > productConfig.value.a_pricing_end) {
|
|
||||||
platformPricing += (productConfig.value.a_pricing_end - productConfig.value.a_pricing_standard) * productConfig.value.a_overpricing_ratio
|
|
||||||
} else {
|
|
||||||
platformPricing += (price.value - productConfig.value.a_pricing_standard) * productConfig.value.a_overpricing_ratio
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return safeTruncate(platformPricing)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const promotionRevenue = computed(() => {
|
const costPrice = computed(() => pricingResult.value.costPrice)
|
||||||
return safeTruncate(price.value - costPrice.value)
|
const promotionRevenue = computed(() => pricingResult.value.promotionRevenue)
|
||||||
});
|
|
||||||
|
|
||||||
// 价格校验与修正逻辑
|
// 价格校验与修正逻辑
|
||||||
const validatePrice = (currentPrice) => {
|
const validatePrice = (currentPrice) => {
|
||||||
@@ -122,15 +105,6 @@ const validatePrice = (currentPrice) => {
|
|||||||
console.log(newPrice, message)
|
console.log(newPrice, message)
|
||||||
return { newPrice, message };
|
return { newPrice, message };
|
||||||
}
|
}
|
||||||
function safeTruncate(num, decimals = 2) {
|
|
||||||
if (isNaN(num) || !isFinite(num)) return "0.00";
|
|
||||||
|
|
||||||
const factor = 10 ** decimals;
|
|
||||||
const scaled = Math.trunc(num * factor);
|
|
||||||
const truncated = scaled / factor;
|
|
||||||
|
|
||||||
return truncated.toFixed(decimals);
|
|
||||||
}
|
|
||||||
const isManualConfirm = ref(false)
|
const isManualConfirm = ref(false)
|
||||||
const onConfirm = () => {
|
const onConfirm = () => {
|
||||||
if (isManualConfirm.value) return
|
if (isManualConfirm.value) return
|
||||||
|
|||||||
46
src/utils/promotionPricing.js
Normal file
46
src/utils/promotionPricing.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
export function safeTruncate(num, decimals = 2) {
|
||||||
|
if (Number.isNaN(num) || !Number.isFinite(num))
|
||||||
|
return '0.00'
|
||||||
|
|
||||||
|
const factor = 10 ** decimals
|
||||||
|
const scaled = Math.trunc(num * factor)
|
||||||
|
return (scaled / factor).toFixed(decimals)
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculatePlatformOverpricingCost(price, config) {
|
||||||
|
if (price <= config.p_pricing_standard)
|
||||||
|
return 0
|
||||||
|
return (price - config.p_pricing_standard) * config.p_overpricing_ratio
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateSuperiorOverpricingCost(price, config) {
|
||||||
|
if (config.a_overpricing_ratio <= 0)
|
||||||
|
return 0
|
||||||
|
if (price <= config.a_pricing_standard)
|
||||||
|
return 0
|
||||||
|
if (config.a_pricing_end <= config.a_pricing_standard)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
const superiorRangeAmount = Math.min(price, config.a_pricing_end) - config.a_pricing_standard
|
||||||
|
return Math.max(0, superiorRangeAmount) * config.a_overpricing_ratio
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calculatePromotionPricing(priceInput, config) {
|
||||||
|
if (!config)
|
||||||
|
return { costPrice: '0.00', promotionRevenue: '0.00' }
|
||||||
|
|
||||||
|
const price = Number(priceInput)
|
||||||
|
if (!Number.isFinite(price))
|
||||||
|
return { costPrice: '0.00', promotionRevenue: '0.00' }
|
||||||
|
|
||||||
|
const baseCost = Number(config.cost_price) || 0
|
||||||
|
const platformOverpricingCost = calculatePlatformOverpricingCost(price, config)
|
||||||
|
const superiorOverpricingCost = calculateSuperiorOverpricingCost(price, config)
|
||||||
|
const totalCost = baseCost + platformOverpricingCost + superiorOverpricingCost
|
||||||
|
const revenue = price - totalCost
|
||||||
|
|
||||||
|
return {
|
||||||
|
costPrice: safeTruncate(totalCost),
|
||||||
|
promotionRevenue: safeTruncate(revenue),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -763,12 +763,6 @@ function selectType(type) {
|
|||||||
|
|
||||||
// 申请VIP或SVIP
|
// 申请VIP或SVIP
|
||||||
async function applyVip() {
|
async function applyVip() {
|
||||||
// 如果是VIP想升级到SVIP,提示联系客服
|
|
||||||
if (isVip.value && selectedType.value === 'svip') {
|
|
||||||
contactService()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是SVIP要降级到VIP,提示不能降级
|
// 如果是SVIP要降级到VIP,提示不能降级
|
||||||
if (isSvip.value && selectedType.value === 'vip') {
|
if (isSvip.value && selectedType.value === 'vip') {
|
||||||
showToast('SVIP会员不能降级到VIP会员')
|
showToast('SVIP会员不能降级到VIP会员')
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<div v-if="isAgent" class="absolute -bottom-2 -right-2">
|
<div v-if="isAgent" class="absolute -bottom-2 -right-2">
|
||||||
<div class="flex items-center justify-center rounded-full px-3 py-1 text-xs font-bold text-white shadow-sm"
|
<div class="flex items-center justify-center rounded-full px-3 py-1 text-xs font-bold text-white shadow-sm"
|
||||||
:class="levelGradient.badge">
|
:class="levelGradient.badge">
|
||||||
{{ levelNames[level] }}
|
{{ levelNames[normalizedLevel] }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -39,15 +39,15 @@
|
|||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
<p v-if="isAgent" class="text-sm font-medium" :class="levelGradient.text">
|
<p v-if="isAgent" class="text-sm font-medium" :class="levelGradient.text">
|
||||||
🎖️ {{ levelText[level] }}
|
🎖️ {{ levelText[normalizedLevel] }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<VipBanner v-if="isAgent && (level === 'normal' || level === '')" />
|
<VipBanner v-if="isAgent && normalizedLevel === 'NORMAL'" />
|
||||||
<!-- 功能菜单 -->
|
<!-- 功能菜单 -->
|
||||||
<div class="">
|
<div class="">
|
||||||
<div class="bg-white rounded-xl shadow-sm overflow-hidden">
|
<div class="bg-white rounded-xl shadow-sm overflow-hidden">
|
||||||
<template v-if="isAgent && ['VIP', 'SVIP'].includes(level)">
|
<template v-if="isAgent && ['VIP', 'SVIP'].includes(normalizedLevel)">
|
||||||
<button
|
<button
|
||||||
class="w-full flex items-center justify-between px-6 py-4 hover:bg-purple-50 transition-colors border-b border-gray-100"
|
class="w-full flex items-center justify-between px-6 py-4 hover:bg-purple-50 transition-colors border-b border-gray-100"
|
||||||
@click="toVipConfig">
|
@click="toVipConfig">
|
||||||
@@ -165,8 +165,17 @@ const { isAgent, level, ExpiryTime } = storeToRefs(agentStore);
|
|||||||
const { userName, userAvatar, isLoggedIn, mobile } = storeToRefs(userStore);
|
const { userName, userAvatar, isLoggedIn, mobile } = storeToRefs(userStore);
|
||||||
const { isWeChat } = useEnv();
|
const { isWeChat } = useEnv();
|
||||||
|
|
||||||
|
const normalizedLevel = computed(() => {
|
||||||
|
const raw = String(level.value || "").trim();
|
||||||
|
if (!raw || ["NORMAL", "NORNAL"].includes(raw.toUpperCase()) || raw === "normal" || raw === "nornal") return "NORMAL";
|
||||||
|
if (raw.toUpperCase().includes("SVIP")) return "SVIP";
|
||||||
|
if (raw.toUpperCase().includes("VIP")) return "VIP";
|
||||||
|
return "NORMAL";
|
||||||
|
});
|
||||||
|
|
||||||
const levelNames = {
|
const levelNames = {
|
||||||
normal: "普通代理",
|
normal: "普通代理",
|
||||||
|
NORMAL: "普通代理",
|
||||||
"": "普通代理",
|
"": "普通代理",
|
||||||
VIP: "VIP代理",
|
VIP: "VIP代理",
|
||||||
SVIP: "SVIP代理",
|
SVIP: "SVIP代理",
|
||||||
@@ -174,6 +183,7 @@ const levelNames = {
|
|||||||
|
|
||||||
const levelText = {
|
const levelText = {
|
||||||
normal: "基础代理特权",
|
normal: "基础代理特权",
|
||||||
|
NORMAL: "基础代理特权",
|
||||||
"": "基础代理特权",
|
"": "基础代理特权",
|
||||||
VIP: "高级代理特权",
|
VIP: "高级代理特权",
|
||||||
SVIP: "尊享代理特权",
|
SVIP: "尊享代理特权",
|
||||||
@@ -182,24 +192,27 @@ const levelText = {
|
|||||||
const levelGradient = computed(() => ({
|
const levelGradient = computed(() => ({
|
||||||
border: {
|
border: {
|
||||||
normal: "bg-gradient-to-r from-gray-300 to-gray-400",
|
normal: "bg-gradient-to-r from-gray-300 to-gray-400",
|
||||||
|
NORMAL: "bg-gradient-to-r from-gray-300 to-gray-400",
|
||||||
"": "bg-gradient-to-r from-gray-300 to-gray-400",
|
"": "bg-gradient-to-r from-gray-300 to-gray-400",
|
||||||
VIP: "bg-gradient-to-r from-yellow-400 to-amber-500",
|
VIP: "bg-gradient-to-r from-yellow-400 to-amber-500",
|
||||||
SVIP: "bg-gradient-to-r from-purple-400 to-pink-400 shadow-[0_0_15px_rgba(163,51,200,0.2)]",
|
SVIP: "bg-gradient-to-r from-purple-400 to-pink-400 shadow-[0_0_15px_rgba(163,51,200,0.2)]",
|
||||||
}[level.value],
|
}[normalizedLevel.value],
|
||||||
|
|
||||||
badge: {
|
badge: {
|
||||||
normal: "bg-gradient-to-r from-gray-500 to-gray-600",
|
normal: "bg-gradient-to-r from-gray-500 to-gray-600",
|
||||||
|
NORMAL: "bg-gradient-to-r from-gray-500 to-gray-600",
|
||||||
"": "bg-gradient-to-r from-gray-500 to-gray-600",
|
"": "bg-gradient-to-r from-gray-500 to-gray-600",
|
||||||
VIP: "bg-gradient-to-r from-yellow-500 to-amber-600",
|
VIP: "bg-gradient-to-r from-yellow-500 to-amber-600",
|
||||||
SVIP: "bg-gradient-to-r from-purple-500 to-pink-500",
|
SVIP: "bg-gradient-to-r from-purple-500 to-pink-500",
|
||||||
}[level.value],
|
}[normalizedLevel.value],
|
||||||
|
|
||||||
text: {
|
text: {
|
||||||
normal: "text-gray-600",
|
normal: "text-gray-600",
|
||||||
|
NORMAL: "text-gray-600",
|
||||||
"": "text-gray-600",
|
"": "text-gray-600",
|
||||||
VIP: "text-amber-600",
|
VIP: "text-amber-600",
|
||||||
SVIP: "text-purple-600",
|
SVIP: "text-purple-600",
|
||||||
}[level.value],
|
}[normalizedLevel.value],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const maskName = (name) => {
|
const maskName = (name) => {
|
||||||
@@ -276,8 +289,9 @@ function formatExpiryTime(expiryTimeStr) {
|
|||||||
const getDefaultAvatar = () => {
|
const getDefaultAvatar = () => {
|
||||||
if (!isAgent.value) return headShot;
|
if (!isAgent.value) return headShot;
|
||||||
|
|
||||||
switch (level.value) {
|
switch (normalizedLevel.value) {
|
||||||
case "normal":
|
case "normal":
|
||||||
|
case "NORMAL":
|
||||||
case "":
|
case "":
|
||||||
return "/image/shot_nonal.png";
|
return "/image/shot_nonal.png";
|
||||||
case "VIP":
|
case "VIP":
|
||||||
|
|||||||
@@ -87,6 +87,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import PriceInputPopup from '@/components/PriceInputPopup.vue';
|
import PriceInputPopup from '@/components/PriceInputPopup.vue';
|
||||||
import VipBanner from '@/components/VipBanner.vue';
|
import VipBanner from '@/components/VipBanner.vue';
|
||||||
|
import { calculatePromotionPricing } from '@/utils/promotionPricing';
|
||||||
const showTypePicker = ref(false);
|
const showTypePicker = ref(false);
|
||||||
const showApplyPopup = ref(false); // 用来控制申请代理弹窗的显示
|
const showApplyPopup = ref(false); // 用来控制申请代理弹窗的显示
|
||||||
const showPricePicker = ref(false);
|
const showPricePicker = ref(false);
|
||||||
@@ -120,41 +121,16 @@ const reportTypes = computed(() => {
|
|||||||
// return (pickerProductConfig.value.cost_price + platformPricing).toFixed(2)
|
// return (pickerProductConfig.value.cost_price + platformPricing).toFixed(2)
|
||||||
// })
|
// })
|
||||||
|
|
||||||
const costPrice = computed(() => {
|
const pricingResult = computed(() => {
|
||||||
if (!pickerProductConfig.value) return 0.00
|
return calculatePromotionPricing(clientPrice.value, pickerProductConfig.value)
|
||||||
// 平台定价成本
|
|
||||||
let platformPricing = 0
|
|
||||||
platformPricing += pickerProductConfig.value.cost_price
|
|
||||||
if (clientPrice.value > pickerProductConfig.value.p_pricing_standard) {
|
|
||||||
platformPricing += (clientPrice.value - pickerProductConfig.value.p_pricing_standard) * pickerProductConfig.value.p_overpricing_ratio
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pickerProductConfig.value.a_pricing_standard > platformPricing && pickerProductConfig.value.a_pricing_end > platformPricing && pickerProductConfig.value.a_overpricing_ratio > 0) {
|
|
||||||
if (clientPrice.value > pickerProductConfig.value.a_pricing_standard) {
|
|
||||||
if (clientPrice.value > pickerProductConfig.value.a_pricing_end) {
|
|
||||||
platformPricing += (pickerProductConfig.value.a_pricing_end - pickerProductConfig.value.a_pricing_standard) * pickerProductConfig.value.a_overpricing_ratio
|
|
||||||
} else {
|
|
||||||
platformPricing += (clientPrice.value - pickerProductConfig.value.a_pricing_standard) * pickerProductConfig.value.a_overpricing_ratio
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return safeTruncate(platformPricing)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const costPrice = computed(() => pricingResult.value.costPrice)
|
||||||
|
|
||||||
const promotionRevenue = computed(() => {
|
const promotionRevenue = computed(() => {
|
||||||
return safeTruncate(clientPrice.value - costPrice.value)
|
return pricingResult.value.promotionRevenue
|
||||||
});
|
});
|
||||||
const showQRcode = ref(false);
|
const showQRcode = ref(false);
|
||||||
function safeTruncate(num, decimals = 2) {
|
|
||||||
if (isNaN(num) || !isFinite(num)) return "0.00";
|
|
||||||
|
|
||||||
const factor = 10 ** decimals;
|
|
||||||
const scaled = Math.trunc(num * factor);
|
|
||||||
const truncated = scaled / factor;
|
|
||||||
|
|
||||||
return truncated.toFixed(decimals);
|
|
||||||
}
|
|
||||||
const generatePromotionCode = async () => {
|
const generatePromotionCode = async () => {
|
||||||
if (selectedReportType.value.length === 0) {
|
if (selectedReportType.value.length === 0) {
|
||||||
showToast({ message: '请选择报告类型' });
|
showToast({ message: '请选择报告类型' });
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const fetchRewardDetails = async () => {
|
|||||||
// 更新用户信息
|
// 更新用户信息
|
||||||
userInfo.value = {
|
userInfo.value = {
|
||||||
createTime: data.value.data.create_time,
|
createTime: data.value.data.create_time,
|
||||||
level: data.value.data.level_name || '普通',
|
level: data.value.data.level_name || data.value.data.level || '',
|
||||||
mobile: data.value.data.mobile,
|
mobile: data.value.data.mobile,
|
||||||
}
|
}
|
||||||
// 更新汇总数据
|
// 更新汇总数据
|
||||||
@@ -148,6 +148,29 @@ const userInfo = ref({})
|
|||||||
const summary = ref({})
|
const summary = ref({})
|
||||||
const statistics = ref([])
|
const statistics = ref([])
|
||||||
|
|
||||||
|
const normalizeAgentLevel = (value) => {
|
||||||
|
const raw = String(value || '').trim()
|
||||||
|
if (!raw)
|
||||||
|
return 'NORMAL'
|
||||||
|
const cleaned = raw.replace(/代理$/, '')
|
||||||
|
if (cleaned === '普通')
|
||||||
|
return 'NORMAL'
|
||||||
|
const upper = cleaned.toUpperCase()
|
||||||
|
if (upper.includes('SVIP'))
|
||||||
|
return 'SVIP'
|
||||||
|
if (upper.includes('VIP'))
|
||||||
|
return 'VIP'
|
||||||
|
if (upper === 'NORMAL' || upper === 'NORNAL')
|
||||||
|
return 'NORMAL'
|
||||||
|
return 'NORMAL'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAgentLevelLabel = value => ({
|
||||||
|
NORMAL: '普通代理',
|
||||||
|
VIP: 'VIP代理',
|
||||||
|
SVIP: 'SVIP代理',
|
||||||
|
}[normalizeAgentLevel(value)] || '普通代理')
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchRewardDetails()
|
fetchRewardDetails()
|
||||||
})
|
})
|
||||||
@@ -214,7 +237,7 @@ const formatNumber = num => {
|
|||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
<div class="text-xl font-semibold text-gray-800">{{ userInfo.mobile }}</div>
|
<div class="text-xl font-semibold text-gray-800">{{ userInfo.mobile }}</div>
|
||||||
<span class="px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-600">
|
<span class="px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-600">
|
||||||
{{ userInfo.level }}代理
|
{{ getAgentLevelLabel(userInfo.level) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -64,16 +64,38 @@ const formatNumber = num => {
|
|||||||
return Number(num).toFixed(2)
|
return Number(num).toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getLevelText = item => {
|
const normalizeAgentLevel = (value) => {
|
||||||
return item?.level_name || item?.level || '普通'
|
const raw = String(value || '').trim()
|
||||||
|
if (!raw)
|
||||||
|
return 'NORMAL'
|
||||||
|
const cleaned = raw.replace(/代理$/, '')
|
||||||
|
if (cleaned === '普通')
|
||||||
|
return 'NORMAL'
|
||||||
|
const upper = cleaned.toUpperCase()
|
||||||
|
if (upper.includes('SVIP'))
|
||||||
|
return 'SVIP'
|
||||||
|
if (upper.includes('VIP'))
|
||||||
|
return 'VIP'
|
||||||
|
if (upper === 'NORMAL' || upper === 'NORNAL')
|
||||||
|
return 'NORMAL'
|
||||||
|
return 'NORMAL'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLevelText = (item) => {
|
||||||
|
const normalized = normalizeAgentLevel(item?.level_name || item?.level)
|
||||||
|
return {
|
||||||
|
NORMAL: '普通代理',
|
||||||
|
VIP: 'VIP代理',
|
||||||
|
SVIP: 'SVIP代理',
|
||||||
|
}[normalized] || '普通代理'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取等级标签样式
|
// 获取等级标签样式
|
||||||
const getLevelClass = level => {
|
const getLevelClass = (levelText) => {
|
||||||
switch (level) {
|
switch (levelText) {
|
||||||
case 'SVIP':
|
case 'SVIP代理':
|
||||||
return 'bg-purple-100 text-purple-600'
|
return 'bg-purple-100 text-purple-600'
|
||||||
case 'VIP':
|
case 'VIP代理':
|
||||||
return 'bg-blue-100 text-blue-600'
|
return 'bg-blue-100 text-blue-600'
|
||||||
default:
|
default:
|
||||||
return 'bg-gray-100 text-gray-600'
|
return 'bg-gray-100 text-gray-600'
|
||||||
@@ -122,7 +144,7 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="text-xl font-semibold text-gray-800">{{ item.mobile }}</div>
|
<div class="text-xl font-semibold text-gray-800">{{ item.mobile }}</div>
|
||||||
<span :class="['px-3 py-1 rounded-full text-sm font-medium', getLevelClass(getLevelText(item))]">
|
<span :class="['px-3 py-1 rounded-full text-sm font-medium', getLevelClass(getLevelText(item))]">
|
||||||
{{ getLevelText(item) }}代理
|
{{ getLevelText(item) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ function generateSeoTemplatesPlugin() {
|
|||||||
if (!viteConfig || viteConfig.command !== "build") return;
|
if (!viteConfig || viteConfig.command !== "build") return;
|
||||||
const env = loadEnv(viteConfig.mode, viteConfig.root, "");
|
const env = loadEnv(viteConfig.mode, viteConfig.root, "");
|
||||||
const mergedEnv = { ...env, ...process.env };
|
const mergedEnv = { ...env, ...process.env };
|
||||||
const script = path.join(viteConfig.root, "seo/generate-seo-templates.cjs");
|
const script = path.join(
|
||||||
|
viteConfig.root,
|
||||||
|
"seo/generate-seo-templates.cjs",
|
||||||
|
);
|
||||||
const result = spawnSync(process.execPath, [script], {
|
const result = spawnSync(process.execPath, [script], {
|
||||||
cwd: viteConfig.root,
|
cwd: viteConfig.root,
|
||||||
env: mergedEnv,
|
env: mergedEnv,
|
||||||
@@ -46,16 +49,16 @@ export default defineConfig({
|
|||||||
strictPort: true, // 如果端口被占用则抛出错误而不是使用下一个可用端口
|
strictPort: true, // 如果端口被占用则抛出错误而不是使用下一个可用端口
|
||||||
proxy: {
|
proxy: {
|
||||||
"/api/v1": {
|
"/api/v1": {
|
||||||
target: "http://127.0.0.1:8888", // 本地接口地址
|
// target: "http://127.0.0.1:8888", // 本地接口地址
|
||||||
// target: "https://www.tianyuandb.com", // 本地接口地址
|
target: "https://chimei.ronsafe.cn/", // 本地接口地址
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
// 构建优化
|
// 构建优化
|
||||||
target: 'es2015', // 支持更多浏览器
|
target: "es2015", // 支持更多浏览器
|
||||||
minify: 'terser', // 使用terser进行压缩
|
minify: "terser", // 使用terser进行压缩
|
||||||
terserOptions: {
|
terserOptions: {
|
||||||
compress: {
|
compress: {
|
||||||
drop_console: true, // 移除console.log
|
drop_console: true, // 移除console.log
|
||||||
@@ -66,15 +69,15 @@ export default defineConfig({
|
|||||||
output: {
|
output: {
|
||||||
// 代码分割策略
|
// 代码分割策略
|
||||||
manualChunks: {
|
manualChunks: {
|
||||||
vendor: ['vue', 'vue-router', 'pinia'],
|
vendor: ["vue", "vue-router", "pinia"],
|
||||||
vant: ['vant'],
|
vant: ["vant"],
|
||||||
utils: ['axios', 'lodash', 'crypto-js'],
|
utils: ["axios", "lodash", "crypto-js"],
|
||||||
charts: ['echarts', 'vue-echarts'],
|
charts: ["echarts", "vue-echarts"],
|
||||||
},
|
},
|
||||||
// 文件名策略
|
// 文件名策略
|
||||||
chunkFileNames: 'assets/js/[name]-[hash].js',
|
chunkFileNames: "assets/js/[name]-[hash].js",
|
||||||
entryFileNames: 'assets/js/[name]-[hash].js',
|
entryFileNames: "assets/js/[name]-[hash].js",
|
||||||
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
|
assetFileNames: "assets/[ext]/[name]-[hash].[ext]",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// 启用CSS代码分割
|
// 启用CSS代码分割
|
||||||
@@ -92,11 +95,7 @@ export default defineConfig({
|
|||||||
"@vueuse/core", // 自动引入 VueUse 中的工具函数(可选)
|
"@vueuse/core", // 自动引入 VueUse 中的工具函数(可选)
|
||||||
],
|
],
|
||||||
dts: "src/auto-imports.d.ts", // 生成类型定义文件(可选)
|
dts: "src/auto-imports.d.ts", // 生成类型定义文件(可选)
|
||||||
dirs: [
|
dirs: ["src/composables", "src/stores", "src/components"],
|
||||||
"src/composables",
|
|
||||||
"src/stores",
|
|
||||||
"src/components",
|
|
||||||
],
|
|
||||||
resolvers: [VantResolver()],
|
resolvers: [VantResolver()],
|
||||||
}),
|
}),
|
||||||
Components({
|
Components({
|
||||||
@@ -112,6 +111,6 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
// 优化依赖预构建
|
// 优化依赖预构建
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
include: ['vue', 'vue-router', 'pinia', 'vant', 'axios'],
|
include: ["vue", "vue-router", "pinia", "vant", "axios"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user