Files
bdrp-app/src/pages/promote.vue

234 lines
7.5 KiB
Vue
Raw Normal View History

2026-04-20 16:42:28 +08:00
<script setup>
import PriceInputPopup from '@/components/PriceInputPopup.vue'
import VipBanner from '@/components/VipBanner.vue'
2026-04-27 14:48:54 +08:00
import { calculatePromotionPricing, safeTruncate } from '@/utils/promotionPricing'
2026-04-20 16:42:28 +08:00
definePage({ layout: 'default' })
const showPricePicker = ref(false)
const pickerProductConfig = ref(null)
const productConfig = ref(null)
const linkIdentifier = ref('')
const showQRcode = ref(false)
const promotionForm = ref(null)
const loadingConfig = ref(false)
const generating = ref(false)
const formData = ref({
productType: '',
clientPrice: null,
})
2026-06-29 14:09:46 +08:00
/** 从首页报告卡片带入的 product_en配置加载后预选 */
const initialFeature = ref('')
2026-04-20 16:42:28 +08:00
const availableReportTypes = computed(() => {
if (!productConfig.value?.length)
return []
2026-04-28 16:37:10 +08:00
return [...productConfig.value]
.sort((a, b) => Number(a.product_id) - Number(b.product_id))
2026-04-20 16:42:28 +08:00
.map(item => ({
id: item.product_id,
label: item.product_name || `产品${item.product_id}`,
value: item.product_en || '',
}))
.filter(item => !!item.value)
})
2026-04-27 14:48:54 +08:00
const pricingResult = computed(() => {
return calculatePromotionPricing(formData.value.clientPrice, pickerProductConfig.value)
2026-04-20 16:42:28 +08:00
})
2026-04-27 14:48:54 +08:00
const costPrice = computed(() => pricingResult.value.costPrice)
const promotionRevenue = computed(() => pricingResult.value.promotionRevenue)
2026-04-20 16:42:28 +08:00
function selectProductType(reportTypeValue) {
const reportType = availableReportTypes.value.find(item => item.id === reportTypeValue || item.value === reportTypeValue)
if (!reportType)
return
formData.value.productType = reportType.value
const matchedConfig = productConfig.value?.find(item => item.product_id === reportType.id)
if (!matchedConfig) {
pickerProductConfig.value = null
formData.value.clientPrice = null
uni.showToast({ title: '该报告暂不可推广', icon: 'none' })
return
}
pickerProductConfig.value = matchedConfig
formData.value.clientPrice = matchedConfig.cost_price
}
function onConfirmType(e) {
2026-04-23 14:57:35 +08:00
// 单列 wd-picker 的 e.value 为标量;多列时为数组。不能用 e.value[0] 取标量,否则字符串会变成首字符。
const raw = e?.value
const nextValue = Array.isArray(raw) ? raw[0] : raw
const fromItem = e?.selectedItems
const resolved = nextValue ?? (Array.isArray(fromItem) ? fromItem[0]?.value : fromItem?.value)
if (resolved != null && resolved !== '')
selectProductType(resolved)
2026-04-20 16:42:28 +08:00
}
function onPriceChange(price) {
formData.value.clientPrice = price
}
function getPricePayload() {
return safeTruncate(Number(formData.value.clientPrice))
}
function openPricePicker() {
if (!pickerProductConfig.value) {
uni.showToast({ title: '请先选择报告类型', icon: 'none' })
return
}
showPricePicker.value = true
}
2026-06-29 14:09:46 +08:00
function toExampleReport() {
const feature = formData.value.productType
if (!feature) {
uni.showToast({ title: '请先选择报告类型', icon: 'none' })
return
}
uni.navigateTo({
url: `/pages/report-example-webview?feature=${encodeURIComponent(feature)}`,
})
}
2026-04-20 16:42:28 +08:00
async function getPromoteConfig() {
loadingConfig.value = true
try {
2026-04-23 14:57:35 +08:00
const { data, error } = await useApiFetch('/agent/product_config', { silent: true }).get().json()
2026-04-20 16:42:28 +08:00
if (data.value && !error.value && data.value.code === 200) {
const list = data.value.data.AgentProductConfig || []
productConfig.value = list
2026-06-29 14:09:46 +08:00
const preset = initialFeature.value
? availableReportTypes.value.find(item => item.value === initialFeature.value)
: null
const availableType = preset || availableReportTypes.value[0]
2026-04-20 16:42:28 +08:00
if (availableType) {
selectProductType(availableType.value)
}
else {
pickerProductConfig.value = null
formData.value.productType = ''
formData.value.clientPrice = null
uni.showToast({ title: '暂无可推广产品', icon: 'none' })
}
return
}
uni.showToast({ title: data.value?.msg || '获取配置失败', icon: 'none' })
}
catch {
uni.showToast({ title: '获取配置失败', icon: 'none' })
}
finally {
loadingConfig.value = false
}
}
async function generatePromotionCode() {
if (generating.value)
return
try {
await promotionForm.value.validate()
}
catch {
return
}
generating.value = true
try {
const { data, error } = await useApiFetch('/agent/generating_link')
.post({ product: formData.value.productType, price: getPricePayload() })
.json()
if (data.value && !error.value && data.value.code === 200) {
linkIdentifier.value = data.value.data.link_identifier
showQRcode.value = true
}
else {
uni.showToast({ title: data.value?.msg || '生成推广码失败', icon: 'none' })
}
}
catch {
uni.showToast({ title: '生成推广码失败', icon: 'none' })
}
finally {
generating.value = false
}
}
2026-06-29 14:09:46 +08:00
onLoad((options) => {
const feature = options?.feature
if (feature)
initialFeature.value = decodeURIComponent(String(feature))
})
2026-04-20 16:42:28 +08:00
onMounted(() => {
getPromoteConfig()
})
</script>
<template>
<view class="promote min-h-screen p-4">
<view class="card mb-4 from-orange-200 to-orange-200/80 !bg-gradient-to-b">
<view class="text-lg text-orange-500 font-bold">
直推用户查询
</view>
<view class="mt-1 text-orange-400 font-bold">
自定义价格赚取差价
</view>
<view class="mt-6 rounded-xl bg-orange-100 px-4 py-2 text-gray-600">
在下方 自定义价格 处选择报告类型设置客户查询价即可立即推广
</view>
</view>
<VipBanner />
2026-06-29 14:09:46 +08:00
<view class="card mb-4 relative">
<text
class="absolute right-4 top-4 z-10 text-sm text-blue-600 active:opacity-70"
@click="toExampleReport"
>
示例报告
</text>
<view class="mb-2 pr-20 text-xl font-semibold">
2026-04-20 16:42:28 +08:00
生成推广码
</view>
<wd-form ref="promotionForm" :model="formData">
<wd-cell-group border>
<wd-picker v-model="formData.productType" label="报告类型" label-width="100px" title="选择报告类型"
:columns="[availableReportTypes]" prop="productType" placeholder="请选择报告类型"
:rules="[{ required: true, message: '请选择报告类型' }]" @confirm="onConfirmType" />
<wd-input v-model="formData.clientPrice" label="客户查询价" label-width="100px" placeholder="请输入价格"
prop="clientPrice" clickable readonly suffix-icon="arrow-right"
:rules="[{ required: true, message: '请输入客户查询价' }]" @click="openPricePicker" />
</wd-cell-group>
<view class="my-2 flex items-center justify-between text-sm text-gray-500">
<view>
推广收益为 <text class="text-orange-500">
{{ promotionRevenue }}
</text>
</view>
<view>
我的成本为 <text class="text-orange-500">
{{ costPrice }}
</text>
</view>
</view>
</wd-form>
<wd-button type="primary" block :loading="generating || loadingConfig"
:disabled="loadingConfig || !pickerProductConfig" @click="generatePromotionCode">
点击立即推广
</wd-button>
</view>
<PriceInputPopup v-model:show="showPricePicker" :default-price="formData.clientPrice"
:product-config="pickerProductConfig" @change="onPriceChange" />
<QRcode v-model:show="showQRcode" :link-identifier="linkIdentifier" />
</view>
</template>
<style scoped></style>