395 lines
12 KiB
Vue
395 lines
12 KiB
Vue
<script setup>
|
||
definePage({
|
||
layout: 'PageLayout',
|
||
style: {
|
||
navigationBarTitleText: '推广报告',
|
||
navigationStyle: 'default',
|
||
},
|
||
})
|
||
|
||
import { ref, computed, watch, onMounted } from 'vue'
|
||
import { onLoad } from '@dcloudio/uni-app'
|
||
import useApiFetch from '@/composables/useApiFetch'
|
||
import { getProductConfig, generateLink } from '@/api/agent'
|
||
import PriceInputPopup from '@/components/PriceInputPopup.vue'
|
||
import QRcode from '@/components/QRcode.vue'
|
||
import ReportFeatures from '@/components/ReportFeatures.vue'
|
||
|
||
const currentFeature = ref('')
|
||
const reportTypes = ref([])
|
||
const showPricePicker = ref(false)
|
||
const pickerFieldText = ref('')
|
||
const selectedReportTypeIndex = ref(0)
|
||
const pickerProductConfig = ref(null)
|
||
const pickerFieldVal = ref(null)
|
||
const clientPrice = ref(null)
|
||
const productConfig = ref(null)
|
||
const fullLink = ref('')
|
||
const featureData = ref({})
|
||
const showQRcode = ref(false)
|
||
const showFollowPopup = ref(false)
|
||
|
||
const logoMap = {
|
||
riskassessment: '/static/promote/personal_data_logo.png',
|
||
companyinfo: '/static/promote/company_logo.png',
|
||
preloanbackgroundcheck: '/static/promote/preloan_background_check_logo.png',
|
||
marriage: '/static/promote/marriage_risk_logo.png',
|
||
homeservice: '/static/promote/housekeeping_risk_logo.png',
|
||
backgroundcheck: '/static/promote/backgroundcheck_logo.png',
|
||
consumerFinanceReport: '/static/promote/consumer_finance_report_logo.png',
|
||
}
|
||
|
||
const currentLogo = computed(() => (currentFeature.value ? logoMap[currentFeature.value] || null : null))
|
||
|
||
function safeTruncate(num, decimals = 2) {
|
||
if (isNaN(num) || !isFinite(num)) return '0.00'
|
||
const factor = 10 ** decimals
|
||
const scaled = Math.trunc(num * factor)
|
||
return (scaled / factor).toFixed(decimals)
|
||
}
|
||
|
||
const baseCost = computed(() => {
|
||
if (!pickerProductConfig.value) return '0.00'
|
||
const v = Number(pickerProductConfig.value.actual_base_price) || 0
|
||
return safeTruncate(v)
|
||
})
|
||
|
||
const raiseCost = computed(() => {
|
||
if (!pickerProductConfig.value) return '0.00'
|
||
const priceNum = Number(clientPrice.value) || 0
|
||
const threshold = Number(pickerProductConfig.value.price_threshold) || 0
|
||
const rate = Number(pickerProductConfig.value.price_fee_rate) || 0
|
||
let cost = 0
|
||
if (priceNum > threshold) cost = (priceNum - threshold) * rate
|
||
return safeTruncate(cost)
|
||
})
|
||
|
||
const promotionRevenue = computed(() => {
|
||
const priceNum = Number(clientPrice.value) || 0
|
||
const base = parseFloat(baseCost.value) || 0
|
||
const raise = parseFloat(raiseCost.value) || 0
|
||
const revenue = priceNum - base - raise
|
||
return safeTruncate(revenue >= 0 ? revenue : 0)
|
||
})
|
||
|
||
const pickerProductConfigForPopup = computed(() => pickerProductConfig.value || {})
|
||
|
||
async function getProductInfo() {
|
||
if (!currentFeature.value) return
|
||
try {
|
||
const { data, error } = await useApiFetch(`/product/en/${currentFeature.value}`).get().json()
|
||
if (data.value && !error.value && data.value.data) {
|
||
featureData.value = data.value.data
|
||
if (featureData.value.features?.length) {
|
||
featureData.value.features.sort((a, b) => {
|
||
if (a.api_id === 'FLXG0V4B') return -1
|
||
if (b.api_id === 'FLXG0V4B') return 1
|
||
return 0
|
||
})
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error(e)
|
||
}
|
||
}
|
||
|
||
function findReportTypeByFeature(feature) {
|
||
return reportTypes.value.find((t) => t.value === feature)
|
||
}
|
||
|
||
function selectTypePicker(reportType) {
|
||
if (!reportType) return
|
||
pickerFieldVal.value = reportType.value
|
||
pickerFieldText.value = reportType.text
|
||
const idx = reportTypes.value.findIndex((t) => t.value === reportType.value)
|
||
selectedReportTypeIndex.value = idx >= 0 ? idx : 0
|
||
if (productConfig.value) {
|
||
for (const i of productConfig.value) {
|
||
if (i.product_en === reportType.value) {
|
||
pickerProductConfig.value = i
|
||
clientPrice.value = Number(i.actual_base_price) || Number(i.price_range_min) || 0
|
||
break
|
||
}
|
||
}
|
||
}
|
||
currentFeature.value = reportType.value
|
||
getProductInfo()
|
||
}
|
||
|
||
async function getPromoteConfig() {
|
||
const token = typeof uni !== 'undefined' && uni.getStorageSync ? uni.getStorageSync('token') : ''
|
||
if (!token) return
|
||
const { data, error } = await getProductConfig()
|
||
if (!data.value || error.value || data.value.code !== 200) return
|
||
const list = data.value.data?.list || []
|
||
productConfig.value = list
|
||
const types = list.filter((c) => c.product_en).map((c) => ({ text: c.product_name, value: c.product_en, id: c.product_id }))
|
||
reportTypes.value = types
|
||
let reportType = currentFeature.value ? findReportTypeByFeature(currentFeature.value) : null
|
||
if (!reportType && types.length) reportType = types[0]
|
||
if (reportType) selectTypePicker(reportType)
|
||
}
|
||
|
||
function onTypePickerChange(e) {
|
||
const idx = Number(e.detail?.value ?? e.detail?.value?.[0] ?? 0)
|
||
selectedReportTypeIndex.value = idx
|
||
const reportType = reportTypes.value[idx]
|
||
if (reportType) selectTypePicker(reportType)
|
||
}
|
||
|
||
async function generatePromotionCode() {
|
||
if (!pickerFieldVal.value || !pickerProductConfig.value) {
|
||
uni.showToast({ title: '请选择报告类型', icon: 'none' })
|
||
return
|
||
}
|
||
const priceNum = Number(clientPrice.value)
|
||
if (isNaN(priceNum) || priceNum <= 0) {
|
||
uni.showToast({ title: '请输入有效的查询价格', icon: 'none' })
|
||
return
|
||
}
|
||
const minPrice = Number(pickerProductConfig.value.price_range_min) || 0
|
||
const maxPrice = Number(pickerProductConfig.value.price_range_max) ?? Infinity
|
||
if (priceNum < minPrice) {
|
||
uni.showToast({ title: `价格不能低于 ${minPrice.toFixed(2)} 元`, icon: 'none' })
|
||
return
|
||
}
|
||
if (priceNum > maxPrice) {
|
||
uni.showToast({ title: `价格不能高于 ${maxPrice.toFixed(2)} 元`, icon: 'none' })
|
||
return
|
||
}
|
||
try {
|
||
const targetPath = '/agent/promotionInquire/'
|
||
const { data, error } = await generateLink({
|
||
product_id: pickerProductConfig.value.product_id,
|
||
set_price: priceNum,
|
||
target_path: targetPath,
|
||
})
|
||
if (data.value && !error.value && data.value.code === 200) {
|
||
fullLink.value = data.value.data?.full_link || ''
|
||
showQRcode.value = true
|
||
} else {
|
||
uni.showToast({ title: data.value?.msg || '生成推广链接失败,请重试', icon: 'none' })
|
||
}
|
||
} catch (err) {
|
||
console.error(err)
|
||
uni.showToast({ title: '生成推广链接失败,请重试', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
function onPriceChange(price) {
|
||
const num = Number(price)
|
||
if (!isNaN(num) && isFinite(num)) clientPrice.value = num
|
||
}
|
||
|
||
function openPricePicker() {
|
||
showPricePicker.value = true
|
||
}
|
||
function onUpdateShowPricePicker(v) {
|
||
showPricePicker.value = !!v
|
||
}
|
||
function onUpdateShowQRcode(v) {
|
||
showQRcode.value = !!v
|
||
}
|
||
|
||
function toExample() {
|
||
if (!currentFeature.value) return
|
||
showFollowPopup.value = true
|
||
}
|
||
function closeFollowPopup() {
|
||
showFollowPopup.value = false
|
||
}
|
||
|
||
onLoad((options) => {
|
||
currentFeature.value = options?.feature || ''
|
||
})
|
||
|
||
watch(
|
||
() => currentFeature.value,
|
||
() => {
|
||
if (currentFeature.value) getPromoteConfig()
|
||
},
|
||
{ immediate: false },
|
||
)
|
||
|
||
onMounted(() => {
|
||
getPromoteConfig()
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<view class="min-h-screen promote bg-gray-100">
|
||
<view class="promote-header-bg">
|
||
<image src="/static/promote/promote_bg.jpg" class="bg-image" mode="aspectFill" />
|
||
</view>
|
||
<view class="px-4 pb-6">
|
||
<view class="card card-with-header rounded-2xl bg-white shadow-lg p-4 -mt-12 relative">
|
||
<view class="report-header-elegant">
|
||
<view class="report-header-content">
|
||
<view v-if="currentLogo" class="report-logo-wrapper">
|
||
<image :src="currentLogo" class="report-logo-elegant" mode="aspectFit" />
|
||
</view>
|
||
<view class="report-info-elegant">
|
||
<view class="report-label-elegant">推广报告</view>
|
||
<view class="report-title-elegant">{{ pickerFieldText || '请选择报告类型' }}</view>
|
||
</view>
|
||
</view>
|
||
<picker
|
||
v-if="reportTypes.length"
|
||
mode="selector"
|
||
:range="reportTypes"
|
||
range-key="text"
|
||
:value="selectedReportTypeIndex"
|
||
@change="onTypePickerChange"
|
||
>
|
||
<view class="report-action-elegant">
|
||
<text class="report-action-text">切换</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<view class="pt-6 space-y-4">
|
||
<view class="flex items-center justify-between py-3 border-b border-gray-100">
|
||
<text class="font-medium text-gray-700">客户查询价</text>
|
||
<text class="text-lg font-semibold text-orange-500">¥{{ clientPrice != null ? clientPrice : '0.00' }}</text>
|
||
</view>
|
||
<view class="rounded-xl bg-gray-50 p-3 space-y-2">
|
||
<view class="flex justify-between">
|
||
<text class="text-sm text-gray-600">底价成本</text>
|
||
<text class="text-sm font-semibold text-orange-500">¥{{ baseCost }}</text>
|
||
</view>
|
||
<view class="flex justify-between">
|
||
<text class="text-sm text-gray-600">提价成本</text>
|
||
<text class="text-sm font-semibold text-orange-500">¥{{ raiseCost }}</text>
|
||
</view>
|
||
<view class="flex justify-between">
|
||
<text class="text-sm text-gray-600">推广收益</text>
|
||
<text class="text-sm font-semibold text-orange-500">¥{{ promotionRevenue }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="grid grid-cols-3 gap-3 mt-4">
|
||
<wd-button type="primary" size="large" @click="openPricePicker">设置价格</wd-button>
|
||
<wd-button
|
||
type="primary"
|
||
size="large"
|
||
:disabled="!clientPrice || !currentFeature"
|
||
@click="generatePromotionCode"
|
||
>
|
||
推广报告
|
||
</wd-button>
|
||
<wd-button type="default" size="large" :disabled="!currentFeature" @click="toExample">示例报告</wd-button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="featureData.product_name" class="card mt-4 rounded-2xl bg-white shadow-lg p-4">
|
||
<ReportFeatures :features="featureData.features || []" />
|
||
</view>
|
||
|
||
</view>
|
||
|
||
<PriceInputPopup
|
||
:show="showPricePicker"
|
||
:default-price="clientPrice"
|
||
:product-config="pickerProductConfigForPopup"
|
||
@update:show="onUpdateShowPricePicker"
|
||
@change="onPriceChange"
|
||
/>
|
||
<QRcode :show="showQRcode" :full-link="fullLink" @update:show="onUpdateShowQRcode" />
|
||
|
||
<!-- 示例报告 - 关注公众号弹窗 -->
|
||
<view v-if="showFollowPopup" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50" @click.self="closeFollowPopup">
|
||
<view class="mx-4 rounded-2xl bg-white p-6 shadow-xl" @click.stop>
|
||
<view class="text-center text-lg font-bold text-gray-800 mb-2">查看示例报告</view>
|
||
<view class="text-center text-sm text-gray-500 mb-4">请长按识别下方公众号二维码,关注后获取示例报告</view>
|
||
<image
|
||
src="/static/qrcode/ycc_qrcode.jpg"
|
||
class="w-56 h-56 mx-auto block rounded-lg bg-gray-100"
|
||
mode="aspectFit"
|
||
show-menu-by-longpress
|
||
/>
|
||
<button
|
||
class="mt-4 w-full rounded-full py-2.5 text-base font-medium text-white bg-blue-500"
|
||
@click="closeFollowPopup"
|
||
>
|
||
关闭
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.promote-header-bg {
|
||
width: 100%;
|
||
height: 200rpx;
|
||
overflow: hidden;
|
||
}
|
||
.bg-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
.report-header-elegant {
|
||
position: absolute;
|
||
top: -2.5rem;
|
||
left: 1rem;
|
||
right: 1rem;
|
||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.98), rgba(249, 250, 251, 0.98));
|
||
border-radius: 1.25rem;
|
||
padding: 1rem 1.5rem;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
z-index: 10;
|
||
}
|
||
.report-header-content {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 1rem;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
.report-logo-wrapper {
|
||
width: 72rpx;
|
||
height: 72rpx;
|
||
border-radius: 1rem;
|
||
overflow: hidden;
|
||
flex-shrink: 0;
|
||
}
|
||
.report-logo-elegant {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
.report-info-elegant {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
.report-label-elegant {
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
color: #6b7280;
|
||
margin-bottom: 4rpx;
|
||
}
|
||
.report-title-elegant {
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #111827;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.report-action-elegant {
|
||
padding: 0 14rpx;
|
||
height: 72rpx;
|
||
border-radius: 12rpx;
|
||
background: linear-gradient(135deg, #64b5f6, #42a5f5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.report-action-text {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #fff;
|
||
}
|
||
</style>
|