f
This commit is contained in:
12
src/auto-imports.d.ts
vendored
12
src/auto-imports.d.ts
vendored
@@ -10,8 +10,10 @@ declare global {
|
||||
const ZoomAdapter: typeof import('./utils/zoomAdapter.js')['ZoomAdapter']
|
||||
const aesDecrypt: typeof import('./utils/crypto.js')['aesDecrypt']
|
||||
const aesEncrypt: typeof import('./utils/crypto.js')['aesEncrypt']
|
||||
const appendAppAuthQueryParams: typeof import('./composables/useReportWebview')['appendAppAuthQueryParams']
|
||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||
const buildAgentVipApplyH5Url: typeof import('./composables/useAgentVipH5')['buildAgentVipApplyH5Url']
|
||||
const calculatePromotionPricing: typeof import('./utils/promotionPricing.js')['calculatePromotionPricing']
|
||||
const chatCrypto: typeof import('./utils/chatCrypto.js')['default']
|
||||
const chatEncrypt: typeof import('./utils/chatEncrypt.js')['default']
|
||||
@@ -78,6 +80,7 @@ declare global {
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
const navigateLogin: typeof import('./utils/navigate')['navigateLogin']
|
||||
const navigateTo: typeof import('./utils/navigate')['navigateTo']
|
||||
const navigateToAgentVipApplyH5: typeof import('./composables/useAgentVipH5')['navigateToAgentVipApplyH5']
|
||||
const nextTick: typeof import('vue')['nextTick']
|
||||
const onActivated: typeof import('vue')['onActivated']
|
||||
const onAddToFavorites: typeof import('@dcloudio/uni-app')['onAddToFavorites']
|
||||
@@ -122,7 +125,9 @@ declare global {
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
|
||||
const openAgentVipApplyInExternalBrowser: typeof import('./composables/useAgentVipH5')['openAgentVipApplyInExternalBrowser']
|
||||
const openCustomerService: typeof import('./composables/useCustomerService')['openCustomerService']
|
||||
const openExternalUrl: typeof import('./composables/openExternalUrl')['openExternalUrl']
|
||||
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const provideLocal: typeof import('@vueuse/core')['provideLocal']
|
||||
@@ -156,6 +161,7 @@ declare global {
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const shouldSilencePrivacyBlockedRequest: typeof import('./composables/usePrivacyConsent')['shouldSilencePrivacyBlockedRequest']
|
||||
const syncRef: typeof import('@vueuse/core')['syncRef']
|
||||
const syncRefs: typeof import('@vueuse/core')['syncRefs']
|
||||
const templateRef: typeof import('@vueuse/core')['templateRef']
|
||||
@@ -408,8 +414,10 @@ declare module 'vue' {
|
||||
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
|
||||
readonly aesDecrypt: UnwrapRef<typeof import('./utils/crypto.js')['aesDecrypt']>
|
||||
readonly aesEncrypt: UnwrapRef<typeof import('./utils/crypto.js')['aesEncrypt']>
|
||||
readonly appendAppAuthQueryParams: UnwrapRef<typeof import('./composables/useReportWebview')['appendAppAuthQueryParams']>
|
||||
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
|
||||
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
|
||||
readonly buildAgentVipApplyH5Url: UnwrapRef<typeof import('./composables/useAgentVipH5')['buildAgentVipApplyH5Url']>
|
||||
readonly calculatePromotionPricing: UnwrapRef<typeof import('./utils/promotionPricing.js')['calculatePromotionPricing']>
|
||||
readonly clearAuthStorage: UnwrapRef<typeof import('./utils/storage')['clearAuthStorage']>
|
||||
readonly clearToken: UnwrapRef<typeof import('./utils/storage')['clearToken']>
|
||||
@@ -474,6 +482,7 @@ declare module 'vue' {
|
||||
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
|
||||
readonly navigateLogin: UnwrapRef<typeof import('./utils/navigate')['navigateLogin']>
|
||||
readonly navigateTo: UnwrapRef<typeof import('./utils/navigate')['navigateTo']>
|
||||
readonly navigateToAgentVipApplyH5: UnwrapRef<typeof import('./composables/useAgentVipH5')['navigateToAgentVipApplyH5']>
|
||||
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
|
||||
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
|
||||
readonly onAddToFavorites: UnwrapRef<typeof import('@dcloudio/uni-app')['onAddToFavorites']>
|
||||
@@ -518,7 +527,9 @@ declare module 'vue' {
|
||||
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
|
||||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||
readonly onWatcherCleanup: UnwrapRef<typeof import('vue')['onWatcherCleanup']>
|
||||
readonly openAgentVipApplyInExternalBrowser: UnwrapRef<typeof import('./composables/useAgentVipH5')['openAgentVipApplyInExternalBrowser']>
|
||||
readonly openCustomerService: UnwrapRef<typeof import('./composables/useCustomerService')['openCustomerService']>
|
||||
readonly openExternalUrl: UnwrapRef<typeof import('./composables/openExternalUrl')['openExternalUrl']>
|
||||
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
||||
@@ -552,6 +563,7 @@ declare module 'vue' {
|
||||
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
|
||||
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
||||
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
|
||||
readonly shouldSilencePrivacyBlockedRequest: UnwrapRef<typeof import('./composables/usePrivacyConsent')['shouldSilencePrivacyBlockedRequest']>
|
||||
readonly syncRef: UnwrapRef<typeof import('@vueuse/core')['syncRef']>
|
||||
readonly syncRefs: UnwrapRef<typeof import('@vueuse/core')['syncRefs']>
|
||||
readonly templateRef: UnwrapRef<typeof import('@vueuse/core')['templateRef']>
|
||||
|
||||
1
src/components.d.ts
vendored
1
src/components.d.ts
vendored
@@ -12,7 +12,6 @@ declare module 'vue' {
|
||||
BindPhoneDialog: typeof import('./components/BindPhoneDialog.vue')['default']
|
||||
EmptyState: typeof import('./components/EmptyState.vue')['default']
|
||||
ImageSaveGuide: typeof import('./components/ImageSaveGuide.vue')['default']
|
||||
InquireForm: typeof import('./components/InquireForm.vue')['default']
|
||||
LoginDialog: typeof import('./components/LoginDialog.vue')['default']
|
||||
Payment: typeof import('./components/Payment.vue')['default']
|
||||
PriceInputPopup: typeof import('./components/PriceInputPopup.vue')['default']
|
||||
|
||||
@@ -1,884 +0,0 @@
|
||||
<script setup>
|
||||
import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'
|
||||
import BindPhoneDialog from '@/components/BindPhoneDialog.vue'
|
||||
import LoginDialog from '@/components/LoginDialog.vue'
|
||||
import Payment from '@/components/Payment.vue'
|
||||
import SectionTitle from '@/components/SectionTitle.vue'
|
||||
import { useRouter } from '@/composables/uni-router'
|
||||
import { useAppConfig } from '@/composables/useAppConfig'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
import { useUserStore } from '@/stores/userStore'
|
||||
import { aesEncrypt } from '@/utils/crypto'
|
||||
import { setAuthSession } from '@/utils/storage'
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
// 查询类型:'normal' | 'promotion'
|
||||
type: {
|
||||
type: String,
|
||||
default: 'normal',
|
||||
},
|
||||
// 产品特征
|
||||
feature: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
// 推广链接标识符(仅推广查询需要)
|
||||
linkIdentifier: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
// 产品数据(从外部传入)
|
||||
featureData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
})
|
||||
// Emits
|
||||
const emit = defineEmits(['submit-success'])
|
||||
const PRODUCT_BACKGROUND_MAP = {
|
||||
companyinfo: '/static/images/product/xwqy_inquire_bg.png',
|
||||
preloanbackgroundcheck: '/static/images/product/dqfx_inquire_bg.png',
|
||||
personalData: '/static/images/product/grdsj_inquire_bg.png',
|
||||
marriage: '/static/images/product/marriage_inquire_bg.png',
|
||||
homeservice: '/static/images/product/homeservice_inquire_bg.png',
|
||||
backgroundcheck: '/static/images/product/backgroundcheck_inquire_bg.png',
|
||||
rentalinfo: '/static/images/product/rentalinfo_inquire_bg.png',
|
||||
}
|
||||
const TRAPEZOID_BACKGROUND_MAP = {
|
||||
marriage: '/static/images/report/title_inquire_bg_red.png',
|
||||
homeservice: '/static/images/report/title_inquire_bg_green.png',
|
||||
default: '/static/images/report/title_inquire_bg.png',
|
||||
}
|
||||
function showToast(options) {
|
||||
const message = typeof options === 'string' ? options : (options?.message || options?.title || '')
|
||||
if (!message)
|
||||
return
|
||||
uni.showToast({
|
||||
title: message,
|
||||
icon: options?.type === 'success' ? 'success' : 'none',
|
||||
})
|
||||
}
|
||||
|
||||
const { feature } = toRefs(props)
|
||||
|
||||
function loadProductBackground(productType) {
|
||||
return PRODUCT_BACKGROUND_MAP[productType] || ''
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
const dialogStore = useDialogStore()
|
||||
const userStore = useUserStore()
|
||||
const isWeChat = ref(false)
|
||||
const { appConfig, loadAppConfig } = useAppConfig()
|
||||
|
||||
// 响应式数据
|
||||
const showPayment = ref(false)
|
||||
const pendingPayment = ref(false)
|
||||
const queryId = ref(null)
|
||||
const productBackground = ref('')
|
||||
const productMainImage = ref('')
|
||||
const trapezoidBgImage = ref('')
|
||||
const isCountingDown = ref(false)
|
||||
const countdown = ref(60)
|
||||
const verificationCodeInputRef = ref(null)
|
||||
|
||||
// 使用传入的featureData或创建响应式引用
|
||||
const featureData = computed(() => props.featureData || {})
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
name: '',
|
||||
idCard: '',
|
||||
mobile: '',
|
||||
verificationCode: '',
|
||||
agreeToTerms: false,
|
||||
})
|
||||
|
||||
// 计算属性
|
||||
const isPhoneNumberValid = computed(() => {
|
||||
return /^1[3-9]\d{9}$/.test(formData.mobile)
|
||||
})
|
||||
|
||||
const isIdCardValid = computed(() => /^\d{17}[\dX]$/i.test(formData.idCard))
|
||||
|
||||
// 小微企业(companyinfo)暂不需要验证码
|
||||
const needVerificationCode = computed(() => props.feature !== 'companyinfo')
|
||||
|
||||
const isLoggedIn = computed(() => userStore.isLoggedIn)
|
||||
|
||||
const buttonText = computed(() => {
|
||||
return isLoggedIn.value ? '立即查询' : '前往登录'
|
||||
})
|
||||
|
||||
const hasHeroImage = computed(() => Boolean(productBackground.value))
|
||||
const queryRetentionDaysText = computed(() => `${appConfig.value.query.retention_days}天`)
|
||||
|
||||
function loadTrapezoidBackground() {
|
||||
trapezoidBgImage.value = TRAPEZOID_BACKGROUND_MAP[props.feature] || TRAPEZOID_BACKGROUND_MAP.default
|
||||
}
|
||||
|
||||
// 牌匾背景图片样式
|
||||
const trapezoidBgStyle = computed(() => {
|
||||
if (trapezoidBgImage.value) {
|
||||
return {
|
||||
backgroundImage: `url(${trapezoidBgImage.value})`,
|
||||
}
|
||||
}
|
||||
return {}
|
||||
})
|
||||
|
||||
// 牌匾文字样式
|
||||
const trapezoidTextStyle = computed(() => {
|
||||
// homeservice 和 marriage 使用白色文字
|
||||
if (props.feature === 'homeservice' || props.feature === 'marriage') {
|
||||
return {
|
||||
color: 'white',
|
||||
}
|
||||
}
|
||||
// 其他情况使用默认字体色(不设置 color,使用浏览器默认或继承)
|
||||
return {}
|
||||
})
|
||||
|
||||
// 获取功能图标
|
||||
function getFeatureIcon(apiId) {
|
||||
const iconMap = {
|
||||
JRZQ4AA8: '/static/inquire_icons/huankuanyali.svg', // 还款压力
|
||||
QCXG7A2B: '/static/inquire_icons/mingxiacheliang.svg', // 名下车辆
|
||||
BehaviorRiskScan: '/static/inquire_icons/fengxianxingwei.svg', // 风险行为扫描
|
||||
IVYZ5733: '/static/inquire_icons/hunyinzhuangtai.svg', // 婚姻状态
|
||||
PersonEnterprisePro: '/static/inquire_icons/renqiguanxi.svg', // 人企关系加强版
|
||||
JRZQ0A03: '/static/inquire_icons/jiedaishenqing.svg', // 借贷申请记录
|
||||
FLXG3D56: '/static/inquire_icons/jiedaiweiyue.svg', // 借贷违约失信
|
||||
FLXG0V4B: '/static/inquire_icons/sifasheyu.svg', // 司法涉诉
|
||||
JRZQ8203: '/static/inquire_icons/jiedaixingwei.svg', // 借贷行为记录
|
||||
JRZQ09J8: '/static/inquire_icons/beijianguanrenyuan.svg', // 收入评估
|
||||
JRZQ4B6C: '/static/inquire_icons/fengxianxingwei.svg', // 探针C风险评估
|
||||
}
|
||||
return iconMap[apiId] || '/static/inquire_icons/default.svg'
|
||||
}
|
||||
|
||||
// 处理图标加载错误
|
||||
function handleIconError(event) {
|
||||
event.target.style.display = 'none'
|
||||
}
|
||||
|
||||
// 获取卡片样式类
|
||||
function getCardClass(index) {
|
||||
const colorIndex = index % 4
|
||||
const colorClasses = [
|
||||
'bg-gradient-to-br from-blue-50 via-blue-25 to-white border-2 border-blue-200',
|
||||
'bg-gradient-to-br from-green-50 via-green-25 to-white border-2 border-green-200',
|
||||
'bg-gradient-to-br from-purple-50 via-purple-25 to-white border-2 border-purple-200',
|
||||
'bg-gradient-to-br from-orange-50 via-orange-25 to-white border-2 border-orange-200',
|
||||
]
|
||||
return colorClasses[colorIndex]
|
||||
}
|
||||
|
||||
// 方法
|
||||
function validateField(field, value, validationFn, errorMessage) {
|
||||
if (isHasInput(field) && !validationFn(value)) {
|
||||
showToast({ message: errorMessage })
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const defaultInput = ['name', 'idCard', 'mobile', 'verificationCode']
|
||||
function isHasInput(input) {
|
||||
return defaultInput.includes(input)
|
||||
}
|
||||
|
||||
// 处理绑定手机号成功的回调
|
||||
function handleBindSuccess() {
|
||||
if (pendingPayment.value) {
|
||||
pendingPayment.value = false
|
||||
submitRequest()
|
||||
}
|
||||
}
|
||||
|
||||
// 处理登录成功的回调
|
||||
function handleLoginSuccess() {
|
||||
if (pendingPayment.value) {
|
||||
pendingPayment.value = false
|
||||
submitRequest()
|
||||
}
|
||||
}
|
||||
|
||||
// 处理输入框点击事件
|
||||
async function handleInputClick() {
|
||||
if (!isLoggedIn.value) {
|
||||
// 非微信浏览器环境:未登录用户提示打开登录弹窗
|
||||
if (!isWeChat.value) {
|
||||
const { confirm } = await uni.showModal({
|
||||
title: '提示',
|
||||
content: '您需要登录后才能进行查询,是否立即登录?',
|
||||
confirmText: '立即登录',
|
||||
cancelText: '取消',
|
||||
})
|
||||
if (confirm)
|
||||
dialogStore.openLogin()
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 微信浏览器环境:已登录但检查是否需要绑定手机号
|
||||
if (isWeChat.value && !userStore.mobile) {
|
||||
dialogStore.openBindPhone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
// 非微信浏览器环境:检查登录状态
|
||||
if (!isWeChat.value && !isLoggedIn.value) {
|
||||
dialogStore.openLogin()
|
||||
return
|
||||
}
|
||||
|
||||
// 基本协议验证
|
||||
if (!formData.agreeToTerms) {
|
||||
showToast({ message: `请阅读并同意用户协议和隐私政策` })
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
!validateField('name', formData.name, value => value, '请输入姓名')
|
||||
|| !validateField(
|
||||
'mobile',
|
||||
formData.mobile,
|
||||
() => isPhoneNumberValid.value,
|
||||
'请输入有效的手机号',
|
||||
)
|
||||
|| !validateField(
|
||||
'idCard',
|
||||
formData.idCard,
|
||||
() => isIdCardValid.value,
|
||||
'请输入有效的身份证号码',
|
||||
)
|
||||
|| (needVerificationCode.value
|
||||
&& !validateField(
|
||||
'verificationCode',
|
||||
formData.verificationCode,
|
||||
value => value,
|
||||
'请输入验证码',
|
||||
))
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否需要绑定手机号
|
||||
if (!userStore.mobile) {
|
||||
pendingPayment.value = true
|
||||
dialogStore.openBindPhone()
|
||||
}
|
||||
else {
|
||||
submitRequest()
|
||||
}
|
||||
}
|
||||
|
||||
async function submitRequest() {
|
||||
const req = {
|
||||
name: formData.name,
|
||||
id_card: formData.idCard,
|
||||
mobile: formData.mobile,
|
||||
}
|
||||
if (needVerificationCode.value) {
|
||||
req.code = formData.verificationCode
|
||||
}
|
||||
const reqStr = JSON.stringify(req)
|
||||
const inquireKey = import.meta.env.VITE_INQUIRE_AES_KEY
|
||||
if (!inquireKey) {
|
||||
throw new Error('缺少环境变量: VITE_INQUIRE_AES_KEY')
|
||||
}
|
||||
const encodeData = aesEncrypt(reqStr, inquireKey)
|
||||
|
||||
let apiUrl = ''
|
||||
const requestData = { data: encodeData }
|
||||
|
||||
if (props.type === 'promotion') {
|
||||
apiUrl = `/query/service_agent/${props.feature}`
|
||||
requestData.agent_identifier = props.linkIdentifier
|
||||
}
|
||||
else {
|
||||
apiUrl = `/query/service/${props.feature}`
|
||||
}
|
||||
|
||||
const { data } = await useApiFetch(apiUrl)
|
||||
.post(requestData)
|
||||
.json()
|
||||
|
||||
if (data.value.code === 200) {
|
||||
queryId.value = data.value.data.id
|
||||
|
||||
// 推广查询需要保存token
|
||||
if (props.type === 'promotion') {
|
||||
setAuthSession(data.value.data)
|
||||
}
|
||||
|
||||
showPayment.value = true
|
||||
emit('submit-success', data.value.data)
|
||||
}
|
||||
}
|
||||
|
||||
async function sendVerificationCode() {
|
||||
if (isCountingDown.value || !isPhoneNumberValid.value)
|
||||
return
|
||||
if (!isPhoneNumberValid.value) {
|
||||
showToast({ message: '请输入有效的手机号' })
|
||||
return
|
||||
}
|
||||
const { data, error } = await useApiFetch('/auth/sendSms')
|
||||
.post({ mobile: formData.mobile, actionType: 'query', captchaVerifyParam: '' })
|
||||
.json()
|
||||
if (!error.value && data.value?.code === 200) {
|
||||
showToast({ message: '验证码发送成功', type: 'success' })
|
||||
startCountdown()
|
||||
nextTick(() => {
|
||||
verificationCodeInputRef.value?.focus?.()
|
||||
})
|
||||
}
|
||||
else {
|
||||
showToast({ message: data.value?.msg || '验证码发送失败,请重试' })
|
||||
}
|
||||
}
|
||||
|
||||
let timer = null
|
||||
|
||||
function startCountdown() {
|
||||
isCountingDown.value = true
|
||||
countdown.value = 60
|
||||
timer = setInterval(() => {
|
||||
if (countdown.value > 0) {
|
||||
countdown.value--
|
||||
}
|
||||
else {
|
||||
clearInterval(timer)
|
||||
isCountingDown.value = false
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
function toUserAgreement() {
|
||||
router.push(`/userAgreement`)
|
||||
}
|
||||
|
||||
function toPrivacyPolicy() {
|
||||
router.push(`/privacyPolicy`)
|
||||
}
|
||||
|
||||
function toAuthorization() {
|
||||
router.push(`/authorization`)
|
||||
}
|
||||
|
||||
function toExample() {
|
||||
router.push(`/example?feature=${props.feature}`)
|
||||
}
|
||||
|
||||
function toHistory() {
|
||||
router.push('/historyQuery')
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(async () => {
|
||||
loadBackgroundImage()
|
||||
loadTrapezoidBackground()
|
||||
await loadAppConfig()
|
||||
})
|
||||
|
||||
// 加载背景图片
|
||||
async function loadBackgroundImage() {
|
||||
productBackground.value = loadProductBackground(props.feature)
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
}
|
||||
})
|
||||
watch(feature, async () => {
|
||||
loadBackgroundImage()
|
||||
loadTrapezoidBackground()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="inquire-bg relative min-h-screen">
|
||||
<view v-if="hasHeroImage" class="hero-banner">
|
||||
<image :src="productBackground" class="hero-banner-image" mode="widthFix" />
|
||||
<view class="hero-banner-mask" />
|
||||
<view class="hero-badge-wrap">
|
||||
<view class="trapezoid-bg-image flex items-center justify-center" :style="trapezoidBgStyle">
|
||||
<view class="whitespace-nowrap text-xl" :style="trapezoidTextStyle">
|
||||
{{ featureData.product_name }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="content-wrap relative mx-4 min-h-screen pb-12" :class="{ 'with-hero': hasHeroImage }">
|
||||
<view class="card-container">
|
||||
<!-- 基本信息标题 -->
|
||||
<view class="mb-6 flex items-center">
|
||||
<SectionTitle title="基本信息" />
|
||||
<view class="ml-auto flex cursor-pointer items-center text-gray-600" @click="toExample">
|
||||
<image src="/static/images/report/slbg_inquire_icon.png" alt="示例报告" class="mr-1 h-4 w-4" />
|
||||
<text class="">
|
||||
示例报告
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 表单输入区域 -->
|
||||
<view class="mb-6 space-y-4">
|
||||
<view class="flex items-center border-b border-gray-100 py-3">
|
||||
<text class="w-20 text-gray-700 font-medium">
|
||||
姓名
|
||||
</text>
|
||||
<wd-input
|
||||
v-model="formData.name"
|
||||
class="inquire-wd-input flex-1"
|
||||
type="text"
|
||||
placeholder="请输入正确的姓名"
|
||||
no-border
|
||||
clearable
|
||||
@focus="handleInputClick"
|
||||
/>
|
||||
</view>
|
||||
<view class="flex items-center border-b border-gray-100 py-3">
|
||||
<text class="w-20 text-gray-700 font-medium">
|
||||
身份证号
|
||||
</text>
|
||||
<wd-input
|
||||
v-model="formData.idCard"
|
||||
class="inquire-wd-input flex-1"
|
||||
type="text"
|
||||
placeholder="请输入准确的身份证号"
|
||||
no-border
|
||||
clearable
|
||||
@focus="handleInputClick"
|
||||
/>
|
||||
</view>
|
||||
<view class="flex items-center border-b border-gray-100 py-3">
|
||||
<text class="w-20 text-gray-700 font-medium">
|
||||
手机号
|
||||
</text>
|
||||
<wd-input
|
||||
v-model="formData.mobile"
|
||||
class="inquire-wd-input flex-1"
|
||||
type="number"
|
||||
placeholder="请输入手机号"
|
||||
maxlength="11"
|
||||
no-border
|
||||
clearable
|
||||
@focus="handleInputClick"
|
||||
/>
|
||||
</view>
|
||||
<!-- 小微企业(companyinfo)暂不展示验证码 -->
|
||||
<view v-if="needVerificationCode" class="flex items-center border-b border-gray-100 py-3">
|
||||
<text class="w-20 text-gray-700 font-medium">
|
||||
验证码
|
||||
</text>
|
||||
<wd-input
|
||||
ref="verificationCodeInputRef"
|
||||
v-model="formData.verificationCode"
|
||||
class="inquire-wd-input flex-1"
|
||||
placeholder="请输入验证码"
|
||||
maxlength="6"
|
||||
no-border
|
||||
clearable
|
||||
@focus="handleInputClick"
|
||||
>
|
||||
<template #suffix>
|
||||
<wd-button
|
||||
class="captcha-wd-btn"
|
||||
size="small"
|
||||
type="primary"
|
||||
plain
|
||||
:disabled="isCountingDown || !isPhoneNumberValid"
|
||||
@click="sendVerificationCode"
|
||||
>
|
||||
{{ isCountingDown ? `${countdown}s` : '获取验证码' }}
|
||||
</wd-button>
|
||||
</template>
|
||||
</wd-input>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 协议同意 -->
|
||||
<view class="agreement-wrap mb-6">
|
||||
<wd-checkbox v-model="formData.agreeToTerms" shape="square" size="18px" />
|
||||
<view class="agreement-text">
|
||||
<text class="text-sm text-gray-500">
|
||||
我已阅读并同意
|
||||
</text>
|
||||
<text class="agreement-link" @click="toUserAgreement">
|
||||
《用户协议》
|
||||
</text>
|
||||
<text class="agreement-link" @click="toPrivacyPolicy">
|
||||
《隐私政策》
|
||||
</text>
|
||||
<text class="agreement-link" @click="toAuthorization">
|
||||
《授权书》
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 查询按钮 -->
|
||||
<wd-button class="submit-wd-btn mb-4 mt-10" block type="primary" @click="handleSubmit">
|
||||
<view class="w-full flex items-center justify-center">
|
||||
<text>{{ buttonText }}</text>
|
||||
<text class="ml-4">
|
||||
¥{{ featureData.sell_price }}
|
||||
</text>
|
||||
</view>
|
||||
</wd-button>
|
||||
<!-- <view class="text-xs text-gray-500 leading-relaxed mt-8" v-html="featureData.description">
|
||||
</view> -->
|
||||
<!-- 免责声明 -->
|
||||
<view class="mt-2 text-center text-xs text-gray-500 leading-relaxed">
|
||||
为保证用户的隐私及数据安全,查询结果生成{{ queryRetentionDaysText }}后将自动删除
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 报告包含内容 -->
|
||||
<view v-if="featureData.features && featureData.features.length > 0" class="card mt-3">
|
||||
<view class="mb-3 flex items-center text-base font-semibold" style="color: var(--color-text-primary);">
|
||||
<view class="mr-2 h-5 w-1 rounded-full"
|
||||
style="background: linear-gradient(to bottom, var(--color-primary), var(--color-primary-700));" />
|
||||
报告包含内容
|
||||
</view>
|
||||
<view class="grid grid-cols-4 items-stretch gap-2">
|
||||
<template v-for="(item, index) in featureData.features" :key="item.id">
|
||||
<!-- FLXG0V4B 特殊处理:显示8个独立的案件类型 -->
|
||||
<template v-if="item.api_id === 'FLXG0V4B'">
|
||||
<view v-for="(caseType, caseIndex) in [
|
||||
{ name: '管辖案件', icon: 'beijianguanrenyuan.svg' },
|
||||
{ name: '刑事案件', icon: 'xingshi.svg' },
|
||||
{ name: '民事案件', icon: 'minshianjianguanli.svg' },
|
||||
{ name: '失信被执行', icon: 'shixinren.svg' },
|
||||
{ name: '行政案件', icon: 'xingzhengfuwu.svg' },
|
||||
{ name: '赔偿案件', icon: 'yuepeichang.svg' },
|
||||
{ name: '执行案件', icon: 'zhixinganjian.svg' },
|
||||
{ name: '限高被执行', icon: 'xianzhigaoxiaofei.svg' },
|
||||
]" :key="`${item.id}-${caseIndex}`"
|
||||
class="h-full min-h-0 w-full flex flex-col items-center rounded-xl p-2 text-center text-sm text-gray-700 font-medium shadow-lg"
|
||||
:class="getCardClass(index + caseIndex)">
|
||||
<view class="mb-1 shrink-0">
|
||||
<image :src="`/static/inquire_icons/${caseType.icon}`" :alt="caseType.name"
|
||||
class="mx-auto h-6 w-6 drop-shadow-sm" @error="handleIconError" />
|
||||
</view>
|
||||
<view
|
||||
class="w-full flex flex-1 items-center justify-center break-all px-0.5 text-xs font-medium leading-snug">
|
||||
{{ caseType.name }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- DWBG8B4D 特殊处理:显示拆分模块 -->
|
||||
<template v-else-if="item.api_id === 'DWBG8B4D'">
|
||||
<view v-for="(module, moduleIndex) in [
|
||||
{ name: '要素核查', icon: 'beijianguanrenyuan.svg' },
|
||||
{ name: '运营商核验', icon: 'mingxiacheliang.svg' },
|
||||
{ name: '公安重点人员检验', icon: 'xingshi.svg' },
|
||||
{ name: '逾期风险综述', icon: 'huankuanyali.svg' },
|
||||
{ name: '法院曝光台信息', icon: 'sifasheyu.svg' },
|
||||
{ name: '借贷评估', icon: 'jiedaishenqing.svg' },
|
||||
{ name: '租赁风险评估', icon: 'jiedaixingwei.svg' },
|
||||
{ name: '关联风险监督', icon: 'renqiguanxi.svg' },
|
||||
{ name: '规则风险提示', icon: 'fengxianxingwei.svg' },
|
||||
]" :key="`${item.id}-${moduleIndex}`"
|
||||
class="h-full min-h-0 w-full flex flex-col items-center rounded-xl p-2 text-center text-sm text-gray-700 font-medium shadow-lg"
|
||||
:class="getCardClass(index + moduleIndex)">
|
||||
<view class="mb-1 flex shrink-0 items-center justify-center text-xl">
|
||||
<image :src="`/static/inquire_icons/${module.icon}`" :alt="module.name" class="h-6 w-6 drop-shadow-sm"
|
||||
@error="handleIconError" />
|
||||
</view>
|
||||
<view
|
||||
class="w-full flex flex-1 items-center justify-center break-all px-1 text-xs font-medium leading-snug">
|
||||
{{ module.name }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- CJRZQ5E9F 特殊处理:显示拆分模块 -->
|
||||
<template v-else-if="item.api_id === 'JRZQ5E9F'">
|
||||
<view v-for="(module, moduleIndex) in [
|
||||
{ name: '信用评分', icon: 'huankuanyali.svg' },
|
||||
{ name: '贷款行为分析', icon: 'jiedaixingwei.svg' },
|
||||
{ name: '机构分析', icon: 'jiedaishenqing.svg' },
|
||||
{ name: '时间趋势分析', icon: 'zhixinganjian.svg' },
|
||||
{ name: '风险指标详情', icon: 'fengxianxingwei.svg' },
|
||||
{ name: '专业建议', icon: 'yuepeichang.svg' },
|
||||
]" :key="`${item.id}-${moduleIndex}`"
|
||||
class="h-full min-h-0 w-full flex flex-col items-center rounded-xl p-2 text-center text-sm text-gray-700 font-medium shadow-lg"
|
||||
:class="getCardClass(index + moduleIndex)">
|
||||
<view class="mb-1 flex shrink-0 items-center justify-center text-xl">
|
||||
<image :src="`/static/inquire_icons/${module.icon}`" :alt="module.name" class="h-6 w-6 drop-shadow-sm"
|
||||
@error="handleIconError" />
|
||||
</view>
|
||||
<view
|
||||
class="w-full flex flex-1 items-center justify-center break-all px-1 text-xs font-medium leading-snug">
|
||||
{{ module.name }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- PersonEnterprisePro/CQYGL3F8E 特殊处理:显示拆分模块 -->
|
||||
<template v-else-if="item.api_id === 'PersonEnterprisePro' || item.api_id === 'QYGL3F8E'">
|
||||
<view v-for="(module, moduleIndex) in [
|
||||
{ name: '投资企业记录', icon: 'renqiguanxi.svg' },
|
||||
{ name: '高管任职记录', icon: 'beijianguanrenyuan.svg' },
|
||||
{ name: '涉诉风险', icon: 'sifasheyu.svg' },
|
||||
{ name: '对外投资历史', icon: 'renqiguanxi.svg' },
|
||||
{ name: '融资历史', icon: 'huankuanyali.svg' },
|
||||
{ name: '行政处罚', icon: 'xingzhengfuwu.svg' },
|
||||
{ name: '经营异常', icon: 'fengxianxingwei.svg' },
|
||||
]" :key="`${item.id}-${moduleIndex}`"
|
||||
class="h-full min-h-0 w-full flex flex-col items-center rounded-xl p-2 text-center text-sm text-gray-700 font-medium shadow-lg"
|
||||
:class="getCardClass(index + moduleIndex)">
|
||||
<view class="mb-1 flex shrink-0 items-center justify-center text-xl">
|
||||
<image :src="`/static/inquire_icons/${module.icon}`" :alt="module.name" class="h-6 w-6 drop-shadow-sm"
|
||||
@error="handleIconError" />
|
||||
</view>
|
||||
<view
|
||||
class="w-full flex flex-1 items-center justify-center break-all px-1 text-xs font-medium leading-snug">
|
||||
{{ module.name }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- DWBG6A2C 特殊处理:显示拆分模块 -->
|
||||
<template v-else-if="item.api_id === 'DWBG6A2C'">
|
||||
<view v-for="(module, moduleIndex) in [
|
||||
{ name: '命中风险标注', icon: 'fengxianxingwei.svg' },
|
||||
{ name: '公安重点人员核验', icon: 'beijianguanrenyuan.svg' },
|
||||
{ name: '涉赌涉诈人员核验', icon: 'xingshi.svg' },
|
||||
{ name: '风险名单', icon: 'jiedaiweiyue.svg' },
|
||||
{ name: '历史借贷行为', icon: 'jiedaixingwei.svg' },
|
||||
{ name: '近24个月放款情况', icon: 'jiedaishenqing.svg' },
|
||||
{ name: '履约情况', icon: 'huankuanyali.svg' },
|
||||
{ name: '历史逾期记录', icon: 'jiedaiweiyue.svg' },
|
||||
{ name: '授信详情', icon: 'huankuanyali.svg' },
|
||||
{ name: '租赁行为', icon: 'mingxiacheliang.svg' },
|
||||
{ name: '关联风险监督', icon: 'renqiguanxi.svg' },
|
||||
{ name: '法院风险信息', icon: 'sifasheyu.svg' },
|
||||
]" :key="`${item.id}-${moduleIndex}`"
|
||||
class="h-full min-h-0 w-full flex flex-col items-center rounded-xl p-2 text-center text-sm text-gray-700 font-medium shadow-lg"
|
||||
:class="getCardClass(index + moduleIndex)">
|
||||
<view class="mb-1 flex shrink-0 items-center justify-center text-xl">
|
||||
<image :src="`/static/inquire_icons/${module.icon}`" :alt="module.name" class="h-6 w-6 drop-shadow-sm"
|
||||
@error="handleIconError" />
|
||||
</view>
|
||||
<view
|
||||
class="w-full flex flex-1 items-center justify-center break-all px-1 text-xs font-medium leading-snug">
|
||||
{{ module.name }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 其他功能正常显示 -->
|
||||
<view v-else
|
||||
class="h-full min-h-0 w-full flex flex-col items-center rounded-xl p-2 text-center text-sm text-gray-700 font-medium shadow-lg"
|
||||
:class="getCardClass(index)">
|
||||
<view class="mb-1 flex shrink-0 items-center justify-center">
|
||||
<image :src="getFeatureIcon(item.api_id)" :alt="item.name" class="h-6 w-6 drop-shadow-sm"
|
||||
@error="handleIconError" />
|
||||
</view>
|
||||
<view
|
||||
class="w-full flex flex-1 items-center justify-center break-all px-1 text-xs font-medium leading-snug">
|
||||
{{ item.name }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
<view class="mt-3 text-center">
|
||||
<view class="inline-flex items-center border rounded-full px-3 py-1.5 transition-all"
|
||||
style="background: linear-gradient(135deg, var(--color-primary-light), rgba(255,255,255,0.8)); border-color: var(--color-primary);">
|
||||
<view class="mr-1.5 h-1.5 w-1.5 rounded-full" style="background-color: var(--color-primary);" />
|
||||
<text class="text-xs font-medium" style="color: var(--color-primary);">
|
||||
更多信息请解锁报告
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 产品详情卡片 -->
|
||||
<view class="card mt-4">
|
||||
<view class="mb-4 text-xl font-bold" style="color: var(--color-text-primary);">
|
||||
{{ featureData.product_name }}
|
||||
</view>
|
||||
<view class="mb-4 flex items-start justify-between">
|
||||
<view class="text-lg" style="color: var(--color-text-secondary);">
|
||||
价格:
|
||||
</view>
|
||||
<view>
|
||||
<view class="text-danger text-2xl font-semibold">
|
||||
¥{{ featureData.sell_price }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<image v-if="productMainImage" :src="productMainImage" alt="产品详情主图" class="mb-4 w-full rounded-lg" />
|
||||
|
||||
<view class="mb-4 leading-relaxed" style="color: var(--color-text-secondary);" v-html="featureData.description" />
|
||||
<view class="text-danger mb-2 text-xs italic">
|
||||
为保证用户的隐私以及数据安全,查询的结果生成{{ queryRetentionDaysText }}之后将自动清除。
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 支付组件 -->
|
||||
<Payment :id="queryId" v-model="showPayment" :data="featureData" type="query" @close="showPayment = false" />
|
||||
<BindPhoneDialog @bind-success="handleBindSuccess" />
|
||||
<LoginDialog @login-success="handleLoginSuccess" />
|
||||
|
||||
<!-- 历史查询按钮 - 仅推广查询且已登录时显示 -->
|
||||
<view v-if="props.type === 'promotion' && isLoggedIn"
|
||||
class="bg-primary fixed right-2 top-3/4 cursor-pointer rounded-xl px-4 py-2 text-sm text-white font-bold shadow active:bg-blue-500"
|
||||
@click="toHistory">
|
||||
历史查询
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 背景样式 */
|
||||
.inquire-bg {
|
||||
background-color: var(--color-primary-50);
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hero-banner {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-bottom-left-radius: 24px;
|
||||
border-bottom-right-radius: 24px;
|
||||
background: linear-gradient(180deg, #eef4ff 0%, #dfe9ff 100%);
|
||||
}
|
||||
|
||||
.hero-banner-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
.hero-banner-mask {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(180deg, rgba(20, 32, 64, 0.04) 0%, rgba(20, 32, 64, 0.1) 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hero-badge-wrap {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 34px;
|
||||
transform: translateX(-50%);
|
||||
width: 160px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.content-wrap.with-hero {
|
||||
margin-top: -68px;
|
||||
padding-top: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 卡片样式优化 */
|
||||
.card {
|
||||
@apply shadow-lg rounded-xl p-6 transition-all hover:shadow-xl;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
box-shadow:
|
||||
0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
||||
0 2px 4px -1px rgba(0, 0, 0, 0.06),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* 梯形背景图片样式 */
|
||||
.trapezoid-bg-image {
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
height: 44px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 卡片容器样式 */
|
||||
.card-container {
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
padding: 32px 16px;
|
||||
box-shadow: 0px 0px 24px 0px #3F3F3F0F;
|
||||
border: 1px solid rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
.card-container input::placeholder {
|
||||
color: #DDDDDD;
|
||||
}
|
||||
|
||||
.captcha-wd-btn {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
:deep(.inquire-wd-input .wd-input__inner) {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
:deep(.submit-wd-btn.wd-button) {
|
||||
border-radius: 48px;
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
:deep(.captcha-wd-btn.wd-button) {
|
||||
min-width: 96px;
|
||||
}
|
||||
|
||||
:deep(.captcha-wd-btn.wd-button.is-small) {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.agreement-wrap {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
:deep(.agreement-wrap .wd-checkbox) {
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.agreement-text {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.agreement-link {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
/* 功能标签样式 */
|
||||
.feature-tag {
|
||||
background-color: var(--color-primary-light);
|
||||
color: var(--color-primary);
|
||||
padding: 6px 12px;
|
||||
border-radius: 9999px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 功能标签圆点 */
|
||||
.feature-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: var(--color-primary);
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,8 @@
|
||||
<script setup>
|
||||
import { openAgentVipApplyInExternalBrowser } from '@/composables/useAgentVipH5'
|
||||
|
||||
function toAgentVip() {
|
||||
uni.navigateTo({ url: '/pages/agent-vip-apply' })
|
||||
openAgentVipApplyInExternalBrowser()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
46
src/composables/openExternalUrl.ts
Normal file
46
src/composables/openExternalUrl.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
/** 在系统默认浏览器中打开链接(App 用 plus,H5 开发调试用 window.open) */
|
||||
function redactUrlForLog(url: string): string {
|
||||
try {
|
||||
const u = new URL(url)
|
||||
for (const key of ['token', 'refreshAfter', 'accessExpire']) {
|
||||
if (u.searchParams.has(key))
|
||||
u.searchParams.set(key, '***')
|
||||
}
|
||||
return u.toString()
|
||||
}
|
||||
catch {
|
||||
return '[invalid-url]'
|
||||
}
|
||||
}
|
||||
|
||||
export function openExternalUrl(url: string): boolean {
|
||||
if (!url)
|
||||
return false
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
try {
|
||||
if (typeof plus !== 'undefined' && plus.runtime?.openURL) {
|
||||
plus.runtime.openURL(url, (err: unknown) => {
|
||||
console.error('[openExternalUrl] 打开失败', err, redactUrlForLog(url))
|
||||
uni.showToast({ title: '无法打开浏览器', icon: 'none' })
|
||||
})
|
||||
return true
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error('[openExternalUrl] 异常', e)
|
||||
}
|
||||
uni.showToast({ title: '当前环境无法打开外部浏览器', icon: 'none' })
|
||||
return false
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
window.open(url, '_blank')
|
||||
return true
|
||||
// #endif
|
||||
|
||||
// #ifndef APP-PLUS || H5
|
||||
uni.showToast({ title: '请使用 App 打开', icon: 'none' })
|
||||
return false
|
||||
// #endif
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import { pages } from 'virtual:uni-pages'
|
||||
const pathToPage: Record<string, string> = {
|
||||
'/': '/pages/index',
|
||||
'/login': '/pages/login',
|
||||
'/historyQuery': '/pages/history-query',
|
||||
'/historyQuery': '/pages/index',
|
||||
'/help': '/pages/help',
|
||||
'/help/detail': '/pages/help-detail',
|
||||
'/help/guide': '/pages/help-guide',
|
||||
@@ -35,7 +35,7 @@ const nameToPage: Record<string, string> = {
|
||||
invitation: '/pages/invitation',
|
||||
promote: '/pages/promote',
|
||||
agent: '/pages/agent',
|
||||
history: '/pages/history-query',
|
||||
history: '/pages/index',
|
||||
help: '/pages/help',
|
||||
helpDetail: '/pages/help-detail',
|
||||
helpGuide: '/pages/help-guide',
|
||||
@@ -70,7 +70,7 @@ export function resolveWebToUni(to: string | { name?: string, path?: string, que
|
||||
|
||||
if (pathOnly.startsWith('/inquire/')) {
|
||||
const feature = pathOnly.replace(/^\/inquire\//, '')
|
||||
const base = `/pages/inquire?feature=${encodeURIComponent(feature)}`
|
||||
const base = `/pages/promote?feature=${encodeURIComponent(feature)}`
|
||||
return queryPart ? `${base}&${queryPart}` : base
|
||||
}
|
||||
if (pathOnly.startsWith('/agent/invitationAgentApply/')) {
|
||||
@@ -142,12 +142,31 @@ const pageMetaByRoute = new Map(
|
||||
(pages as UniPageMeta[]).map(page => [normalizeUniRoute(page.path), page]),
|
||||
)
|
||||
|
||||
function resolvePageMetaByRoute(route: string): UniPageMeta | undefined {
|
||||
const normalized = normalizeUniRoute(route)
|
||||
const candidates = [
|
||||
normalized,
|
||||
normalized.startsWith('pages/') ? normalized.slice('pages/'.length) : `pages/${normalized}`,
|
||||
]
|
||||
for (const key of candidates) {
|
||||
const meta = pageMetaByRoute.get(key)
|
||||
if (meta)
|
||||
return meta
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function getCurrentPageMeta(): UniPageMeta | undefined {
|
||||
return pageMetaByRoute.get(getCurrentUniRoute())
|
||||
return resolvePageMetaByRoute(getCurrentUniRoute())
|
||||
}
|
||||
|
||||
export function getPageTitleByRoute(route = getCurrentUniRoute()): string {
|
||||
return pageMetaByRoute.get(normalizeUniRoute(route))?.style?.navigationBarTitleText || 'BDRP'
|
||||
const meta = resolvePageMetaByRoute(route)
|
||||
const raw = meta?.style?.navigationBarTitleText
|
||||
if (typeof raw === 'string' && raw.trim())
|
||||
return raw.trim()
|
||||
const appName = import.meta.env.VITE_APP_NAME
|
||||
return (typeof appName === 'string' && appName.trim()) ? appName.trim() : 'BDRP'
|
||||
}
|
||||
|
||||
export function getLayoutPageTitle(): string {
|
||||
@@ -163,7 +182,6 @@ const UNI_TO_WEB_NOTIFY_PATHS: Record<string, string[]> = {
|
||||
'pages/agent': ['/agent'],
|
||||
'pages/me': ['/me'],
|
||||
'pages/promote': ['/agent/promote'],
|
||||
'pages/history-query': ['/historyQuery'],
|
||||
'pages/help': ['/help'],
|
||||
'pages/help-detail': ['/help/detail'],
|
||||
'pages/help-guide': ['/help/guide'],
|
||||
@@ -176,7 +194,6 @@ const UNI_TO_WEB_NOTIFY_PATHS: Record<string, string[]> = {
|
||||
'pages/agent-service-agreement': ['/agentSerivceAgreement'],
|
||||
'pages/authorization': ['/authorization'],
|
||||
'pages/payment-result': ['/payment/result'],
|
||||
'pages/inquire': ['/inquire'],
|
||||
'pages/login': ['/login'],
|
||||
'pages/invitation': ['/agent/invitation'],
|
||||
'pages/agent-promote-details': ['/agent/promoteDetails'],
|
||||
@@ -202,8 +219,8 @@ export function getWebPathsForNotification(): string[] {
|
||||
const q: Record<string, string> = { ...(page?.options || {}) }
|
||||
|
||||
// 动态路由:带参数的路径
|
||||
if (r === 'pages/inquire' && q.feature)
|
||||
return [`/inquire/${q.feature}`]
|
||||
if (r === 'pages/promote' && q.feature)
|
||||
return [`/inquire/${q.feature}`, '/agent/promote']
|
||||
if (r === 'pages/subordinate-detail' && q.id)
|
||||
return [`/agent/subordinateDetail/${q.id}`]
|
||||
if (r === 'pages/invitation-agent-apply' && q.linkIdentifier)
|
||||
|
||||
58
src/composables/useAgentVipH5.ts
Normal file
58
src/composables/useAgentVipH5.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { openExternalUrl } from '@/composables/openExternalUrl'
|
||||
import { useReportWebview } from '@/composables/useReportWebview'
|
||||
import { getToken } from '@/utils/storage'
|
||||
|
||||
const VIP_APPLY_H5_PATH = '/agent/vipApply'
|
||||
|
||||
const CONFIRM_TITLE = '前往浏览器完成升级'
|
||||
const CONFIRM_CONTENT = '代理会员开通/续费将在系统浏览器中完成,与 App 支付完全分离。是否继续打开?'
|
||||
|
||||
export function buildAgentVipApplyH5Url(params: Record<string, string> = {}) {
|
||||
const { buildSitePathUrl } = useReportWebview()
|
||||
return buildSitePathUrl(VIP_APPLY_H5_PATH, params)
|
||||
}
|
||||
|
||||
function buildTypeQuery(options?: { type?: 'vip' | 'svip' | 'VIP' | 'SVIP' }) {
|
||||
const query: Record<string, string> = {}
|
||||
const rawType = options?.type
|
||||
if (rawType) {
|
||||
const normalized = String(rawType).toUpperCase()
|
||||
if (normalized === 'VIP' || normalized === 'SVIP')
|
||||
query.type = normalized.toLowerCase()
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
/**
|
||||
* 在系统浏览器打开 H5 会员页(带 App 登录态),打开前需用户确认。
|
||||
*/
|
||||
export function openAgentVipApplyInExternalBrowser(options?: { type?: 'vip' | 'svip' | 'VIP' | 'SVIP' }) {
|
||||
if (!getToken()) {
|
||||
uni.showToast({ title: '请先登录', icon: 'none' })
|
||||
uni.navigateTo({ url: '/pages/login' })
|
||||
return false
|
||||
}
|
||||
|
||||
const h5Url = buildAgentVipApplyH5Url(buildTypeQuery(options))
|
||||
if (!h5Url) {
|
||||
uni.showToast({ title: '未配置 H5 站点地址', icon: 'none' })
|
||||
return false
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: CONFIRM_TITLE,
|
||||
content: CONFIRM_CONTENT,
|
||||
confirmText: '继续',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm)
|
||||
openExternalUrl(h5Url)
|
||||
},
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
/** @deprecated 请使用 openAgentVipApplyInExternalBrowser */
|
||||
export function navigateToAgentVipApplyH5(options?: { type?: 'vip' | 'svip' | 'VIP' | 'SVIP' }) {
|
||||
return openAgentVipApplyInExternalBrowser(options)
|
||||
}
|
||||
@@ -18,7 +18,6 @@ const HOME_URL = '/pages/index'
|
||||
*/
|
||||
const ROUTE_PERMISSION_MAP: Record<string, PagePermission> = {
|
||||
'pages/promote': { requiresAuth: true, requiresAgent: true },
|
||||
'pages/history-query': { requiresAuth: true },
|
||||
'pages/withdraw': { requiresAuth: true, requiresAgent: true },
|
||||
'pages/payment-result': { requiresAuth: true },
|
||||
'pages/report-result-webview': { requiresAuth: true },
|
||||
|
||||
@@ -3,6 +3,27 @@ import { getToken } from '@/utils/storage'
|
||||
|
||||
declare function getCurrentPages(): any[]
|
||||
|
||||
/** 与 bdrp-webview `VITE_TOKEN_VERSION` 保持一致,供外部浏览器 / WebView 带参登录 */
|
||||
function getAppTokenVersionForH5(): string {
|
||||
return String(import.meta.env.VITE_TOKEN_VERSION || '1.0')
|
||||
}
|
||||
|
||||
/** 外部浏览器打开 H5 时携带 App 登录态(须与 webview `applyAppWebViewTokenFromRoute` 对齐) */
|
||||
export function appendAppAuthQueryParams(params: Record<string, string> = {}): Record<string, string> {
|
||||
const merged: Record<string, string> = { ...params, source: 'app' }
|
||||
const token = getToken()
|
||||
if (token)
|
||||
merged.token = token
|
||||
const refreshAfter = uni.getStorageSync('refreshAfter')
|
||||
const accessExpire = uni.getStorageSync('accessExpire')
|
||||
if (refreshAfter != null && refreshAfter !== '')
|
||||
merged.refreshAfter = String(refreshAfter)
|
||||
if (accessExpire != null && accessExpire !== '')
|
||||
merged.accessExpire = String(accessExpire)
|
||||
merged.tokenVersion = getAppTokenVersionForH5()
|
||||
return merged
|
||||
}
|
||||
|
||||
/** App 端部分 WebView 无 URLSearchParams,用手动编码保证兼容 */
|
||||
function buildQueryString(params: Record<string, string>): string {
|
||||
const parts: string[] = []
|
||||
@@ -20,46 +41,37 @@ export function useReportWebview() {
|
||||
}
|
||||
|
||||
const buildReportUrl = (page: 'example' | 'report', params: Record<string, string> = {}) => {
|
||||
const token = getToken() || ''
|
||||
const base = resolveBase()
|
||||
if (!base) {
|
||||
console.warn('[report webview] 请配置 VITE_SITE_ORIGIN 或 VITE_REPORT_BASE_URL')
|
||||
return ''
|
||||
}
|
||||
const merged: Record<string, string> = { source: 'app', ...params }
|
||||
if (token)
|
||||
merged.token = token
|
||||
const merged = appendAppAuthQueryParams(params)
|
||||
const search = buildQueryString(merged)
|
||||
/** 使用 H5 无顶栏的 App 专用路由,避免 PageLayout 与 App 原生导航栏重复 */
|
||||
return `${base}/app/${page === 'example' ? 'example' : 'report'}?${search}`
|
||||
}
|
||||
|
||||
const buildReportShareUrl = (linkIdentifier: string, params: Record<string, string> = {}) => {
|
||||
const token = getToken() || ''
|
||||
const base = resolveBase()
|
||||
if (!base) {
|
||||
console.warn('[report webview] 请配置 VITE_SITE_ORIGIN 或 VITE_REPORT_BASE_URL')
|
||||
return ''
|
||||
}
|
||||
const merged: Record<string, string> = { source: 'app', ...params }
|
||||
if (token)
|
||||
merged.token = token
|
||||
const merged = appendAppAuthQueryParams(params)
|
||||
const search = buildQueryString(merged)
|
||||
const encodedLink = encodeURIComponent(linkIdentifier)
|
||||
return `${base}/report/share/${encodedLink}${search ? `?${search}` : ''}`
|
||||
}
|
||||
|
||||
const buildSitePathUrl = (path: string, params: Record<string, string> = {}) => {
|
||||
const token = getToken() || ''
|
||||
const base = resolveBase()
|
||||
if (!base) {
|
||||
console.warn('[site webview] 请配置 VITE_SITE_ORIGIN 或 VITE_REPORT_BASE_URL')
|
||||
return ''
|
||||
}
|
||||
const normalizedPath = path.startsWith('/') ? path : `/${path}`
|
||||
const merged: Record<string, string> = { source: 'app', ...params }
|
||||
if (token)
|
||||
merged.token = token
|
||||
const merged = appendAppAuthQueryParams(params)
|
||||
const search = buildQueryString(merged)
|
||||
return `${base}${normalizedPath}${search ? `?${search}` : ''}`
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { computed, nextTick, onMounted, ref } from 'vue'
|
||||
import {
|
||||
getCurrentUniRoute,
|
||||
getLayoutPageTitle,
|
||||
@@ -11,10 +11,13 @@ import { ensurePrivacyConsentIfNeeded } from '@/composables/usePrivacyConsent'
|
||||
import { useGlobalNotification } from '@/composables/useGlobalNotification'
|
||||
|
||||
const router = useRouter()
|
||||
const pageTitle = ref('赤眉')
|
||||
const currentRoute = ref('')
|
||||
const safeAreaTop = ref(0)
|
||||
const immersiveNavbarSolid = ref(false)
|
||||
/** 用于在 onShow 时触发标题 computed 重新读取页面栈 */
|
||||
const routeRevision = ref(0)
|
||||
const currentRoute = computed(() => {
|
||||
routeRevision.value
|
||||
return getCurrentUniRoute()
|
||||
})
|
||||
const pageTitle = computed(() => getLayoutPageTitle())
|
||||
|
||||
const {
|
||||
showPopup,
|
||||
@@ -25,24 +28,10 @@ const {
|
||||
onClosePopup,
|
||||
} = useGlobalNotification()
|
||||
|
||||
const immersiveRoutes = new Set([
|
||||
'pages/inquire',
|
||||
])
|
||||
|
||||
const isImmersivePage = computed(() => immersiveRoutes.has(currentRoute.value))
|
||||
const immersiveNavbarClass = computed(() => (
|
||||
immersiveNavbarSolid.value ? 'immersive-wd-navbar is-solid' : 'immersive-wd-navbar'
|
||||
))
|
||||
const immersiveNavbarStyle = computed(() => (
|
||||
immersiveNavbarSolid.value
|
||||
? 'background: rgba(255,255,255,0.96); box-shadow: 0 2px 12px rgba(15, 23, 42, 0.08);'
|
||||
: 'background: transparent; box-shadow: none;'
|
||||
))
|
||||
|
||||
function syncTitle() {
|
||||
currentRoute.value = getCurrentUniRoute()
|
||||
pageTitle.value = getLayoutPageTitle()
|
||||
immersiveNavbarSolid.value = false
|
||||
nextTick(() => {
|
||||
routeRevision.value += 1
|
||||
})
|
||||
}
|
||||
|
||||
function buildCurrentPageUrl() {
|
||||
@@ -60,15 +49,6 @@ function ensureAuthIfNeeded() {
|
||||
ensurePageAccessByUrl(buildCurrentPageUrl(), 'redirect')
|
||||
}
|
||||
|
||||
function getSafeAreaTop() {
|
||||
uni.getSystemInfo({
|
||||
success: (res) => {
|
||||
if (res.safeArea)
|
||||
safeAreaTop.value = res.safeArea.top
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function onClickOverlay() {
|
||||
onClosePopup()
|
||||
}
|
||||
@@ -77,17 +57,9 @@ function onClickLeft() {
|
||||
router.back()
|
||||
}
|
||||
|
||||
function onImmersiveNavbarChange(payload) {
|
||||
if (!payload || payload.route !== currentRoute.value || !immersiveRoutes.has(payload.route))
|
||||
return
|
||||
immersiveNavbarSolid.value = Boolean(payload.solid)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getSafeAreaTop()
|
||||
syncTitle()
|
||||
loadShownNotificationKeys()
|
||||
console.log('[通知DEBUG] default layout onMounted, 即将请求通知')
|
||||
getGlobalNotify()
|
||||
})
|
||||
|
||||
@@ -95,35 +67,29 @@ onShow(() => {
|
||||
ensurePrivacyConsentIfNeeded()
|
||||
syncTitle()
|
||||
ensureAuthIfNeeded()
|
||||
console.log('[通知DEBUG] default layout onShow')
|
||||
checkNotification()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
uni.$on('immersive-navbar-change', onImmersiveNavbarChange)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
uni.$off('immersive-navbar-change', onImmersiveNavbarChange)
|
||||
onReady(() => {
|
||||
syncTitle()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view
|
||||
class="page-layout min-h-screen from-sky-100/20 to-white bg-gradient-to-b"
|
||||
:class="{ 'transparent-layout': isImmersivePage }"
|
||||
>
|
||||
<view class="page-layout min-h-screen from-sky-100/20 to-white bg-gradient-to-b">
|
||||
<wd-navbar
|
||||
v-if="isImmersivePage"
|
||||
:class="immersiveNavbarClass"
|
||||
:title="pageTitle"
|
||||
:custom-style="immersiveNavbarStyle"
|
||||
:key="currentRoute"
|
||||
safe-area-inset-top
|
||||
left-arrow
|
||||
placeholder
|
||||
fixed
|
||||
bordered
|
||||
@click-left="onClickLeft"
|
||||
/>
|
||||
<wd-navbar v-else :title="pageTitle" safe-area-inset-top left-arrow placeholder fixed @click-left="onClickLeft" />
|
||||
>
|
||||
<template #title>
|
||||
<text class="layout-navbar-title">{{ pageTitle }}</text>
|
||||
</template>
|
||||
</wd-navbar>
|
||||
<slot />
|
||||
<wd-popup v-model="showPopup" round :close-on-click-modal="true" @close="onClickOverlay">
|
||||
<view class="popup-content p-8 text-center" style="width: 85vw;">
|
||||
@@ -142,28 +108,16 @@ onUnmounted(() => {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.transparent-layout {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
:deep(.immersive-wd-navbar) {
|
||||
z-index: 1000;
|
||||
transition: background-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
:deep(.immersive-wd-navbar .wd-navbar__title),
|
||||
:deep(.immersive-wd-navbar .wd-navbar__left),
|
||||
:deep(.immersive-wd-navbar .wd-navbar__left .wd-icon) {
|
||||
color: #fff !important;
|
||||
text-shadow: 0 1px 6px rgba(0, 0, 0, 0.22);
|
||||
transition: color 0.2s ease, text-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
:deep(.immersive-wd-navbar.is-solid .wd-navbar__title),
|
||||
:deep(.immersive-wd-navbar.is-solid .wd-navbar__left),
|
||||
:deep(.immersive-wd-navbar.is-solid .wd-navbar__left .wd-icon) {
|
||||
color: #111827 !important;
|
||||
text-shadow: none;
|
||||
:deep(.layout-navbar-title) {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: #111827;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
|
||||
@@ -1,986 +0,0 @@
|
||||
<script setup>
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { openCustomerService } from '@/composables/useCustomerService'
|
||||
import { useAgentStore } from '@/stores/agentStore'
|
||||
|
||||
definePage({ layout: 'default', auth: true })
|
||||
|
||||
/** API 与初始状态可能为 undefined/字符串,避免参与运算出现 NaN */
|
||||
function toFiniteNumber(v, fallback = 0) {
|
||||
if (v === null || v === undefined || v === '')
|
||||
return fallback
|
||||
const n = typeof v === 'number' ? v : Number(String(v).replace(/,/g, ''))
|
||||
return Number.isFinite(n) ? n : fallback
|
||||
}
|
||||
|
||||
// 获取代理状态
|
||||
const agentStore = useAgentStore()
|
||||
const { level, ExpiryTime } = storeToRefs(agentStore)
|
||||
|
||||
const selectedType = ref('vip') // 默认选择 VIP(供下方 computed 使用)
|
||||
|
||||
// 计算是否已经是VIP或SVIP
|
||||
const isVipOrSvip = computed(() => ['VIP', 'SVIP'].includes(level.value))
|
||||
const isVip = computed(() => level.value === 'VIP')
|
||||
const isSvip = computed(() => level.value === 'SVIP')
|
||||
|
||||
// 计算续费后的到期时间
|
||||
const renewalExpiryTime = computed(() => {
|
||||
if (!ExpiryTime.value)
|
||||
return '未知'
|
||||
|
||||
// 从格式化字符串中提取日期部分
|
||||
const dateStr = ExpiryTime.value.split(' ')[0] // 假设格式是 "YYYY-MM-DD HH:MM:SS"
|
||||
const parts = dateStr.split('-').map(p => Number.parseInt(p, 10))
|
||||
const [year, month, day] = parts
|
||||
if (parts.length < 3 || [year, month, day].some(n => !Number.isFinite(n)))
|
||||
return '未知'
|
||||
|
||||
// 创建日期对象并加一年
|
||||
const expiryDate = new Date(year, month - 1, day) // 月份从0开始,所以要-1
|
||||
expiryDate.setFullYear(expiryDate.getFullYear() + 1)
|
||||
|
||||
// 返回格式化的日期字符串
|
||||
return `${expiryDate.getFullYear()}-${String(expiryDate.getMonth() + 1).padStart(2, '0')}-${String(expiryDate.getDate()).padStart(2, '0')}`
|
||||
})
|
||||
|
||||
// 按钮文字 - 根据当前状态显示不同文案
|
||||
const buttonText = computed(() => {
|
||||
if (!isVipOrSvip.value)
|
||||
return '立即开通' // 非会员状态
|
||||
|
||||
if (selectedType.value === 'vip') {
|
||||
if (isVip.value)
|
||||
return '续费VIP代理' // VIP续费VIP
|
||||
return '降级不可用' // SVIP不能降级到VIP
|
||||
}
|
||||
else {
|
||||
if (isSvip.value)
|
||||
return '续费SVIP代理' // SVIP续费SVIP
|
||||
return '升级SVIP代理' // VIP升级SVIP
|
||||
}
|
||||
})
|
||||
|
||||
// 是否可以操作按钮
|
||||
const canPerformAction = computed(() => {
|
||||
// 非会员可以开通任何会员
|
||||
if (!isVipOrSvip.value)
|
||||
return true
|
||||
|
||||
// VIP不能降级到普通会员
|
||||
if (isVip.value && selectedType.value === '')
|
||||
return false
|
||||
|
||||
// SVIP不能降级到VIP
|
||||
if (isSvip.value && selectedType.value === 'vip')
|
||||
return false
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
// 计算按钮类名
|
||||
const buttonClass = computed(() => {
|
||||
const baseClass
|
||||
= 'w-full py-4 rounded-lg font-bold text-lg shadow-lg transform transition-transform active:scale-98 relative overflow-hidden'
|
||||
|
||||
if (!canPerformAction.value) {
|
||||
return `${baseClass} bg-gray-400 text-white cursor-not-allowed`
|
||||
}
|
||||
|
||||
if (isVip.value && selectedType.value === 'svip') {
|
||||
return `${baseClass} bg-gradient-to-r from-purple-500 to-indigo-600 text-white`
|
||||
}
|
||||
|
||||
return `${baseClass} bg-gradient-to-r from-amber-500 to-amber-600 active:from-amber-600 active:to-amber-700 text-white`
|
||||
})
|
||||
|
||||
// VIP价格配置(展示用单位;数值在拉取 API 后写入)
|
||||
const vipConfig = reactive({ priceUnit: '元' })
|
||||
|
||||
// 计算得出的收益数据
|
||||
const revenueData = computed(() => {
|
||||
const price = toFiniteNumber(vipConfig.price)
|
||||
const svipPrice = toFiniteNumber(vipConfig.svipPrice)
|
||||
const vipCommission = toFiniteNumber(vipConfig.vipCommission)
|
||||
const svipCommission = toFiniteNumber(vipConfig.svipCommission)
|
||||
const vipFloatingRate = toFiniteNumber(vipConfig.vipFloatingRate)
|
||||
const svipFloatingRate = toFiniteNumber(vipConfig.svipFloatingRate)
|
||||
const vipConversionBonus = toFiniteNumber(vipConfig.vipConversionBonus)
|
||||
const svipConversionBonus = toFiniteNumber(vipConfig.svipConversionBonus)
|
||||
const withdrawRatio = toFiniteNumber(vipConfig.withdrawRatio)
|
||||
|
||||
const baseOrders = 300 // 基础订单数
|
||||
const pricePerOrder = 50 // 每单价格
|
||||
const baseRevenue = baseOrders * pricePerOrder // 基础推广收益
|
||||
|
||||
const vipCommissionRevenue = baseOrders * vipCommission // VIP下级贡献收益
|
||||
const svipCommissionRevenue = baseOrders * svipCommission // SVIP下级贡献收益
|
||||
|
||||
const vipFloatingRevenue = 100 * 100 * (vipFloatingRate / 100) // VIP浮动收益
|
||||
const svipFloatingRevenue = 200 * 100 * (svipFloatingRate / 100) // SVIP浮动收益
|
||||
|
||||
const vipConversionRevenue = vipConversionBonus * 2 // VIP转化奖励
|
||||
const svipConversionRevenue = svipConversionBonus * 2 // SVIP转化奖励
|
||||
|
||||
const vipExtraRevenue = 3000 // VIP额外收益估计
|
||||
const svipExtraRevenue = 6000 // SVIP额外收益估计
|
||||
|
||||
// 平级提现奖励(只有SVIP才有)
|
||||
const withdrawReward = 20000 * (withdrawRatio / 100)
|
||||
|
||||
// 计算月总收益
|
||||
const vipMonthlyTotal
|
||||
= baseRevenue
|
||||
+ vipCommissionRevenue
|
||||
+ vipFloatingRevenue
|
||||
+ vipConversionRevenue
|
||||
+ vipExtraRevenue
|
||||
|
||||
const svipMonthlyTotal
|
||||
= baseRevenue
|
||||
+ svipCommissionRevenue
|
||||
+ svipFloatingRevenue
|
||||
+ withdrawReward
|
||||
+ svipConversionRevenue
|
||||
+ svipExtraRevenue
|
||||
|
||||
// 计算VIP和SVIP之间的差额
|
||||
const monthlyDifference = svipMonthlyTotal - vipMonthlyTotal
|
||||
const priceDifference = svipPrice - price
|
||||
|
||||
const vipRate = price > 0 ? Math.round(vipMonthlyTotal / price) : 0
|
||||
const svipRate = svipPrice > 0 ? Math.round(svipMonthlyTotal / svipPrice) : 0
|
||||
|
||||
// 月增收对应到「多少天回本」:避免除 0 或月差为 0 时出现 Infinity / NaN
|
||||
let recoverDays = 0
|
||||
if (priceDifference > 0 && monthlyDifference > 0) {
|
||||
const perDay = monthlyDifference / 30
|
||||
if (perDay > 0)
|
||||
recoverDays = Math.ceil(priceDifference / perDay)
|
||||
}
|
||||
|
||||
return {
|
||||
vipMonthly: Math.round(toFiniteNumber(vipMonthlyTotal)),
|
||||
svipMonthly: Math.round(toFiniteNumber(svipMonthlyTotal)),
|
||||
vipYearly: Math.round(toFiniteNumber(vipMonthlyTotal) * 12),
|
||||
svipYearly: Math.round(toFiniteNumber(svipMonthlyTotal) * 12),
|
||||
monthlyDifference: Math.round(toFiniteNumber(monthlyDifference)),
|
||||
yearlyDifference: Math.round(toFiniteNumber(monthlyDifference) * 12),
|
||||
vipRate,
|
||||
svipRate,
|
||||
priceDifference: Math.round(toFiniteNumber(priceDifference)),
|
||||
recoverDays,
|
||||
withdrawReward: Math.round(toFiniteNumber(withdrawReward)),
|
||||
}
|
||||
})
|
||||
|
||||
// 加载价格配置
|
||||
onMounted(async () => {
|
||||
// 从API获取会员配置信息
|
||||
try {
|
||||
const { data, error } = await useApiFetch('/agent/membership/info', { silent: true }).get().json()
|
||||
|
||||
if (data.value && !error.value && data.value.code === 200) {
|
||||
const configData = data.value.data
|
||||
|
||||
// 更新VIP配置
|
||||
if (configData.vip_config) {
|
||||
const vipData = configData.vip_config
|
||||
vipConfig.price = toFiniteNumber(vipData.price)
|
||||
vipConfig.vipCommission = toFiniteNumber(vipData.report_commission) // 直接显示,就是几元/单
|
||||
vipConfig.vipFloatingRate = toFiniteNumber(vipData.price_ratio) * 100 // 转换为百分比
|
||||
vipConfig.vipConversionBonus = toFiniteNumber(vipData.lower_convert_vip_reward)
|
||||
vipConfig.vipWithdrawalLimit = toFiniteNumber(vipData.exemption_amount)
|
||||
}
|
||||
|
||||
// 更新SVIP配置
|
||||
if (configData.svip_config) {
|
||||
const svipData = configData.svip_config
|
||||
vipConfig.svipPrice = toFiniteNumber(svipData.price)
|
||||
vipConfig.svipCommission = toFiniteNumber(svipData.report_commission) // 直接显示,就是几元/单
|
||||
vipConfig.svipFloatingRate = toFiniteNumber(svipData.price_ratio) * 100 // 转换为百分比
|
||||
vipConfig.withdrawRatio = toFiniteNumber(svipData.lower_withdraw_reward_ratio) * 100 // 转换为百分比
|
||||
vipConfig.svipConversionBonus = toFiniteNumber(svipData.lower_convert_svip_reward)
|
||||
vipConfig.svipWithdrawalLimit = toFiniteNumber(svipData.exemption_amount)
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.error('获取会员配置信息失败', data.value?.msg || '未知错误')
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('加载会员配置信息失败', error)
|
||||
}
|
||||
})
|
||||
|
||||
const showPayment = ref(false)
|
||||
const payID = ref('')
|
||||
const payData = computed(() => ({
|
||||
product_name: `${selectedType.value === 'vip' ? 'VIP' : 'SVIP'}代理`,
|
||||
sell_price: selectedType.value === 'vip'
|
||||
? toFiniteNumber(vipConfig.price)
|
||||
: toFiniteNumber(vipConfig.svipPrice),
|
||||
}))
|
||||
|
||||
function showToast(message) {
|
||||
if (!message)
|
||||
return
|
||||
uni.showToast({
|
||||
title: message,
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
|
||||
// 选择代理类型
|
||||
function selectType(type) {
|
||||
selectedType.value = type
|
||||
}
|
||||
|
||||
// 申请VIP或SVIP
|
||||
async function applyVip() {
|
||||
// 如果是SVIP要降级到VIP,提示不能降级
|
||||
if (isSvip.value && selectedType.value === 'vip') {
|
||||
showToast('SVIP会员不能降级到VIP会员')
|
||||
return
|
||||
}
|
||||
|
||||
if (toFiniteNumber(payData.value.sell_price) <= 0) {
|
||||
showToast('会员价格加载中,请稍后重试')
|
||||
return
|
||||
}
|
||||
|
||||
const { data, error } = await useApiFetch('/agent/membership/activate')
|
||||
.post({
|
||||
type: selectedType.value.toUpperCase(),
|
||||
})
|
||||
.json()
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
if (data.value.data.id) {
|
||||
payID.value = data.value.data.id
|
||||
showPayment.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 联系客服
|
||||
function contactService() {
|
||||
openCustomerService()
|
||||
}
|
||||
|
||||
function formatExpiryTime(expiryTimeStr) {
|
||||
if (!expiryTimeStr)
|
||||
return '未知'
|
||||
// 从格式化字符串中提取日期部分
|
||||
return expiryTimeStr.split(' ')[0] // 假设格式是 "YYYY-MM-DD HH:MM:SS"
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="agent-VIP-apply min-h-screen w-full from-amber-50 via-amber-100 to-amber-50 bg-gradient-to-b pb-24">
|
||||
<!-- 装饰元素 -->
|
||||
<view
|
||||
class="absolute right-0 top-0 h-32 w-32 rounded-bl-full from-amber-300 to-amber-500 bg-gradient-to-br opacity-20" />
|
||||
<view
|
||||
class="absolute left-0 top-40 h-16 w-16 rounded-tr-full from-amber-400 to-amber-600 bg-gradient-to-tr opacity-20" />
|
||||
<view
|
||||
class="absolute bottom-60 right-0 h-24 w-24 rounded-tl-full from-amber-300 to-amber-500 bg-gradient-to-bl opacity-20" />
|
||||
|
||||
<!-- 顶部标题区域 -->
|
||||
<view class="header relative px-4 pb-6 pt-8 text-center">
|
||||
<view
|
||||
class="absolute left-1/2 h-1 w-24 animate-pulse rounded-full from-amber-300 via-amber-500 to-amber-300 bg-gradient-to-r -top-2 -translate-x-1/2" />
|
||||
<view class="mb-1 text-3xl text-amber-800 font-bold">
|
||||
{{ isVipOrSvip ? '代理会员续费' : 'VIP代理申请' }}
|
||||
</view>
|
||||
<view class="mx-auto mt-2 max-w-xs text-sm text-amber-700">
|
||||
<template v-if="isVipOrSvip">
|
||||
您的会员有效期至 {{ formatExpiryTime(ExpiryTime) }},续费后有效期至
|
||||
{{ renewalExpiryTime }}
|
||||
</template>
|
||||
<template v-else>
|
||||
平台为疯狂推广者定制的赚买计划,助您收益<text class="text-red-500 font-bold">翻倍增升</text>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<!-- 装饰性金币图标 -->
|
||||
<view class="absolute left-4 top-6 transform -rotate-12">
|
||||
<view
|
||||
class="h-8 w-8 flex items-center justify-center rounded-full from-yellow-300 to-yellow-500 bg-gradient-to-br shadow-lg">
|
||||
<text class="text-xs text-white font-bold">¥</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="absolute right-6 top-10 rotate-12 transform">
|
||||
<view
|
||||
class="h-6 w-6 flex items-center justify-center rounded-full from-yellow-400 to-yellow-600 bg-gradient-to-br shadow-lg">
|
||||
<text class="text-xs text-white font-bold">¥</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 选择代理类型 -->
|
||||
<view class="card-container mb-8 px-4">
|
||||
<view class="transform overflow-hidden border border-amber-100 rounded-xl bg-white shadow-lg transition-all">
|
||||
<view
|
||||
class="relative overflow-hidden from-amber-500 to-amber-600 bg-gradient-to-r px-4 py-3 text-center text-white font-bold">
|
||||
<view class="relative z-10">选择代理类型</view>
|
||||
<view class="absolute inset-0 bg-amber-500 opacity-30">
|
||||
<view
|
||||
class="animate-shimmer absolute left-0 top-0 h-full w-full translate-x-full transform from-transparent via-white to-transparent bg-gradient-to-r opacity-20 -skew-x-30" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="flex gap-4 p-6">
|
||||
<view
|
||||
class="relative flex-1 transform cursor-pointer border-2 rounded-lg p-4 text-center transition-all duration-300 hover:-translate-y-1"
|
||||
:class="[
|
||||
selectedType === 'vip'
|
||||
? 'border-amber-500 bg-amber-50 shadow-md'
|
||||
: 'border-gray-200 hover:border-amber-300',
|
||||
]" @click="selectType('vip')">
|
||||
<view class="text-xl text-amber-700 font-bold">
|
||||
VIP代理
|
||||
</view>
|
||||
<view class="mt-1 text-lg text-amber-600 font-bold">
|
||||
{{ vipConfig.price }}{{ vipConfig.priceUnit }}
|
||||
</view>
|
||||
<view class="mt-2 text-sm text-gray-600">
|
||||
标准VIP权益
|
||||
</view>
|
||||
<view v-if="selectedType === 'vip'"
|
||||
class="absolute h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br shadow-md -right-2 -top-2">
|
||||
<wd-icon name="check" custom-style="color:#fff;font-size:14px;" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="relative flex-1 transform cursor-pointer border-2 rounded-lg p-4 text-center transition-all duration-300 hover:-translate-y-1"
|
||||
:class="[
|
||||
selectedType === 'svip'
|
||||
? 'border-amber-500 bg-amber-50 shadow-md'
|
||||
: 'border-gray-200 hover:border-amber-300',
|
||||
]" @click="selectType('svip')">
|
||||
<view class="text-xl text-amber-700 font-bold">
|
||||
SVIP代理
|
||||
</view>
|
||||
<view class="mt-1 text-lg text-amber-600 font-bold">
|
||||
{{ vipConfig.svipPrice }}{{ vipConfig.priceUnit }}
|
||||
</view>
|
||||
<view class="mt-2 text-sm text-gray-600">
|
||||
超级VIP权益
|
||||
</view>
|
||||
<view v-if="selectedType === 'svip'"
|
||||
class="absolute h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br shadow-md -right-2 -top-2">
|
||||
<wd-icon name="check" custom-style="color:#fff;font-size:14px;" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 六大超值权益 -->
|
||||
<view class="card-container mb-8 px-4">
|
||||
<view class="overflow-hidden border border-amber-100 rounded-xl bg-white shadow-lg">
|
||||
<view
|
||||
class="relative overflow-hidden from-amber-500 to-amber-600 bg-gradient-to-r px-4 py-3 text-center text-white font-bold">
|
||||
<view class="relative z-10">六大超值权益</view>
|
||||
<view class="absolute inset-0 bg-amber-500 opacity-30">
|
||||
<view
|
||||
class="animate-shimmer absolute left-0 top-0 h-full w-full translate-x-full transform from-transparent via-white to-transparent bg-gradient-to-r opacity-20 -skew-x-30" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="grid grid-cols-2 gap-4 p-4">
|
||||
<!-- 权益1 -->
|
||||
<view
|
||||
class="border border-amber-200 rounded-lg from-amber-50 to-amber-100 bg-gradient-to-br p-3 transition-all duration-300 hover:border-amber-300 hover:shadow-md">
|
||||
<view class="mb-2 flex items-center text-amber-800 font-bold">
|
||||
<text
|
||||
class="mr-2 h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br text-xs text-white">1</text>
|
||||
下级贡献收益
|
||||
</view>
|
||||
<text class="text-sm text-gray-600">
|
||||
下级完全收益您来定,涨多少赚多少,一单最高收益<text class="text-red-500 font-bold">10元</text>
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 权益2 -->
|
||||
<view
|
||||
class="border border-amber-200 rounded-lg from-amber-50 to-amber-100 bg-gradient-to-br p-3 transition-all duration-300 hover:border-amber-300 hover:shadow-md">
|
||||
<view class="mb-2 flex items-center text-amber-800 font-bold">
|
||||
<text
|
||||
class="mr-2 h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br text-xs text-white">2</text>
|
||||
下级提现收益
|
||||
</view>
|
||||
<text class="text-sm text-gray-600">
|
||||
下级定价标准由您定,超过标准部分收益更丰厚,一单最高多赚<text class="text-red-500 font-bold">10元</text>
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 权益3 -->
|
||||
<view
|
||||
class="border border-amber-200 rounded-lg from-amber-50 to-amber-100 bg-gradient-to-br p-3 transition-all duration-300 hover:border-amber-300 hover:shadow-md">
|
||||
<view class="mb-2 flex items-center text-amber-800 font-bold">
|
||||
<text
|
||||
class="mr-2 h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br text-xs text-white">3</text>
|
||||
转换高额奖励
|
||||
</view>
|
||||
<text class="text-sm text-gray-600">
|
||||
下级成为VIP、SVIP,高额奖励立马发放,<text class="text-red-500 font-bold">399元</text>
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 权益4 -->
|
||||
<view
|
||||
class="border border-amber-200 rounded-lg from-amber-50 to-amber-100 bg-gradient-to-br p-3 transition-all duration-300 hover:border-amber-300 hover:shadow-md">
|
||||
<view class="mb-2 flex items-center text-amber-800 font-bold">
|
||||
<text
|
||||
class="mr-2 h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br text-xs text-white">4</text>
|
||||
下级提现奖励
|
||||
</view>
|
||||
<text class="text-sm text-gray-600">
|
||||
下级成为SVIP,每次提现都奖励1%,坐享被动收入
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 权益6 -->
|
||||
<view
|
||||
class="border border-amber-200 rounded-lg from-amber-50 to-amber-100 bg-gradient-to-br p-3 transition-all duration-300 hover:border-amber-300 hover:shadow-md">
|
||||
<view class="mb-2 flex items-center text-amber-800 font-bold">
|
||||
<text
|
||||
class="mr-2 h-6 w-6 flex items-center justify-center rounded-full from-amber-500 to-amber-600 bg-gradient-to-br text-xs text-white">6</text>
|
||||
平台专项扶持
|
||||
</view>
|
||||
<text class="text-sm text-gray-600">
|
||||
一对一专属客服服务,为合作伙伴提供全方位成长赋能
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 权益对比表 -->
|
||||
<view v-if="selectedType" class="card-container mb-8 px-4">
|
||||
<view class="overflow-hidden border border-amber-100 rounded-xl bg-white shadow-lg">
|
||||
<view
|
||||
class="relative overflow-hidden from-amber-500 to-amber-600 bg-gradient-to-r px-4 py-3 text-center text-white font-bold">
|
||||
<view class="relative z-10">{{ selectedType === 'vip' ? 'VIP' : 'SVIP' }}代理权益对比</view>
|
||||
<view class="absolute inset-0 bg-amber-500 opacity-30">
|
||||
<view
|
||||
class="animate-shimmer absolute left-0 top-0 h-full w-full translate-x-full transform from-transparent via-white to-transparent bg-gradient-to-r opacity-20 -skew-x-30" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="overflow-x-auto p-4">
|
||||
<table class="w-full border-collapse">
|
||||
<thead>
|
||||
<tr class="from-amber-100 to-amber-200 bg-gradient-to-r">
|
||||
<th class="border border-amber-200 p-2 text-left text-amber-800">
|
||||
权益项目
|
||||
</th>
|
||||
<th class="border border-amber-200 p-2 text-center text-amber-800">
|
||||
普通代理
|
||||
</th>
|
||||
<th class="border border-amber-200 p-2 text-center text-amber-800"
|
||||
:class="{ 'bg-amber-200': selectedType === 'vip' }">
|
||||
VIP代理
|
||||
</th>
|
||||
<th class="border border-amber-200 p-2 text-center text-amber-800"
|
||||
:class="{ 'bg-amber-200': selectedType === 'svip' }">
|
||||
SVIP代理
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="transition-colors hover:bg-amber-50">
|
||||
<td class="border border-amber-200 p-2 font-medium">
|
||||
会员权益
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center">
|
||||
普通代理<br>免费
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center font-bold" :class="{
|
||||
'text-amber-700 bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
{{ vipConfig.price }}{{ vipConfig.priceUnit }}
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center font-bold" :class="{
|
||||
'text-amber-700 bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
{{ vipConfig.svipPrice }}{{ vipConfig.priceUnit }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="transition-colors hover:bg-amber-50">
|
||||
<td class="border border-amber-200 p-2 font-medium">
|
||||
下级贡献收益
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center">
|
||||
1元/单
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
{{ vipConfig.vipCommission }}元/单
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
{{ vipConfig.svipCommission }}元/单
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="transition-colors hover:bg-amber-50">
|
||||
<td class="border border-amber-200 p-2 font-medium">
|
||||
自定义设置下级成本
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center">
|
||||
❌
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
✓
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
✓
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="transition-colors hover:bg-amber-50">
|
||||
<td class="border border-amber-200 p-2 font-medium">
|
||||
下级价格浮动收益
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center">
|
||||
❌
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
最高{{ vipConfig.vipFloatingRate }}%
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
最高{{ vipConfig.svipFloatingRate }}%
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="transition-colors hover:bg-amber-50">
|
||||
<td class="border border-amber-200 p-2 font-medium">
|
||||
下级提现奖励
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center">
|
||||
❌
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
❌
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
{{ vipConfig.withdrawRatio }}%
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="transition-colors hover:bg-amber-50">
|
||||
<td class="border border-amber-200 p-2 font-medium">
|
||||
下级转化奖励
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center">
|
||||
❌
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
{{ vipConfig.vipConversionBonus }}元*10个
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
{{ vipConfig.svipConversionBonus }}元*10个
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="transition-colors hover:bg-amber-50">
|
||||
<td class="border border-amber-200 p-2 font-medium">
|
||||
提现次数额度
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center">
|
||||
800元/次
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
{{ vipConfig.vipWithdrawalLimit }}元/次
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
{{ vipConfig.svipWithdrawalLimit }}元/次
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="transition-colors hover:bg-amber-50">
|
||||
<td class="border border-amber-200 p-2 font-medium">
|
||||
提现次数
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center">
|
||||
1次/日
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
1次/日
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
2次/日
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 收益预估 -->
|
||||
<view v-if="selectedType" class="card-container mb-8 px-4">
|
||||
<view class="overflow-hidden border border-amber-100 rounded-xl bg-white shadow-lg">
|
||||
<view
|
||||
class="relative overflow-hidden from-amber-500 to-amber-600 bg-gradient-to-r px-4 py-3 text-center text-white font-bold">
|
||||
<view class="relative z-10">收益预估对比</view>
|
||||
<view class="absolute inset-0 bg-amber-500 opacity-30">
|
||||
<view
|
||||
class="animate-shimmer absolute left-0 top-0 h-full w-full translate-x-full transform from-transparent via-white to-transparent bg-gradient-to-r opacity-20 -skew-x-30" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="p-4">
|
||||
<!-- 顶部收益概览 -->
|
||||
<view class="mb-6 overflow-hidden border border-amber-200 rounded-lg">
|
||||
<view class="from-amber-100 to-amber-200 bg-gradient-to-r px-4 py-2 text-center text-amber-800 font-bold">
|
||||
VIP与SVIP代理收益对比
|
||||
</view>
|
||||
<view class="grid grid-cols-2 divide-x divide-amber-200">
|
||||
<view class="p-4 text-center" :class="{ 'bg-amber-50': selectedType === 'vip' }">
|
||||
<view class="mb-1 text-sm text-gray-600">
|
||||
VIP月预计收益
|
||||
</view>
|
||||
<view class="text-xl text-amber-600 font-bold">
|
||||
{{ revenueData.vipMonthly }}元
|
||||
</view>
|
||||
<view class="mt-1 text-xs text-gray-500">
|
||||
年收益:{{ revenueData.vipYearly }}元
|
||||
</view>
|
||||
</view>
|
||||
<view class="p-4 text-center" :class="{ 'bg-amber-50': selectedType === 'svip' }">
|
||||
<view class="mb-1 text-sm text-gray-600">
|
||||
SVIP月预计收益
|
||||
</view>
|
||||
<view class="text-xl text-red-500 font-bold">
|
||||
{{ revenueData.svipMonthly }}元
|
||||
</view>
|
||||
<view class="mt-1 text-xs text-gray-500">
|
||||
年收益:{{ revenueData.svipYearly }}元
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="from-red-50 to-red-100 bg-gradient-to-r px-4 py-2 text-center text-red-600 font-medium">
|
||||
选择SVIP,相比VIP月增收益:<text class="font-bold">{{ revenueData.monthlyDifference }}元</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 详细收益表格 -->
|
||||
<view class="overflow-x-auto">
|
||||
<table class="w-full border-collapse">
|
||||
<thead>
|
||||
<tr class="from-amber-100 to-amber-200 bg-gradient-to-r">
|
||||
<th class="border border-amber-200 p-2 text-left text-amber-800">
|
||||
收益来源
|
||||
</th>
|
||||
<th class="w-1/3 border border-amber-200 p-2 text-center text-amber-800"
|
||||
:class="{ 'bg-amber-200': selectedType === 'vip' }">
|
||||
VIP代理
|
||||
</th>
|
||||
<th class="w-1/3 border border-amber-200 p-2 text-center text-amber-800"
|
||||
:class="{ 'bg-amber-200': selectedType === 'svip' }">
|
||||
SVIP代理
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="transition-colors hover:bg-amber-50">
|
||||
<td class="border border-amber-200 p-2 font-medium">
|
||||
推广收益(月)
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
300单×50元=15,000元
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
300单×50元=15,000元
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="transition-colors hover:bg-amber-50">
|
||||
<td class="border border-amber-200 p-2 font-medium">
|
||||
下级贡献收益(月)
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
300单×{{ vipConfig.vipCommission }}元=360元
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
300单×{{ vipConfig.svipCommission }}元=450元
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="transition-colors hover:bg-amber-50">
|
||||
<td class="border border-amber-200 p-2 font-medium">
|
||||
下级价格浮动收益(月)
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
100单×100元×{{ vipConfig.vipFloatingRate }}%=500元
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
200单×100元×{{ vipConfig.svipFloatingRate }}%=2,000元
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="transition-colors hover:bg-amber-50">
|
||||
<td class="border border-amber-200 p-2 font-medium">
|
||||
下级提现奖励(月)
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
-
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
{{ revenueData.withdrawReward }}元
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="transition-colors hover:bg-amber-50">
|
||||
<td class="border border-amber-200 p-2 font-medium">
|
||||
下级转化奖励(月)
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
{{ vipConfig.vipConversionBonus }}元×2个=598元
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
{{ vipConfig.svipConversionBonus }}元×2个=798元
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="transition-colors hover:bg-amber-50">
|
||||
<td class="border border-amber-200 p-2 font-medium">
|
||||
额外业务收益(月)
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
|
||||
}">
|
||||
约3,000元
|
||||
</td>
|
||||
<td class="border border-amber-200 p-2 text-center" :class="{
|
||||
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
|
||||
}">
|
||||
约6,000元
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="from-amber-50 to-amber-100 bg-gradient-to-r font-bold">
|
||||
<td class="border border-amber-200 p-3">
|
||||
月计收益
|
||||
</td>
|
||||
<td class="border border-amber-200 p-3 text-center text-amber-700" :class="{
|
||||
'bg-amber-50 border-amber-300': selectedType === 'vip',
|
||||
}">
|
||||
{{ revenueData.vipMonthly }}元
|
||||
</td>
|
||||
<td class="border border-amber-200 p-3 text-center text-red-500" :class="{
|
||||
'bg-amber-50 border-amber-300': selectedType === 'svip',
|
||||
}">
|
||||
{{ revenueData.svipMonthly }}元
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="from-amber-50 to-amber-100 bg-gradient-to-r font-bold">
|
||||
<td class="border border-amber-200 p-3">
|
||||
年计收益
|
||||
</td>
|
||||
<td class="border border-amber-200 p-3 text-center text-amber-700" :class="{
|
||||
'bg-amber-50 border-amber-300': selectedType === 'vip',
|
||||
}">
|
||||
{{ revenueData.vipYearly }}元
|
||||
</td>
|
||||
<td class="border border-amber-200 p-3 text-center text-red-500" :class="{
|
||||
'bg-amber-50 border-amber-300': selectedType === 'svip',
|
||||
}">
|
||||
{{ revenueData.svipYearly }}元
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</view>
|
||||
|
||||
<!-- 投资回报率 -->
|
||||
<view class="mt-6 border border-amber-200 rounded-lg from-amber-50 to-amber-100 bg-gradient-to-r p-4">
|
||||
<view class="mb-3 text-center text-amber-800 font-bold">
|
||||
投资收益分析
|
||||
</view>
|
||||
<view class="grid grid-cols-1 gap-4">
|
||||
<view class="rounded-lg bg-white p-3 shadow-sm">
|
||||
<view class="flex items-center justify-between">
|
||||
<view class="flex-1 border-r border-amber-100 pr-3">
|
||||
<view class="mb-1 text-center text-amber-700 font-medium">
|
||||
VIP方案
|
||||
</view>
|
||||
<view class="text-center">
|
||||
<view class="text-sm text-amber-600">
|
||||
投资{{ vipConfig.price }}元
|
||||
</view>
|
||||
<view class="text-sm text-gray-600">
|
||||
月收益{{ revenueData.vipMonthly }}元
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex-1 pl-3">
|
||||
<view class="mb-1 text-center text-red-500 font-medium">
|
||||
SVIP方案
|
||||
</view>
|
||||
<view class="text-center">
|
||||
<view class="text-sm text-red-500">
|
||||
投资{{ vipConfig.svipPrice }}元
|
||||
</view>
|
||||
<view class="text-sm text-gray-600">
|
||||
月收益{{ revenueData.svipMonthly }}元
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 升级收益对比 -->
|
||||
<view class="rounded-lg from-red-50 to-amber-50 bg-gradient-to-r p-3 shadow-sm">
|
||||
<view class="mb-2 text-center text-red-700 font-medium">
|
||||
SVIP升级优势分析
|
||||
</view>
|
||||
<view class="flex items-center justify-center gap-3">
|
||||
<view class="text-center">
|
||||
<view class="text-sm text-gray-600">
|
||||
额外投资
|
||||
</view>
|
||||
<view class="text-red-600 font-bold">
|
||||
{{ revenueData.priceDifference }}元
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="h-6 w-6 flex flex-shrink-0 items-center justify-center rounded-full bg-red-500 text-white">
|
||||
<view class="transform -translate-y-px">
|
||||
→
|
||||
</view>
|
||||
</view>
|
||||
<view class="text-center">
|
||||
<view class="text-sm text-gray-600">
|
||||
每月额外收益
|
||||
</view>
|
||||
<view class="text-red-600 font-bold">
|
||||
{{ revenueData.monthlyDifference }}元
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="h-6 w-6 flex flex-shrink-0 items-center justify-center rounded-full bg-red-500 text-white">
|
||||
<text class="transform -translate-y-px">→</text>
|
||||
</view>
|
||||
<view class="text-center">
|
||||
<view class="text-sm text-gray-600">
|
||||
投资回收时间
|
||||
</view>
|
||||
<view class="text-red-600 font-bold">
|
||||
{{ revenueData.recoverDays }}天
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="mt-3 text-center text-red-500 font-medium">
|
||||
额外投资{{ revenueData.priceDifference }}元,<text class="text-red-600 font-bold">年多赚{{
|
||||
revenueData.yearlyDifference }}元</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 申请按钮(固定在底部) -->
|
||||
<view
|
||||
class="fixed bottom-0 left-0 right-0 z-30 from-amber-100 to-transparent bg-gradient-to-t px-4 py-3 backdrop-blur-sm">
|
||||
<view class="flex flex-col gap-2">
|
||||
<wd-button :class="buttonClass" block :disabled="!canPerformAction" @click="applyVip">
|
||||
<text class="relative z-10">{{ buttonText }}</text>
|
||||
<view
|
||||
class="animate-shimmer absolute left-0 top-0 h-full w-full translate-x-full transform from-transparent via-white to-transparent bg-gradient-to-r opacity-20 -skew-x-30" />
|
||||
</wd-button>
|
||||
<wd-button custom-class="contact-wd-btn" block plain @click="contactService">
|
||||
<view class="flex items-center justify-center text-amber-700 font-medium">
|
||||
<wd-icon name="service" custom-class="mr-1" />
|
||||
<text>联系客服咨询</text>
|
||||
</view>
|
||||
</wd-button>
|
||||
<!-- 最终解释权声明 -->
|
||||
<view class="py-1 text-center text-xs text-gray-400">
|
||||
最终解释权归戎行技术有限公司所有
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<Payment :id="payID" v-model="showPayment" :data="payData" type="agent_vip" @close="showPayment = false" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.agent-VIP-apply {
|
||||
font-family:
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
transform: translateX(-100%) skewX(-30deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(200%) skewX(-30deg);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-shimmer {
|
||||
animation: shimmer 3s infinite;
|
||||
}
|
||||
|
||||
.active\:scale-98:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
:deep(.contact-wd-btn.wd-button) {
|
||||
border-color: #fbbf24;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 4px 10px rgba(120, 53, 15, 0.08);
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,10 @@
|
||||
<script setup>
|
||||
import { openCustomerService } from '@/composables/useCustomerService'
|
||||
import { openAgentVipApplyInExternalBrowser } from '@/composables/useAgentVipH5'
|
||||
|
||||
definePage({ layout: 'default', auth: true })
|
||||
function toVipApply() {
|
||||
uni.navigateTo({ url: '/pages/agent-vip-apply' })
|
||||
openAgentVipApplyInExternalBrowser()
|
||||
}
|
||||
|
||||
function toService() {
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { onReachBottom } from '@dcloudio/uni-app'
|
||||
definePage({ layout: 'default', auth: true })
|
||||
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
const reportList = ref([])
|
||||
const loading = ref(false)
|
||||
const hasMore = ref(true)
|
||||
const loadMoreState = ref('loading')
|
||||
// 初始加载数据
|
||||
function mergeUniqueById(oldList, newList) {
|
||||
const map = new Map()
|
||||
const resolveKey = (item, index) => String(item?.id ?? item?.order_id ?? `${item?.create_time || ''}_${item?.query_state || ''}_${index}`)
|
||||
oldList.forEach((item, index) => {
|
||||
map.set(resolveKey(item, index), item)
|
||||
})
|
||||
newList.forEach((item, index) => {
|
||||
map.set(resolveKey(item, oldList.length + index), item)
|
||||
})
|
||||
return Array.from(map.values())
|
||||
}
|
||||
|
||||
async function fetchData(reset = false) {
|
||||
if (loading.value)
|
||||
return
|
||||
if (!reset && !hasMore.value)
|
||||
return
|
||||
loading.value = true
|
||||
if (reset) {
|
||||
page.value = 1
|
||||
hasMore.value = true
|
||||
loadMoreState.value = 'loading'
|
||||
reportList.value = []
|
||||
}
|
||||
const { data, error } = await useApiFetch(`query/list?page=${page.value}&page_size=${pageSize.value}`, { silent: true })
|
||||
.get()
|
||||
.json()
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
total.value = data.value.data.total
|
||||
const incoming = data.value.data.list || []
|
||||
reportList.value = reset ? incoming : mergeUniqueById(reportList.value, incoming)
|
||||
const isLastPage = incoming.length < pageSize.value || reportList.value.length >= total.value
|
||||
hasMore.value = !isLastPage
|
||||
loadMoreState.value = isLastPage ? 'finished' : 'loading'
|
||||
if (!isLastPage)
|
||||
page.value += 1
|
||||
}
|
||||
else {
|
||||
loadMoreState.value = 'error'
|
||||
}
|
||||
}
|
||||
else {
|
||||
loadMoreState.value = 'error'
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
// 初始加载
|
||||
onMounted(() => {
|
||||
fetchData(true)
|
||||
})
|
||||
|
||||
onReachBottom(() => {
|
||||
fetchData()
|
||||
})
|
||||
|
||||
function toDetail(item) {
|
||||
if (item.query_state !== 'success')
|
||||
return
|
||||
uni.navigateTo({
|
||||
url: `/pages/report-result-webview?order_id=${encodeURIComponent(String(item.order_id || ''))}`,
|
||||
})
|
||||
}
|
||||
|
||||
// 状态文字映射
|
||||
function stateText(state) {
|
||||
switch (state) {
|
||||
case 'pending':
|
||||
return '查询中'
|
||||
case 'success':
|
||||
return '查询成功'
|
||||
case 'failed':
|
||||
return '查询失败'
|
||||
case 'refunded':
|
||||
return '已退款'
|
||||
default:
|
||||
return '未知状态'
|
||||
}
|
||||
}
|
||||
|
||||
// 状态颜色映射
|
||||
function statusClass(state) {
|
||||
switch (state) {
|
||||
case 'pending':
|
||||
return 'status-pending'
|
||||
case 'success':
|
||||
return 'status-success'
|
||||
case 'failed':
|
||||
return 'status-failed'
|
||||
case 'refunded':
|
||||
return 'status-refunded'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="flex flex-col gap-4 p-4">
|
||||
<view class="history-scroll">
|
||||
<view
|
||||
v-for="item in reportList" :key="item.id" class="relative mb-4 cursor-pointer rounded-lg bg-white p-4 shadow-sm"
|
||||
@click="toDetail(item)"
|
||||
>
|
||||
<view class="flex flex-col">
|
||||
<view class="mb-1 text-xl text-black">
|
||||
{{ item.product_name }}
|
||||
</view>
|
||||
<view class="text-sm text-[#999999]">
|
||||
{{ item.create_time }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="absolute right-0 top-0 rounded-bl-lg rounded-tr-lg px-2 py-[1px] text-sm text-white font-medium"
|
||||
:class="[statusClass(item.query_state)]"
|
||||
>
|
||||
{{ stateText(item.query_state) }}
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="loading" class="py-4 text-center text-sm text-gray-400">
|
||||
加载中...
|
||||
</view>
|
||||
<EmptyState v-else-if="!reportList.length" text="暂无查询记录" />
|
||||
</view>
|
||||
<wd-loadmore v-if="reportList.length > 0" :state="loadMoreState" @reload="fetchData" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.history-scroll {
|
||||
min-height: calc(100vh - 120px);
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background-color: #1976d2;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-success {
|
||||
background-color: #1FBE5D;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-failed {
|
||||
background-color: #EB3C3C;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-refunded {
|
||||
background-color: #999999;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
@@ -1,22 +1,10 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import bgIcon from '/static/images/bg_icon.png'
|
||||
import bannerImg from '/static/images/index/banner_1.png'
|
||||
import bannerImg2 from '/static/images/index/banner_2.png'
|
||||
import bannerImg3 from '/static/images/index/banner_3.png'
|
||||
import honestyBanner from '/static/images/index/banner_B.png'
|
||||
import companyIcon from '/static/images/index/company_bg.png'
|
||||
import housekeepingRiskIcon from '/static/images/index/housekeeping_risk_bg.png'
|
||||
import loanCheckIcon from '/static/images/index/loan_check_bg.png'
|
||||
import marriageRiskIcon from '/static/images/index/marriage_risk_bg.png'
|
||||
import personalDataIcon from '/static/images/index/personal_data_bg.png'
|
||||
import preLoanRiskIcon from '/static/images/index/preloan_risk_bg.png'
|
||||
import rentalInfoBg from '/static/images/index/rentalinfo_bg.png'
|
||||
import rightIcon from '/static/images/index/right.png'
|
||||
import indexPromoteIcon from '/static/images/index/tgbg.png'
|
||||
import indexMyReportIcon from '/static/images/index/wdbg.png'
|
||||
import indexInvitationIcon from '/static/images/index/yqhy.png'
|
||||
import { useAppConfig } from '@/composables/useAppConfig'
|
||||
import { ensurePrivacyConsentIfNeeded } from '@/composables/usePrivacyConsent'
|
||||
|
||||
definePage({ layout: 'home' })
|
||||
@@ -27,28 +15,6 @@ onShow(() => {
|
||||
|
||||
const banners = [bannerImg, bannerImg2, bannerImg3]
|
||||
|
||||
const services = [
|
||||
{ title: '个人大数据', name: 'personalData', bg: personalDataIcon },
|
||||
{ title: '婚恋风险', name: 'marriage', bg: marriageRiskIcon },
|
||||
{ title: '入职背调', name: 'backgroundcheck', bg: preLoanRiskIcon },
|
||||
]
|
||||
|
||||
const riskServices = [
|
||||
{ title: '小微', name: 'companyinfo', bg: companyIcon },
|
||||
{ title: '家政', name: 'homeservice', bg: housekeepingRiskIcon },
|
||||
{ title: '贷前', name: 'preloanbackgroundcheck', bg: loanCheckIcon },
|
||||
{ title: '诚信租赁', name: 'rentalinfo', bg: rentalInfoBg },
|
||||
]
|
||||
const { appConfig, loadAppConfig } = useAppConfig()
|
||||
const queryRetentionDaysText = computed(() => `${appConfig.value.query.retention_days}天`)
|
||||
void loadAppConfig()
|
||||
|
||||
function toInquire(name) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/inquire?feature=${encodeURIComponent(name)}`,
|
||||
})
|
||||
}
|
||||
|
||||
function toInvitation() {
|
||||
uni.navigateTo({ url: '/pages/invitation' })
|
||||
}
|
||||
@@ -56,112 +22,70 @@ function toInvitation() {
|
||||
function toPromote() {
|
||||
uni.navigateTo({ url: '/pages/promote' })
|
||||
}
|
||||
|
||||
function toHistory() {
|
||||
uni.navigateTo({ url: '/pages/history-query' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="box-border min-h-screen">
|
||||
<view class="relative p-4">
|
||||
<swiper class="banner-swiper overflow-hidden rounded-xl" circular autoplay :interval="3000" indicator-dots
|
||||
indicator-color="rgba(255,255,255,0.5)" indicator-active-color="#ffffff">
|
||||
<swiper-item v-for="(item, index) in banners" :key="index">
|
||||
<view class="box-border min-h-screen bg-[#f5faff] pb-4">
|
||||
<view class="m-4 overflow-hidden rounded-2xl">
|
||||
<swiper
|
||||
class="banner-swiper w-full"
|
||||
circular
|
||||
autoplay
|
||||
:interval="3000"
|
||||
indicator-dots
|
||||
indicator-color="rgba(255,255,255,0.45)"
|
||||
indicator-active-color="#ffffff"
|
||||
>
|
||||
<swiper-item v-for="(item, index) in banners" :key="index" class="h-full w-full">
|
||||
<image :src="item" class="h-full w-full" mode="aspectFill" />
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
|
||||
<view class="px-6">
|
||||
<view class="grid grid-cols-3 gap-3">
|
||||
<view class="flex flex-col items-center justify-center text-center" @click="toPromote">
|
||||
<view
|
||||
class="box-content h-16 w-16 flex items-center justify-center rounded-full from-white to-blue-100/10 bg-gradient-to-b p-1 shadow-lg">
|
||||
<image :src="indexPromoteIcon" class="h-12 w-12" mode="aspectFit" />
|
||||
</view>
|
||||
<text class="mt-1 text-center font-bold">
|
||||
推广报告
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="flex flex-col items-center justify-center text-center" @click="toInvitation">
|
||||
<view
|
||||
class="box-content h-16 w-16 flex items-center justify-center rounded-full from-white to-blue-100/10 bg-gradient-to-b p-1 shadow-lg">
|
||||
<image :src="indexInvitationIcon" class="h-12 w-12" mode="aspectFit" />
|
||||
</view>
|
||||
<text class="mt-1 text-center font-bold">
|
||||
邀请下级
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="flex flex-col items-center justify-center text-center" @click="toHistory">
|
||||
<view
|
||||
class="box-content h-16 w-16 flex items-center justify-center rounded-full from-white to-blue-100/10 bg-gradient-to-b p-1 shadow-lg">
|
||||
<image :src="indexMyReportIcon" class="h-12 w-12" mode="aspectFit" />
|
||||
</view>
|
||||
<text class="mt-1 text-center font-bold">
|
||||
我的报告
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="relative p-4 pt-0">
|
||||
<view class="grid grid-cols-2 my-4 gap-4" style="grid-template-rows: repeat(2, 1fr);">
|
||||
<view v-for="(service, index) in services" :key="index"
|
||||
class="relative min-h-18 flex flex-col rounded-xl px-4 py-2 shadow-lg"
|
||||
:class="index === 0 ? 'row-span-2' : ''"
|
||||
:style="`background: url(${service.bg}) no-repeat; background-size: 100% 100%; background-position: center;`"
|
||||
@click="toInquire(service.name)">
|
||||
<view class="min-h-18 flex items-end">
|
||||
<!-- <text class="text-base text-gray-700 font-semibold">
|
||||
{{ service.title }}
|
||||
</text> -->
|
||||
</view>
|
||||
</view>
|
||||
<view class="mt-2 px-4">
|
||||
<view class="mb-2 text-base text-gray-800 font-bold">
|
||||
即刻赚佣金
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-x class="risk-scroll my-4 px-1 pb-4 pt-2 -mx-1">
|
||||
<view class="inline-flex gap-2">
|
||||
<view v-for="(service, index) in riskServices" :key="index"
|
||||
class="relative h-24 w-[107px] flex-shrink-0 rounded-xl shadow-lg"
|
||||
:style="`background: url(${service.bg}) no-repeat; background-size: 100% 100%; background-position: center;`"
|
||||
@click="toInquire(service.name)">
|
||||
<view class="h-full flex items-end px-2 py-2">
|
||||
<!-- <text class="text-sm text-gray-700 font-semibold">
|
||||
{{ service.title }}
|
||||
</text> -->
|
||||
</view>
|
||||
<view
|
||||
class="card-gradient-1 mb-4 flex items-center rounded-2xl p-0 shadow-xl"
|
||||
@click="toPromote"
|
||||
>
|
||||
<view class="flex flex-1 flex-col items-start justify-center py-4 pl-4">
|
||||
<view class="mb-1 text-[20px] text-blue-700 font-bold">
|
||||
直推报告
|
||||
</view>
|
||||
<view class="mb-4 text-xs text-gray-700">
|
||||
选择所需报告类型,灵活定价,一键分享客户,客户下单即结算,佣金实时到账
|
||||
</view>
|
||||
<wd-button plain>
|
||||
立即推广
|
||||
</wd-button>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="mb-3 mt-6 flex items-center">
|
||||
<view class="bg-primary h-5 w-1.5 rounded-xl" />
|
||||
<text class="ml-2 text-lg text-gray-800">
|
||||
诚信专栏
|
||||
</text>
|
||||
<image class="mx-4 my-4 h-20 w-20" :src="indexPromoteIcon" mode="aspectFit" />
|
||||
</view>
|
||||
|
||||
<view class="mt-4 overflow-hidden rounded-xl bg-white shadow-xl">
|
||||
<image :src="honestyBanner" class="block w-full" mode="widthFix" />
|
||||
<view
|
||||
class="card-gradient-2 flex items-center rounded-2xl p-0 shadow-xl"
|
||||
@click="toInvitation"
|
||||
>
|
||||
<view class="flex flex-1 flex-col items-start justify-center py-4 pl-4">
|
||||
<view class="mb-1 text-[20px] text-teal-700 font-bold">
|
||||
邀请下级代理
|
||||
</view>
|
||||
<view class="mb-4 text-xs text-gray-700">
|
||||
邀请好友成为代理,好友推广获客,客户支付即返佣,团队收益轻松到手
|
||||
</view>
|
||||
<wd-button plain>
|
||||
立即邀请
|
||||
</wd-button>
|
||||
</view>
|
||||
<image class="mx-4 my-4 h-20 w-20" :src="indexInvitationIcon" mode="aspectFit" />
|
||||
</view>
|
||||
|
||||
<view class="mt-4 box-border h-14 w-full flex items-center rounded-lg bg-white px-4 text-gray-700 shadow-lg"
|
||||
@click="toHistory">
|
||||
<view class="mr-4 h-full flex items-center justify-center">
|
||||
<image class="h-10 w-10" :src="bgIcon" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="flex-1">
|
||||
<view class="text-gray-800">
|
||||
我的历史查询记录
|
||||
</view>
|
||||
<view class="text-xs text-gray-500">
|
||||
查询记录有效期为{{ queryRetentionDaysText }}
|
||||
</view>
|
||||
</view>
|
||||
<image :src="rightIcon" class="h-6 w-6" mode="aspectFit" />
|
||||
<view class="relative mt-4 h-[120px] overflow-hidden rounded-2xl shadow-xl">
|
||||
<image class="absolute inset-0 h-full w-full" :src="honestyBanner" mode="aspectFill" />
|
||||
<view class="absolute inset-0 bg-black/5" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -172,7 +96,17 @@ function toHistory() {
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
.risk-scroll {
|
||||
white-space: nowrap;
|
||||
.card-gradient-1 {
|
||||
background: linear-gradient(135deg, #e3f0ff 0%, #fafdff 100%);
|
||||
box-shadow:
|
||||
0 6px 24px 0 rgba(60, 120, 255, 0.1),
|
||||
0 1.5px 4px 0 rgba(60, 120, 255, 0.08);
|
||||
}
|
||||
|
||||
.card-gradient-2 {
|
||||
background: linear-gradient(135deg, #e6f7fa 0%, #fafdff 100%);
|
||||
box-shadow:
|
||||
0 6px 24px 0 rgba(0, 200, 180, 0.1),
|
||||
0 1.5px 4px 0 rgba(0, 200, 180, 0.08);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import InquireForm from '@/components/InquireForm.vue'
|
||||
|
||||
definePage({ layout: 'default' })
|
||||
|
||||
const feature = ref('')
|
||||
const IMMERSIVE_NAVBAR_ROUTE = 'pages/inquire'
|
||||
|
||||
// 获取产品信息
|
||||
const featureData = ref({})
|
||||
|
||||
onLoad(async (query) => {
|
||||
const q = query || {}
|
||||
feature.value = String(q.feature || '')
|
||||
if (q.out_trade_no) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/report-result-webview?out_trade_no=${encodeURIComponent(String(q.out_trade_no))}`,
|
||||
})
|
||||
return
|
||||
}
|
||||
await getProduct()
|
||||
})
|
||||
|
||||
async function getProduct() {
|
||||
if (!feature.value)
|
||||
return
|
||||
const { data } = await useApiFetch(`/product/en/${feature.value}`, { silent: true })
|
||||
.get()
|
||||
.json()
|
||||
|
||||
if (data.value) {
|
||||
featureData.value = data.value.data
|
||||
}
|
||||
}
|
||||
|
||||
onPageScroll((event) => {
|
||||
uni.$emit('immersive-navbar-change', {
|
||||
route: IMMERSIVE_NAVBAR_ROUTE,
|
||||
solid: event.scrollTop > 120,
|
||||
})
|
||||
})
|
||||
|
||||
onUnload(() => {
|
||||
uni.$emit('immersive-navbar-change', {
|
||||
route: IMMERSIVE_NAVBAR_ROUTE,
|
||||
solid: false,
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InquireForm type="normal" :feature="feature" :feature-data="featureData" />
|
||||
</template>
|
||||
@@ -2,6 +2,7 @@
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, onBeforeMount, ref, watch } from 'vue'
|
||||
import { openCustomerService } from '@/composables/useCustomerService'
|
||||
import { openAgentVipApplyInExternalBrowser } from '@/composables/useAgentVipH5'
|
||||
import { useAgentStore } from '@/stores/agentStore'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
import { useUserStore } from '@/stores/userStore'
|
||||
@@ -75,9 +76,7 @@ function maskName(name) {
|
||||
return `${name.substring(0, 3)}****${name.substring(7)}`
|
||||
}
|
||||
|
||||
function toHistory() {
|
||||
uni.navigateTo({ url: '/pages/history-query' })
|
||||
}
|
||||
|
||||
function toPromote() {
|
||||
uni.navigateTo({ url: '/pages/promote' })
|
||||
}
|
||||
@@ -127,7 +126,7 @@ function toVipConfig() {
|
||||
}
|
||||
|
||||
function toVipRenewal() {
|
||||
uni.navigateTo({ url: '/pages/agent-vip-apply' })
|
||||
openAgentVipApplyInExternalBrowser()
|
||||
}
|
||||
|
||||
function formatExpiryTime(expiryTimeStr) {
|
||||
@@ -298,17 +297,6 @@ onBeforeMount(() => {
|
||||
</view>
|
||||
<image src="/static/images/me/right.png" class="h-4 w-4" alt="右箭头" />
|
||||
</view>
|
||||
<view
|
||||
class="w-full flex items-center justify-between border-b border-gray-100 px-6 py-4 transition-colors hover:bg-blue-50"
|
||||
@click="toHistory">
|
||||
<view class="flex items-center gap-3">
|
||||
<image src="/static/images/index/wdbg.png" class="h-6 w-6" alt="我的报告" />
|
||||
<text class="text-gray-700 font-medium">
|
||||
我的报告
|
||||
</text>
|
||||
</view>
|
||||
<image src="/static/images/me/right.png" class="h-4 w-4" alt="右箭头" />
|
||||
</view>
|
||||
<view
|
||||
class="w-full flex items-center justify-between border-b border-gray-100 px-6 py-4 transition-colors hover:bg-blue-50"
|
||||
@click="toUserAgreement">
|
||||
|
||||
@@ -105,13 +105,13 @@ onShow(() => {
|
||||
</view>
|
||||
<view class="permissions">
|
||||
<view class="permission-item">
|
||||
1. 网络权限:用于连接服务、加载业务数据与提交操作请求。
|
||||
1. 网络权限:用于连接服务、加载页面与提交查询、支付等操作。
|
||||
</view>
|
||||
<view class="permission-item">
|
||||
2. 存储权限:用于缓存页面数据、保存登录状态与图片文件。
|
||||
2. 相册/存储写入:仅在您点击保存推广海报或二维码图片时,将应用内生成的图片保存到手机相册。
|
||||
</view>
|
||||
<view class="permission-item">
|
||||
3. 设备信息权限:用于运行稳定性、安全风控与异常排查。
|
||||
3. 支付相关能力:在您主动发起支付宝支付时,由支付服务完成交易(具体权限以支付 SDK 为准)。
|
||||
</view>
|
||||
</view>
|
||||
<view class="hint">
|
||||
|
||||
@@ -19,6 +19,9 @@ const formData = ref({
|
||||
clientPrice: null,
|
||||
})
|
||||
|
||||
/** 从首页报告卡片带入的 product_en,配置加载后预选 */
|
||||
const initialFeature = ref('')
|
||||
|
||||
const availableReportTypes = computed(() => {
|
||||
if (!productConfig.value?.length)
|
||||
return []
|
||||
@@ -84,6 +87,17 @@ function openPricePicker() {
|
||||
showPricePicker.value = true
|
||||
}
|
||||
|
||||
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)}`,
|
||||
})
|
||||
}
|
||||
|
||||
async function getPromoteConfig() {
|
||||
loadingConfig.value = true
|
||||
try {
|
||||
@@ -91,7 +105,10 @@ async function getPromoteConfig() {
|
||||
if (data.value && !error.value && data.value.code === 200) {
|
||||
const list = data.value.data.AgentProductConfig || []
|
||||
productConfig.value = list
|
||||
const availableType = availableReportTypes.value[0]
|
||||
const preset = initialFeature.value
|
||||
? availableReportTypes.value.find(item => item.value === initialFeature.value)
|
||||
: null
|
||||
const availableType = preset || availableReportTypes.value[0]
|
||||
if (availableType) {
|
||||
selectProductType(availableType.value)
|
||||
}
|
||||
@@ -143,6 +160,12 @@ async function generatePromotionCode() {
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
const feature = options?.feature
|
||||
if (feature)
|
||||
initialFeature.value = decodeURIComponent(String(feature))
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getPromoteConfig()
|
||||
})
|
||||
@@ -163,8 +186,14 @@ onMounted(() => {
|
||||
</view>
|
||||
<VipBanner />
|
||||
|
||||
<view class="card mb-4">
|
||||
<view class="mb-2 text-xl font-semibold">
|
||||
<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">
|
||||
生成推广码
|
||||
</view>
|
||||
<wd-form ref="promotionForm" :model="formData">
|
||||
|
||||
3
src/uni-pages.d.ts
vendored
3
src/uni-pages.d.ts
vendored
@@ -9,7 +9,6 @@ type _LocationUrl =
|
||||
"/pages/agent-promote-details" |
|
||||
"/pages/agent-rewards-details" |
|
||||
"/pages/agent-service-agreement" |
|
||||
"/pages/agent-vip-apply" |
|
||||
"/pages/agent-vip-config" |
|
||||
"/pages/agent-vip" |
|
||||
"/pages/agent" |
|
||||
@@ -18,8 +17,6 @@ type _LocationUrl =
|
||||
"/pages/help-detail" |
|
||||
"/pages/help-guide" |
|
||||
"/pages/help" |
|
||||
"/pages/history-query" |
|
||||
"/pages/inquire" |
|
||||
"/pages/invitation-agent-apply" |
|
||||
"/pages/invitation" |
|
||||
"/pages/launch" |
|
||||
|
||||
Reference in New Issue
Block a user