first commit
Some checks failed
Check / lint (18.x, macos-latest) (push) Has been cancelled
Check / lint (18.x, ubuntu-latest) (push) Has been cancelled
Check / lint (18.x, windows-latest) (push) Has been cancelled
Check / lint (20.x, macos-latest) (push) Has been cancelled
Check / lint (20.x, ubuntu-latest) (push) Has been cancelled
Check / lint (20.x, windows-latest) (push) Has been cancelled
Check / lint (22.x, macos-latest) (push) Has been cancelled
Check / lint (22.x, ubuntu-latest) (push) Has been cancelled
Check / lint (22.x, windows-latest) (push) Has been cancelled
Check / typecheck (18.x, macos-latest) (push) Has been cancelled
Check / typecheck (18.x, ubuntu-latest) (push) Has been cancelled
Check / typecheck (18.x, windows-latest) (push) Has been cancelled
Check / typecheck (20.x, macos-latest) (push) Has been cancelled
Check / typecheck (20.x, ubuntu-latest) (push) Has been cancelled
Check / typecheck (20.x, windows-latest) (push) Has been cancelled
Check / typecheck (22.x, macos-latest) (push) Has been cancelled
Check / typecheck (22.x, ubuntu-latest) (push) Has been cancelled
Check / typecheck (22.x, windows-latest) (push) Has been cancelled
Check / build (build, 18.x, macos-latest) (push) Has been cancelled
Check / build (build, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build, 18.x, windows-latest) (push) Has been cancelled
Check / build (build, 20.x, macos-latest) (push) Has been cancelled
Check / build (build, 20.x, ubuntu-latest) (push) Has been cancelled
Check / build (build, 20.x, windows-latest) (push) Has been cancelled
Check / build (build, 22.x, macos-latest) (push) Has been cancelled
Check / build (build, 22.x, ubuntu-latest) (push) Has been cancelled
Check / build (build, 22.x, windows-latest) (push) Has been cancelled
Check / build (build:app, 18.x, macos-latest) (push) Has been cancelled
Check / build (build:app, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:app, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:app, 20.x, macos-latest) (push) Has been cancelled
Check / build (build:app, 20.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:app, 20.x, windows-latest) (push) Has been cancelled
Check / build (build:app, 22.x, macos-latest) (push) Has been cancelled
Check / build (build:app, 22.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:app, 22.x, windows-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, macos-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 20.x, macos-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 20.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 20.x, windows-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 22.x, macos-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 22.x, ubuntu-latest) (push) Has been cancelled
Check / build (build:mp-weixin, 22.x, windows-latest) (push) Has been cancelled

This commit is contained in:
2026-02-10 14:13:29 +08:00
commit e8dea8b561
129 changed files with 21556 additions and 0 deletions

287
src/pages/agent.vue Normal file
View File

@@ -0,0 +1,287 @@
<script setup>
import { ref, computed } from 'vue'
import { getAgentRevenue } from '@/api/apis'
import GzhQrcode from '@/components/GzhQrcode.vue'
// 日期选项映射
const dateRangeMap = {
today: 'today',
week: 'last7d',
month: 'last30d'
}
// 日期文本映射
const dateTextMap = {
today: '今日',
week: '近7天',
month: '近1月'
}
// 直推报告数据
const promoteDateOptions = [
{ label: '今日', value: 'today' },
{ label: '近7天', value: 'week' },
{ label: '近1月', value: 'month' }
]
const selectedPromoteDate = ref('today')
// 活跃下级数据
const activeDateOptions = [
{ label: '今日', value: 'today' },
{ label: '近7天', value: 'week' },
{ label: '近1月', value: 'month' }
]
const selectedActiveDate = ref('today')
const data = ref(null)
// 控制公众号二维码弹窗显示
const showGzhQrcode = ref(false)
// 计算当前直推数据
const currentPromoteData = computed(() => {
const range = dateRangeMap[selectedPromoteDate.value]
return data.value?.direct_push?.[range] || { commission: 0, report: 0 }
})
// 计算当前活跃数据
const currentActiveData = computed(() => {
const range = dateRangeMap[selectedActiveDate.value]
return data.value?.active_reward?.[range] || {
active_reward: 0,
sub_promote_reward: 0,
sub_upgrade_reward: 0,
sub_withdraw_reward: 0
}
})
// 计算日期文本
const promoteTimeText = computed(() => {
return dateTextMap[selectedPromoteDate.value] || '今日'
})
const activeTimeText = computed(() => {
return dateTextMap[selectedActiveDate.value] || '今日'
})
const getData = async () => {
try {
const res = await getAgentRevenue()
if (res.code === 200) {
data.value = res.data
}
} catch (error) {
console.error(error)
}
}
onBeforeMount(() => {
if (uni.getStorageSync('token')) {
getData()
}
})
// 路由跳转
function goToPromoteDetail() {
uni.navigateTo({
url: '/pages/promoteDetails'
})
}
function goToActiveDetail() {
uni.navigateTo({
url: '/pages/rewardsDetails'
})
}
function toWithdraw() {
// 弹出公众号二维码提示提现
showGzhQrcode.value = true
}
// 关闭公众号二维码弹窗
function closeGzhQrcode() {
showGzhQrcode.value = false
}
function toWithdrawDetails() {
uni.navigateTo({
url: '/pages/withdrawDetails'
})
}
</script>
<template>
<view class="safe-area-top min-h-screen">
<view class="p-4">
<!-- 资产卡片 -->
<view class="rounded-xl shadow-lg mb-4 bg-gradient-to-r from-blue-50/70 to-blue-100/50 p-6">
<view class="flex justify-between items-center mb-3">
<view class="flex items-center">
<text class="text-lg font-bold text-gray-800">余额</text>
</view>
<text class="text-3xl text-blue-600 font-bold">¥ {{ (data?.balance || 0).toFixed(2) }}</text>
</view>
<view class="text-sm text-gray-500 mb-2">累计收益¥ {{ (data?.total_earnings || 0).toFixed(2) }}</view>
<view class="text-sm text-gray-500 mb-6">冻结余额¥ {{ (data?.frozen_balance || 0).toFixed(2) }}</view>
<view class="grid grid-cols-2 gap-3">
<view @click="toWithdraw"
class="bg-gradient-to-r from-blue-500 to-blue-400 text-white rounded-full py-2 shadow-md flex items-center justify-center">
<text>提现</text>
</view>
<view @click="toWithdrawDetails"
class="bg-white/90 text-gray-600 border border-gray-200/50 rounded-full py-2 shadow-sm flex items-center justify-center">
<text>提现记录</text>
</view>
</view>
</view>
<!-- 直推报告收益 -->
<view class="rounded-xl shadow-lg mb-4 bg-gradient-to-r from-blue-50/40 to-cyan-50/50 p-6">
<view class="flex justify-between items-center mb-4">
<view class="flex items-center">
<text class="text-lg font-bold text-gray-800">直推报告收益</text>
</view>
<view class="text-right">
<text class="text-2xl text-blue-600 font-bold">¥ {{ (data?.direct_push?.total_commission || 0).toFixed(2)
}}</text>
<view class="text-sm text-gray-500 mt-1">有效报告 {{ data?.direct_push?.total_report || 0 }} </view>
</view>
</view>
<!-- 日期选择 -->
<view class="grid grid-cols-3 gap-2 mb-6">
<view v-for="item in promoteDateOptions" :key="item.value" @click="selectedPromoteDate = item.value" :class="[
'rounded-full transition-all py-1 px-4 text-sm text-center',
selectedPromoteDate === item.value
? 'bg-blue-500 text-white shadow-md'
: 'bg-white/90 text-gray-600 border border-gray-200/50'
]">
{{ item.label }}
</view>
</view>
<view class="grid grid-cols-2 gap-4 mb-6">
<view class="bg-blue-50/60 p-3 rounded-lg backdrop-blur-sm">
<view class="flex items-center text-sm text-gray-500">
<text>{{ promoteTimeText }}收益</text>
</view>
<text class="text-xl text-blue-600 font-bold mt-1">¥ {{ currentPromoteData.commission?.toFixed(2) || '0.00'
}}</text>
</view>
<view class="bg-blue-50/60 p-3 rounded-lg backdrop-blur-sm">
<view class="flex items-center text-sm text-gray-500">
<text>有效报告</text>
</view>
<text class="text-xl text-blue-600 font-bold mt-1">{{ currentPromoteData.report || 0 }} </text>
</view>
</view>
<view class="flex items-center justify-between text-blue-500 text-sm font-semibold" @click="goToPromoteDetail">
<text>查看收益明细</text>
<text class="text-lg"></text>
</view>
</view>
<!-- 活跃下级奖励 -->
<view class="rounded-xl shadow-lg bg-gradient-to-r from-green-50/40 to-cyan-50/30 p-6">
<view class="flex justify-between items-center mb-4">
<view class="flex items-center">
<text class="text-lg font-bold text-gray-800">活跃下级奖励</text>
</view>
<view class="text-right">
<text class="text-2xl text-green-600 font-bold">¥ {{ (data?.active_reward?.total_reward || 0).toFixed(2)
}}</text>
<view class="text-sm text-gray-500 mt-1">活跃下级 0 </view>
</view>
</view>
<!-- 日期选择 -->
<view class="grid grid-cols-3 gap-2 mb-6">
<view v-for="item in activeDateOptions" :key="item.value" @click="selectedActiveDate = item.value" :class="[
'rounded-full transition-all py-1 px-4 text-sm text-center',
selectedActiveDate === item.value
? 'bg-green-500 text-white shadow-md'
: 'bg-white/90 text-gray-600 border border-gray-200/50'
]">
{{ item.label }}
</view>
</view>
<view class="grid grid-cols-2 gap-2 mb-6">
<view class="bg-green-50/60 p-3 rounded-lg backdrop-blur-sm">
<view class="flex items-center text-sm text-gray-500">
<text>{{ activeTimeText }}奖励</text>
</view>
<text class="text-xl text-green-600 font-bold mt-1">¥ {{ (currentActiveData.active_reward || 0).toFixed(2)
}}</text>
</view>
<view class="bg-green-50/60 p-3 rounded-lg backdrop-blur-sm">
<view class="flex items-center text-sm text-gray-500">
<text>下级推广奖励</text>
</view>
<text class="text-xl text-green-600 font-bold mt-1">¥ {{ (currentActiveData.sub_promote_reward ||
0).toFixed(2) }}</text>
</view>
<view class="bg-green-50/60 p-3 rounded-lg backdrop-blur-sm">
<view class="flex items-center text-sm text-gray-500">
<text>新增活跃奖励</text>
</view>
<text class="text-xl text-green-600 font-bold mt-1">¥ {{ (currentActiveData.sub_upgrade_reward ||
0).toFixed(2) }}</text>
</view>
<view class="bg-green-50/60 p-3 rounded-lg backdrop-blur-sm">
<view class="flex items-center text-sm text-gray-500">
<text>下级转化奖励</text>
</view>
<text class="text-xl text-green-600 font-bold mt-1">¥ {{ (currentActiveData.sub_withdraw_reward ||
0).toFixed(2) }}</text>
</view>
</view>
<view class="flex items-center justify-between text-green-500 text-sm font-semibold" @click="goToActiveDetail">
<text>查看奖励明细</text>
<text class="text-lg"></text>
</view>
<!-- 添加查看下级按钮 -->
<!-- <view class="mt-4 px-4">
<view
@click="toSubordinateList"
class=" mx-auto bg-gradient-to-r from-green-500 to-green-400 text-white rounded-full py-2 px-4 shadow-md flex items-center justify-center active:opacity-80"
hover-class="opacity-90 scale-98"
>
<text>查看我的下级</text>
</view>
</view> -->
</view>
</view>
<!-- 公众号二维码弹窗 -->
<GzhQrcode
:visible="showGzhQrcode"
type="withdraw"
@close="closeGzhQrcode"
/>
</view>
</template>
<style>
button {
transition: all 0.2s ease;
}
button:hover {
transform: translateY(-1px);
}
</style>
<route lang="json">{
"layout": "home",
"style": {
"navigationBarTextStyle": "black",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#e3f0ff"
}
}</route>

63
src/pages/agentVip.vue Normal file
View File

@@ -0,0 +1,63 @@
<template>
<view class="relative">
<image class="w-full" src="/static/image/vip_bg.png" mode="widthFix" />
<view @click="toService" class="service-btn">
点击马上报名
</view>
</view>
</template>
<script setup>
function toService() {
// 直接使用plus.runtime.openURL在系统浏览器中打开链接
// #ifdef APP-PLUS
plus.runtime.openURL('https://work.weixin.qq.com/kfid/kfc5c19b2b93a5e73b9');
// #endif
// #ifdef H5
window.location.href = 'https://work.weixin.qq.com/kfid/kfc5c19b2b93a5e73b9';
// #endif
// #ifdef MP
uni.navigateTo({
url: '/pages/agreement?url=' + encodeURIComponent('https://work.weixin.qq.com/kfid/kfc5c19b2b93a5e73b9')
});
// #endif
}
</script>
<style lang="scss" scoped>
.relative {
position: relative;
width: 100%;
height: 100%;
}
.service-btn {
position: absolute;
left: 50%;
bottom: 600rpx;
transform: translateX(-50%);
background: linear-gradient(to right, #2d3748, #000000, #2d3748);
padding: 10rpx 20rpx;
border-radius: 16rpx;
color: #ffffff;
font-size: 48rpx;
font-weight: bold;
box-shadow: 0 0 30rpx rgba(255, 255, 255, 0.3);
transition: transform 0.3s;
&:active {
transform: translateX(-50%) scale(1.05);
}
}
</style>
<route lang="json">
{
"layout": "page",
"title": "代理会员",
"agent": true,
"auth": true
}
</route>

816
src/pages/agentVipApply.vue Normal file
View File

@@ -0,0 +1,816 @@
<template>
<view class="agent-VIP-apply w-full min-h-screen bg-gradient-to-b from-amber-50 via-amber-100 to-amber-50 pb-24">
<!-- 装饰元素 -->
<view
class="absolute top-0 right-0 w-32 h-32 bg-gradient-to-br from-amber-300 to-amber-500 rounded-bl-full opacity-20">
</view>
<view
class="absolute top-40 left-0 w-16 h-16 bg-gradient-to-tr from-amber-400 to-amber-600 rounded-tr-full opacity-20">
</view>
<view
class="absolute bottom-60 right-0 w-24 h-24 bg-gradient-to-bl from-amber-300 to-amber-500 rounded-tl-full opacity-20">
</view>
<!-- 顶部标题区域 -->
<view class="header relative pt-8 px-4 pb-6 text-center">
<view
class="animate-pulse absolute -top-2 left-1/2 -translate-x-1/2 w-24 h-1 bg-gradient-to-r from-amber-300 via-amber-500 to-amber-300 rounded-full">
</view>
<text class="text-3xl font-bold text-amber-800 mb-1 block">
{{ isVipOrSvip ? '代理会员续费' : 'VIP代理申请' }}
</text>
<view class="text-sm text-amber-700 mt-2 max-w-xs mx-auto">
<block v-if="isVipOrSvip">
您的会员有效期至 {{ formatExpiryTime(ExpiryTime) }}续费后有效期至
{{ renewalExpiryTime }}
</block>
<block v-else>
平台为疯狂推广者定制的赚买计划助您收益<text class="text-red-500 font-bold">翻倍增升</text>
</block>
</view>
<!-- 装饰性金币图标 -->
<view class="absolute top-6 left-4 transform -rotate-12">
<view
class="w-8 h-8 bg-gradient-to-br from-yellow-300 to-yellow-500 rounded-full flex items-center justify-center shadow-lg">
<text class="text-white font-bold text-xs">¥</text>
</view>
</view>
<view class="absolute top-10 right-6 transform rotate-12">
<view
class="w-6 h-6 bg-gradient-to-br from-yellow-400 to-yellow-600 rounded-full flex items-center justify-center shadow-lg">
<text class="text-white font-bold text-xs">¥</text>
</view>
</view>
</view>
<!-- 选择代理类型 -->
<view class="card-container px-4 mb-8">
<view class="bg-white rounded-xl shadow-lg overflow-hidden border border-amber-100 transform transition-all">
<view
class="bg-gradient-to-r from-amber-500 to-amber-600 text-white py-3 px-4 text-center font-bold relative overflow-hidden">
<text class="relative z-10">选择代理类型</text>
<view class="absolute inset-0 bg-amber-500 opacity-30">
<view
class="absolute top-0 left-0 w-full h-full bg-gradient-to-r from-transparent via-white to-transparent opacity-20 transform -skew-x-30 translate-x-full animate-shimmer">
</view>
</view>
</view>
<view class="flex p-6 gap-4">
<view
class="flex-1 border-2 rounded-lg p-4 text-center cursor-pointer transition-all duration-300 relative transform 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')">
<text class="text-xl font-bold text-amber-700 block">VIP代理</text>
<text class="text-amber-600 font-bold mt-1 text-lg block">{{ vipConfig.price }}{{ vipConfig.priceUnit
}}</text>
<text class="mt-2 text-gray-600 text-sm block">标准VIP权益</text>
<view v-if="selectedType === 'vip'"
class="absolute -top-2 -right-2 w-6 h-6 bg-gradient-to-br from-amber-500 to-amber-600 rounded-full flex items-center justify-center shadow-md">
<wd-icon name="check" color="#fff" size="14" />
</view>
</view>
<view
class="flex-1 border-2 rounded-lg p-4 text-center cursor-pointer transition-all duration-300 relative transform 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')">
<text class="text-xl font-bold text-amber-700 block">SVIP代理</text>
<text class="text-amber-600 font-bold mt-1 text-lg block">{{ vipConfig.svipPrice }}{{ vipConfig.priceUnit
}}</text>
<text class="mt-2 text-gray-600 text-sm block">超级VIP权益</text>
<view v-if="selectedType === 'svip'"
class="absolute -top-2 -right-2 w-6 h-6 bg-gradient-to-br from-amber-500 to-amber-600 rounded-full flex items-center justify-center shadow-md">
<wd-icon name="check" color="#fff" size="14" />
</view>
</view>
</view>
</view>
</view>
<!-- 六大超值权益 -->
<view class="card-container px-4 mb-8">
<view class="bg-white rounded-xl shadow-lg overflow-hidden border border-amber-100">
<view
class="bg-gradient-to-r from-amber-500 to-amber-600 text-white py-3 px-4 text-center font-bold relative overflow-hidden">
<text class="relative z-10">六大超值权益</text>
<view class="absolute inset-0 bg-amber-500 opacity-30">
<view
class="absolute top-0 left-0 w-full h-full bg-gradient-to-r from-transparent via-white to-transparent opacity-20 transform -skew-x-30 translate-x-full animate-shimmer">
</view>
</view>
</view>
<view class="grid grid-cols-2 gap-4 p-4">
<!-- 权益1 -->
<view
class="bg-gradient-to-br from-amber-50 to-amber-100 rounded-lg p-3 border border-amber-200 transition-all duration-300 hover:shadow-md hover:border-amber-300">
<view class="text-amber-800 font-bold mb-2 flex items-center">
<text
class="w-6 h-6 bg-gradient-to-br from-amber-500 to-amber-600 rounded-full flex items-center justify-center text-white text-xs mr-2">1</text>
下级贡献收益
</view>
<text class="text-sm text-gray-600 block">
下级完全收益您来定涨多少赚多少一单最高收益<text class="text-red-500 font-bold">10</text>
</text>
</view>
<!-- 权益2 -->
<view
class="bg-gradient-to-br from-amber-50 to-amber-100 rounded-lg p-3 border border-amber-200 transition-all duration-300 hover:shadow-md hover:border-amber-300">
<view class="text-amber-800 font-bold mb-2 flex items-center">
<text
class="w-6 h-6 bg-gradient-to-br from-amber-500 to-amber-600 rounded-full flex items-center justify-center text-white text-xs mr-2">2</text>
下级提现收益
</view>
<text class="text-sm text-gray-600 block">
下级定价标准由您定超过标准部分收益更丰厚一单最高多赚<text class="text-red-500 font-bold">10</text>
</text>
</view>
<!-- 权益3 -->
<view
class="bg-gradient-to-br from-amber-50 to-amber-100 rounded-lg p-3 border border-amber-200 transition-all duration-300 hover:shadow-md hover:border-amber-300">
<view class="text-amber-800 font-bold mb-2 flex items-center">
<text
class="w-6 h-6 bg-gradient-to-br from-amber-500 to-amber-600 rounded-full flex items-center justify-center text-white text-xs mr-2">3</text>
转换高额奖励
</view>
<text class="text-sm text-gray-600 block">
下级成为VIPSVIP高额奖励立马发放<text class="text-red-500 font-bold">399</text>
</text>
</view>
<!-- 权益4 -->
<view
class="bg-gradient-to-br from-amber-50 to-amber-100 rounded-lg p-3 border border-amber-200 transition-all duration-300 hover:shadow-md hover:border-amber-300">
<view class="text-amber-800 font-bold mb-2 flex items-center">
<text
class="w-6 h-6 bg-gradient-to-br from-amber-500 to-amber-600 rounded-full flex items-center justify-center text-white text-xs mr-2">4</text>
下级提现奖励
</view>
<text class="text-sm text-gray-600 block">下级成为SVIP每次提现都奖励1%坐享被动收入</text>
</view>
<!-- 权益5 -->
<view
class="bg-gradient-to-br from-amber-50 to-amber-100 rounded-lg p-3 border border-amber-200 transition-all duration-300 hover:shadow-md hover:border-amber-300">
<view class="text-amber-800 font-bold mb-2 flex items-center">
<text
class="w-6 h-6 bg-gradient-to-br from-amber-500 to-amber-600 rounded-full flex items-center justify-center text-white text-xs mr-2">5</text>
月度现金奖励
</view>
<text class="text-sm text-gray-600 block">
下级每月活跃达100名额外奖励<text class="text-red-500 font-bold">50</text>新增15名活跃下级再得<text
class="text-red-500 font-bold">50</text>
</text>
</view>
<!-- 权益6 -->
<view
class="bg-gradient-to-br from-amber-50 to-amber-100 rounded-lg p-3 border border-amber-200 transition-all duration-300 hover:shadow-md hover:border-amber-300">
<view class="text-amber-800 font-bold mb-2 flex items-center">
<text
class="w-6 h-6 bg-gradient-to-br from-amber-500 to-amber-600 rounded-full flex items-center justify-center text-white text-xs mr-2">6</text>
平台专项扶持
</view>
<text class="text-sm text-gray-600 block">一对一专属客服服务为合作伙伴提供全方位成长赋能</text>
</view>
</view>
</view>
</view>
<!-- 权益对比表 -->
<view class="card-container px-4 mb-8" v-if="selectedType">
<view class="bg-white rounded-xl shadow-lg overflow-hidden border border-amber-100">
<view
class="bg-gradient-to-r from-amber-500 to-amber-600 text-white py-3 px-4 text-center font-bold relative overflow-hidden">
<text class="relative z-10">{{ selectedType === 'vip' ? 'VIP' : 'SVIP' }}代理权益对比</text>
<view class="absolute inset-0 bg-amber-500 opacity-30">
<view
class="absolute top-0 left-0 w-full h-full bg-gradient-to-r from-transparent via-white to-transparent opacity-20 transform -skew-x-30 translate-x-full animate-shimmer">
</view>
</view>
</view>
<view class="p-4">
<!-- 权益对比表格 -->
<view class="w-full border border-amber-200 rounded-lg overflow-hidden">
<!-- 表头 -->
<view class="flex bg-gradient-to-r from-amber-100 to-amber-200">
<view class="w-16 flex-shrink-0 p-3 border-r border-amber-200 text-left font-bold text-amber-800">权益项目
</view>
<view class="flex-1 flex-shrink-0 p-3 border-r border-amber-200 text-center font-bold text-amber-800">普通代理
</view>
<view class="flex-1 flex-shrink-0 p-3 border-r border-amber-200 text-center font-bold text-amber-800"
:class="{ 'bg-amber-200': selectedType === 'vip' }">VIP代理</view>
<view class="flex-1 flex-shrink-0 p-3 text-center font-bold text-amber-800"
:class="{ 'bg-amber-200': selectedType === 'svip' }">SVIP代理</view>
</view>
<!-- 表格内容 -->
<view v-for="(item, index) in benefitsComparisonData" :key="index"
class="flex border-b border-amber-200 last:border-b-0" :class="{ 'bg-amber-50': index % 2 === 1 }">
<view class="w-16 flex-shrink-0 p-3 border-r border-amber-200 text-left font-medium">{{ item.benefit }}
</view>
<view class="flex-1 flex-shrink-0 p-3 border-r border-amber-200 text-center text-sm">{{ item.normal }}
</view>
<view class="flex-1 flex-shrink-0 p-3 border-r border-amber-200 text-center text-sm" :class="{
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
}">{{ item.vip }}</view>
<view class="flex-1 flex-shrink-0 p-3 text-center text-sm" :class="{
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
}">{{ item.svip }}</view>
</view>
</view>
</view>
</view>
</view>
<!-- 收益预估 -->
<view class="card-container px-4 mb-8" v-if="selectedType">
<view class="bg-white rounded-xl shadow-lg overflow-hidden border border-amber-100">
<view
class="bg-gradient-to-r from-amber-500 to-amber-600 text-white py-3 px-4 text-center font-bold relative overflow-hidden">
<text class="relative z-10">收益预估对比</text>
<view class="absolute inset-0 bg-amber-500 opacity-30">
<view
class="absolute top-0 left-0 w-full h-full bg-gradient-to-r from-transparent via-white to-transparent opacity-20 transform -skew-x-30 translate-x-full animate-shimmer">
</view>
</view>
</view>
<view class="p-4">
<!-- 顶部收益概览 -->
<view class="mb-6 rounded-lg overflow-hidden border border-amber-200">
<view class="bg-gradient-to-r from-amber-100 to-amber-200 py-2 px-4 text-center font-bold text-amber-800">
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' }">
<text class="text-sm text-gray-600 mb-1 block">VIP月预计收益</text>
<text class="text-amber-600 font-bold text-xl block">{{ revenueData.vipMonthly }}</text>
<text class="text-xs text-gray-500 mt-1 block">年收益{{ revenueData.vipYearly }}</text>
</view>
<view class="p-4 text-center" :class="{ 'bg-amber-50': selectedType === 'svip' }">
<text class="text-sm text-gray-600 mb-1 block">SVIP月预计收益</text>
<text class="text-red-500 font-bold text-xl block">{{ revenueData.svipMonthly }}</text>
<text class="text-xs text-gray-500 mt-1 block">年收益{{ revenueData.svipYearly }}</text>
</view>
</view>
<view class="bg-gradient-to-r from-red-50 to-red-100 py-2 px-4 text-center text-red-600 font-medium">
选择SVIP相比VIP月增收益<text class="font-bold">{{ revenueData.monthlyDifference }}</text>
</view>
</view>
<!-- 详细收益表格 -->
<view>
<view class="w-full border border-amber-200 rounded-lg overflow-hidden">
<!-- 表头 -->
<view class="flex bg-gradient-to-r from-amber-100 to-amber-200">
<view class="w-24 flex-shrink-0 p-3 border-r border-amber-200 text-left font-bold text-amber-800">收益来源
</view>
<view class="flex-1 flex-shrink-0 p-3 border-r border-amber-200 text-center font-bold text-amber-800"
:class="{ 'bg-amber-200': selectedType === 'vip' }">VIP代理</view>
<view class="flex-1 flex-shrink-0 p-3 text-center font-bold text-amber-800"
:class="{ 'bg-amber-200': selectedType === 'svip' }">SVIP代理</view>
</view>
<!-- 表格内容 -->
<view v-for="(item, index) in revenueComparisonData" :key="index"
class="flex border-b border-amber-200 last:border-b-0" :class="{
'bg-amber-50': index % 2 === 1,
'bg-gradient-to-r from-amber-50 to-amber-100 font-bold': item.source.includes('收益')
}">
<view class="w-24 flex-shrink-0 p-3 border-r border-amber-200 text-left font-medium">{{ item.source }}
</view>
<view class="flex-1 flex-shrink-0 p-3 border-r border-amber-200 text-center text-sm" :class="{
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
'text-amber-700': item.source.includes('收益'),
'border-amber-300': item.source.includes('收益') && selectedType === 'vip'
}">{{ item.vip }}</view>
<view class="flex-1 flex-shrink-0 p-3 text-center text-sm" :class="{
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
'text-red-500': item.source.includes('收益'),
'border-amber-300': item.source.includes('收益') && selectedType === 'svip'
}">{{ item.svip }}</view>
</view>
</view>
</view>
<!-- 投资回报率 -->
<view class="mt-6 p-4 bg-gradient-to-r from-amber-50 to-amber-100 rounded-lg border border-amber-200">
<text class="text-center mb-3 font-bold text-amber-800 block">投资收益分析</text>
<view class="grid grid-cols-1 gap-4">
<view class="p-3 bg-white rounded-lg shadow-sm">
<view class="flex items-center justify-between">
<view class="flex-1 border-r border-amber-100 pr-3">
<text class="text-amber-700 font-medium text-center mb-1 block">VIP方案</text>
<view class="text-center">
<text class="text-amber-600 text-sm block">投资{{ vipConfig.price }}</text>
<text class="text-gray-600 text-sm block">月收益{{ revenueData.vipMonthly }}</text>
</view>
</view>
<view class="flex-1 pl-3">
<text class="text-red-500 font-medium text-center mb-1 block">SVIP方案</text>
<view class="text-center">
<text class="text-red-500 text-sm block">投资{{ vipConfig.svipPrice }}</text>
<text class="text-gray-600 text-sm block">月收益{{ revenueData.svipMonthly }}</text>
</view>
</view>
</view>
</view>
<!-- 升级收益对比 -->
<view class="p-3 bg-gradient-to-r from-red-50 to-amber-50 rounded-lg shadow-sm">
<text class="text-center font-medium text-red-700 mb-2 block">SVIP升级优势分析</text>
<view class="flex items-center justify-center gap-3">
<view class="text-center">
<text class="text-sm text-gray-600 block">额外投资</text>
<text class="text-red-600 font-bold block">{{ revenueData.priceDifference }}</text>
</view>
<view
class="bg-red-500 flex-shrink-0 text-white rounded-full w-6 h-6 flex items-center justify-center">
<text class="transform -translate-y-px"></text>
</view>
<view class="text-center">
<text class="text-sm text-gray-600 block">每月额外收益</text>
<text class="text-red-600 font-bold block">{{ revenueData.monthlyDifference }}</text>
</view>
<view
class="bg-red-500 flex-shrink-0 text-white rounded-full w-6 h-6 flex items-center justify-center">
<text class="transform -translate-y-px"></text>
</view>
<view class="text-center">
<text class="text-sm text-gray-600 block">投资回收时间</text>
<text class="text-red-600 font-bold block">{{ revenueData.recoverDays }}</text>
</view>
</view>
<view class="text-center text-red-500 font-medium mt-3">
额外投资{{ 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 px-4 py-3 bg-gradient-to-t from-amber-100 to-transparent backdrop-blur-sm z-30">
<view class="flex flex-col gap-2">
<button :class="buttonClass" @click="applyVip" :disabled="!canPerformAction">
<text class="relative z-10">{{ buttonText }}</text>
<view
class="absolute top-0 left-0 w-full h-full bg-gradient-to-r from-transparent via-white to-transparent opacity-20 transform -skew-x-30 translate-x-full animate-shimmer">
</view>
</button>
<!-- 最终解释权声明 -->
<text class="text-center text-xs text-gray-400 py-1 block">最终解释权归海南天远大数据科技有限公司所有</text>
</view>
</view>
</view>
<Payment v-model="showPayment" :data="payData" :id="payID" type="agent_vip" @close="showPayment = false" />
</template>
<script setup>
import { ref, onMounted, reactive, computed } from 'vue'
import { useToast } from 'wot-design-uni'
import { activateAgentMembership } from '@/apis/agent'
// 使用 wot-design-ui 的 toast
const toast = useToast()
// 代理相关数据
const level = ref('normal')
const ExpiryTime = ref('')
const isAgent = ref(false)
const agentStatus = ref(0)
const agentID = ref('')
const mobile = ref('')
const isRealName = ref(false)
// 计算是否已经是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 [year, month, day] = dateStr.split('-').map(num => parseInt(num))
// 创建日期对象并加一年
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 scale-btn 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价格配置
const vipConfig = reactive({
price: 399, // VIP价格
svipPrice: 599, // SVIP价格
priceUnit: '元/年',
vipCommission: 1.2, // VIP下级贡献收益(元/单)
svipCommission: 1.5, // SVIP下级贡献收益(元/单)
vipFloatingRate: 5, // VIP下级价格浮动收益率(%)
svipFloatingRate: 10, // SVIP下级价格浮动收益率(%)
vipSingleOrderProfit: 5, // VIP单个订单最高利润(元)
svipSingleOrderProfit: 10, // SVIP单个订单最高利润(元)
withdrawRatio: 1, // 下级提现奖励比率(%)
monthlyRewardForTeam: 50, // 月度团队奖励(元)
monthlyRewardForNewTeam: 50, // 月度新增团队奖励(元)
vipConversionBonus: 299, // VIP下级转化奖励(元)
svipConversionBonus: 399, // SVIP下级转化奖励(元)
vipWithdrawalLimit: 1500, // VIP提现额度(元)
svipWithdrawalLimit: 3000, // SVIP提现额度(元)
vipWithdrawalTimes: 1, // VIP日提现次数
svipWithdrawalTimes: 2, // SVIP日提现次数
})
// 权益对比表数据
const benefitsComparisonData = computed(() => {
return [
{
benefit: '会员权益',
normal: '普通代理 免费',
vip: `${vipConfig.price}${vipConfig.priceUnit}`,
svip: `${vipConfig.svipPrice}${vipConfig.priceUnit}`
},
{
benefit: '下级贡献收益',
normal: '1元/单',
vip: `${vipConfig.vipCommission}元/单`,
svip: `${vipConfig.svipCommission}元/单`
},
{
benefit: '自定义设置下级成本',
normal: '❌',
vip: '✓',
svip: '✓'
},
{
benefit: '下级价格浮动收益',
normal: '❌',
vip: `最高${vipConfig.vipFloatingRate}%`,
svip: `最高${vipConfig.svipFloatingRate}%`
},
{
benefit: '下级提现奖励',
normal: '❌',
vip: '❌',
svip: `${vipConfig.withdrawRatio}%`
},
{
benefit: '下级活跃奖励',
normal: '❌',
vip: `${vipConfig.monthlyRewardForTeam}元/月`,
svip: `${vipConfig.monthlyRewardForTeam}元/月`
},
{
benefit: '新增活跃奖励',
normal: '❌',
vip: `${vipConfig.monthlyRewardForNewTeam}元/月`,
svip: `${vipConfig.monthlyRewardForNewTeam}元/月`
},
{
benefit: '下级转化奖励',
normal: '❌',
vip: `${vipConfig.vipConversionBonus}元*10个`,
svip: `${vipConfig.svipConversionBonus}元*10个`
},
{
benefit: '提现次数额度',
normal: '800元/次',
vip: `${vipConfig.vipWithdrawalLimit}元/次`,
svip: `${vipConfig.svipWithdrawalLimit}元/次`
},
{
benefit: '提现次数',
normal: '1次/日',
vip: '1次/日',
svip: '2次/日'
}
]
})
// 收益对比表数据
const revenueComparisonData = computed(() => {
const revenue = revenueData.value
const withdrawReward = 20000 * (vipConfig.withdrawRatio / 100)
return [
{
source: '推广收益(月)',
vip: '300单×50元=15,000元',
svip: '300单×50元=15,000元'
},
{
source: '下级贡献收益(月)',
vip: `300单×${vipConfig.vipCommission}元=360元`,
svip: `300单×${vipConfig.svipCommission}元=450元`
},
{
source: '下级价格浮动收益(月)',
vip: `100单×100元×${vipConfig.vipFloatingRate}%=500元`,
svip: `200单×100元×${vipConfig.svipFloatingRate}%=2,000元`
},
{
source: '下级提现奖励(月)',
vip: '-',
svip: `${withdrawReward}`
},
{
source: '下级活跃奖励(月)',
vip: `${vipConfig.monthlyRewardForTeam}`,
svip: `${vipConfig.monthlyRewardForTeam}`
},
{
source: '新增活跃奖励(月)',
vip: `${vipConfig.monthlyRewardForNewTeam}`,
svip: `${vipConfig.monthlyRewardForNewTeam}`
},
{
source: '下级转化奖励(月)',
vip: `${vipConfig.vipConversionBonus}×2个=598元`,
svip: `${vipConfig.svipConversionBonus}×2个=798元`
},
{
source: '额外业务收益(月)',
vip: '约3,000元',
svip: '约6,000元'
},
{
source: '月计收益',
vip: `${revenue.vipMonthly}`,
svip: `${revenue.svipMonthly}`
},
{
source: '年计收益',
vip: `${revenue.vipYearly}`,
svip: `${revenue.svipYearly}`
}
]
})
// 计算得出的收益数据
const revenueData = computed(() => {
const baseOrders = 300 // 基础订单数
const pricePerOrder = 50 // 每单价格
const baseRevenue = baseOrders * pricePerOrder // 基础推广收益
const vipCommissionRevenue = baseOrders * vipConfig.vipCommission // VIP下级贡献收益
const svipCommissionRevenue = baseOrders * vipConfig.svipCommission // SVIP下级贡献收益
const vipFloatingRevenue = 100 * 100 * (vipConfig.vipFloatingRate / 100) // VIP浮动收益
const svipFloatingRevenue = 200 * 100 * (vipConfig.svipFloatingRate / 100) // SVIP浮动收益
const vipConversionRevenue = vipConfig.vipConversionBonus * 2 // VIP转化奖励
const svipConversionRevenue = vipConfig.svipConversionBonus * 2 // SVIP转化奖励
const vipExtraRevenue = 3000 // VIP额外收益估计
const svipExtraRevenue = 6000 // SVIP额外收益估计
// 平级提现奖励(只有SVIP才有)
const withdrawReward = 20000 * (vipConfig.withdrawRatio / 100)
// 计算月总收益
const vipMonthlyTotal =
baseRevenue +
vipCommissionRevenue +
vipFloatingRevenue +
vipConfig.monthlyRewardForTeam +
vipConfig.monthlyRewardForNewTeam +
vipConversionRevenue +
vipExtraRevenue
const svipMonthlyTotal =
baseRevenue +
svipCommissionRevenue +
svipFloatingRevenue +
withdrawReward +
vipConfig.monthlyRewardForTeam +
vipConfig.monthlyRewardForNewTeam +
svipConversionRevenue +
svipExtraRevenue
// 计算VIP和SVIP之间的差额
const monthlyDifference = svipMonthlyTotal - vipMonthlyTotal
const priceDifference = vipConfig.svipPrice - vipConfig.price
return {
vipMonthly: Math.round(vipMonthlyTotal),
svipMonthly: Math.round(svipMonthlyTotal),
vipYearly: Math.round(vipMonthlyTotal * 12),
svipYearly: Math.round(svipMonthlyTotal * 12),
monthlyDifference: Math.round(monthlyDifference),
yearlyDifference: Math.round(monthlyDifference * 12),
vipRate: Math.round(vipMonthlyTotal / vipConfig.price),
svipRate: Math.round(svipMonthlyTotal / vipConfig.svipPrice),
priceDifference,
recoverDays: Math.ceil(priceDifference / (monthlyDifference / 30)),
withdrawReward,
}
})
// 初始化代理数据和价格配置
onMounted(async () => {
// 从本地缓存获取代理信息
loadAgentInfo()
// 模拟API请求获取价格
try {
// 未来可以在这里添加实际的API调用
// const response = await uni.request({...});
// 使用API返回的数据更新vipConfig
console.log('价格配置已初始化未来将从API加载')
} catch (error) {
console.error('加载价格配置失败', error)
}
})
// 从本地缓存加载代理信息
const loadAgentInfo = () => {
try {
const agentInfo = uni.getStorageSync('agentInfo')
if (agentInfo) {
level.value = agentInfo.level || 'normal'
ExpiryTime.value = agentInfo.expiryTime || ''
isAgent.value = agentInfo.isAgent || false
agentStatus.value = agentInfo.status || 0
agentID.value = agentInfo.agentID || ''
mobile.value = agentInfo.mobile || ''
isRealName.value = agentInfo.isRealName || false
console.log('代理信息已加载:', agentInfo)
} else {
console.log('未找到代理信息缓存')
}
} catch (error) {
console.error('加载代理信息失败:', error)
}
}
const selectedType = ref('vip') // 默认选择VIP
const showPayment = ref(false)
const payData = ref({
product_name: `${selectedType.value.toUpperCase()}代理`,
sell_price: vipConfig.price,
})
const payID = ref('')
// 选择代理类型
function selectType(type) {
selectedType.value = type
// 更新payData中的价格和产品名称
payData.value = {
product_name: `${type === 'vip' ? 'VIP' : 'SVIP'}代理`,
sell_price: type === 'vip' ? vipConfig.price : vipConfig.svipPrice,
}
}
// 申请VIP或SVIP
async function applyVip() {
// 如果是VIP想升级到SVIP提示联系客服
if (isVip.value && selectedType.value === 'svip') {
contactService()
return
}
// 如果是SVIP要降级到VIP提示不能降级
if (isSvip.value && selectedType.value === 'vip') {
toast.error('SVIP会员不能降级到VIP会员')
return
}
try {
const res = await activateAgentMembership({
type: selectedType.value.toUpperCase(),
})
if (res.code === 200) {
if (res.data.id) {
payID.value = res.data.id
showPayment.value = true
uni.requestPayment({
"provider": "wxpay",
"orderInfo": {
"appid": "wx499********7c70e", // 微信开放平台 - 应用 - AppId注意和微信小程序、公众号 AppId 可能不一致
"noncestr": "c5sEwbaNPiXAF3iv", // 随机字符串
"package": "Sign=WXPay", // 固定值
"partnerid": "148*****52", // 微信支付商户号
"prepayid": "wx202254********************fbe90000", // 统一下单订单号
"timestamp": 1597935292, // 时间戳(单位:秒)
"sign": "A842B45937F6EFF60DEC7A2EAA52D5A0" // 签名,这里用的 MD5/RSA 签名
},
success(res) { },
fail(e) { }
})
}
} else {
toast.error(res.msg || '申请失败')
}
} catch (error) {
console.error('申请VIP失败:', error)
toast.error('网络请求失败,请稍后重试')
}
}
function formatExpiryTime(expiryTimeStr) {
if (!expiryTimeStr) return '未知'
// 从格式化字符串中提取日期部分
return expiryTimeStr.split(' ')[0] // 假设格式是 "YYYY-MM-DD HH:MM:SS"
}
</script>
<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;
}
.scale-btn:active {
transform: scale(0.98);
}
</style>
<route type="page" lang="json">{
"layout": "page",
"title": "开通代理会员",
"agent": true,
"auth": true
}</route>

View File

@@ -0,0 +1,513 @@
<template>
<view class="p-4 mx-auto min-h-screen">
<!-- 标题部分 -->
<view class="card mb-4 p-4 bg-gradient-to-r from-blue-500 to-blue-600 rounded-lg shadow-lg text-white">
<text class="text-2xl font-extrabold mb-2 block">专业报告定价配置</text>
<text class="opacity-90 block">请选择报告类型并设置定价策略助您实现精准定价</text>
</view>
<view class="mb-4 bg-white rounded-lg overflow-hidden px-4 flex items-center justify-between">
<span class="text-blue-600 font-medium text-sm">📝 选择报告</span>
<wd-picker custom-class="flex-1" v-model="selectedReportText" :columns="reportOptions" title="选择报告类型"
@confirm="onConfirm" />
</view>
<view v-if="selectedReportText" class="space-y-6">
<!-- 配置卡片 -->
<view class="card">
<!-- 当前报告标题 -->
<view class="flex items-center mb-6">
<text class="text-xl font-semibold text-gray-800">
{{ selectedReportText }}配置
</text>
</view>
<!-- 显示当前产品的基础成本信息 -->
<view v-if="productConfigData && productConfigData.cost_price"
class="px-4 py-2 mb-4 bg-gray-50 border border-gray-200 rounded-lg shadow-sm">
<text class="text-lg font-semibold text-gray-700 block">报告基础配置信息</text>
<view class="mt-1 text-sm text-gray-600">
<text class="block">基础成本价<text class="font-medium">{{ productConfigData.cost_price }}</text> </text>
<text class="block">最高设定金额上限<text class="font-medium">{{ productConfigData.price_range_max }}</text>
</text>
<text class="block">最高设定比例上限<text class="font-medium">{{ priceRatioMax }}</text> %</text>
</view>
</view>
<!-- 分隔线 -->
<view class="my-6 flex items-center justify-center">
<view class="bg-gray-200 h-px flex-1"></view>
<text class="mx-2 text-gray-400 text-sm">成本策略配置</text>
<view class="bg-gray-200 h-px flex-1"></view>
</view>
<!-- 表单部分 -->
<wd-form>
<!-- 加价金额 -->
<wd-form-item label="加价金额" prop="price_increase_amount">
<wd-input v-model="configData.price_increase_amount" type="number" placeholder="0"
@blur="validateDecimal('price_increase_amount')" />
</wd-form-item>
<view class="text-xs text-gray-400 mt-1">
<text class="block">提示最大加价金额为{{ priceIncreaseAmountMax }}</text>
<text class="block">说明加价金额是在基础成本价上增加的额外费用决定下级报告的最低定价您将获得所有输入的金额利润</text>
</view>
<!-- 分隔线 -->
<view class="my-6 flex items-center justify-center">
<view class="bg-gray-200 h-px flex-1"></view>
<text class="mx-2 text-gray-400 text-sm">定价策略配置</text>
<view class="bg-gray-200 h-px flex-1"></view>
</view>
<!-- 定价区间最低 -->
<wd-form-item label="定价区间最低" prop="price_range_from">
<wd-input v-model="configData.price_range_from" type="number" placeholder="0"
@blur="() => { validateDecimal('price_range_from'); validateRange(); }" />
</wd-form-item>
<view class="text-xs text-gray-400 mt-1">
<text class="block">提示定价区间最低金额不能低于基础最低 {{ productConfigData?.price_range_min || 0 }} + 加价金额</text>
<text class="block">说明设定的定价区间最低金额为定价区间的起始值若下级设定的报告金额在区间内则区间内部分将按比例获得收益</text>
</view>
<!-- 定价区间最高 -->
<wd-form-item label="定价区间最高" prop="price_range_to">
<wd-input v-model="configData.price_range_to" type="number" placeholder="0"
@blur="() => { validateDecimal('price_range_to'); validateRange(); }" />
</wd-form-item>
<view class="text-xs text-gray-400 mt-1">
<text class="block">提示定价区间最高金额不能超过上限{{ productConfigData?.price_range_max || 0 }}和大于定价区间最低金额{{
priceIncreaseMax
}}</text>
<text class="block">说明设定的定价区间最高金额为定价区间的结束值若下级设定的报告金额在区间内则区间内部分将按比例获得收益</text>
</view>
<!-- 收取比例 -->
<wd-form-item label="收取比例" prop="price_ratio">
<wd-input v-model="configData.price_ratio" type="number" placeholder="0" @blur="validateRatio" />
</wd-form-item>
<view class="text-xs text-gray-400 mt-1">
<text class="block">提示最大收取比例为{{ priceRatioMax }}%</text>
<text class="block">说明收取比例表示对定价区间内即报告金额超过最低金额小于最高金额的部分的金额按此比例进行利润分成</text>
</view>
</wd-form>
</view>
<!-- 保存按钮 -->
<button type="primary" class="bg-blue-500 text-white py-1 rounded-xl w-full" @click="handleSubmit">
保存当前报告配置
</button>
</view>
<!-- 未选择提示 -->
<view v-else class="text-center py-12">
<text class="text-gray-400 text-4xl block mb-4"></text>
<text class="text-gray-500 block">请先选择需要配置的报告类型</text>
</view>
</view>
</template>
<script setup>
import { ref, reactive, computed, onMounted, watch } from 'vue'
import { getAgentMembershipUserConfig, saveAgentMembershipUserConfig } from '@/apis/agent'
// 报告类型选项
const reportOptions = [
{ label: '人事背调', value: 1 },
{ label: '老板企业报告', value: 2 },
{ label: '家政风险', value: 3 },
{ label: '婚恋风险', value: 4 },
{ label: '贷前背调', value: 5 },
{ label: '租赁风险', value: 6 },
{ label: '个人风险', value: 7 },
]
// 状态管理
const showPicker = ref(false)
const selectedReportId = ref(1)
const selectedReportText = ref('人事背调')
const configData = ref({})
const productConfigData = ref({})
const priceIncreaseMax = ref(null)
const priceIncreaseAmountMax = ref(null)
const priceRatioMax = ref(null)
const rangeError = ref(false)
const ratioError = ref(false)
const increaseError = ref(false)
// 金额输入格式验证:确保最多两位小数
const validateDecimal = (field) => {
console.log(`validateDecimal开始: field=${field}, 值=${configData.value[field]}`)
const value = configData.value[field]
if (value === null || value === undefined) {
console.log(`validateDecimal: ${field}为空,退出验证`)
return
}
const numValue = Number(value)
if (isNaN(numValue)) {
console.log(`validateDecimal: ${field}无法转换为数字设置为null`)
configData.value[field] = null
return
}
const fixedValue = parseFloat(numValue.toFixed(2))
console.log(`validateDecimal: ${field}原值=${numValue},处理后=${fixedValue}`)
configData.value[field] = fixedValue
if (field === 'price_increase_amount') {
console.log(`validateDecimal: 检查加价金额上限 ${fixedValue} vs ${priceIncreaseAmountMax.value}`)
if (fixedValue > priceIncreaseAmountMax.value) {
configData.value[field] = priceIncreaseAmountMax.value
console.log(`validateDecimal: 加价金额超过上限,已修正为${priceIncreaseAmountMax.value}`)
uni.showToast({
title: `加价金额最大为${priceIncreaseAmountMax.value}`,
icon: 'none'
})
increaseError.value = true
setTimeout(() => {
increaseError.value = false
}, 2000)
} else {
increaseError.value = false
}
// 当加价金额改变后,重新验证价格区间
validateRange()
}
console.log(`validateDecimal结束: ${field}最终值=${configData.value[field]}`)
}
// 价格区间验证
const validateRange = () => {
console.log('validateRange开始:',
`最低=${configData.value.price_range_from}`,
`最高=${configData.value.price_range_to}`)
// if (configData.value.price_range_from === null || configData.value.price_range_to === null) {
// console.log('validateRange: 价格区间值为null退出验证')
// rangeError.value = false
// return
// }
if (isNaN(configData.value.price_range_from) || isNaN(configData.value.price_range_to)) {
console.log('validateRange: 价格区间值非数字,退出验证')
return
}
const additional = configData.value.price_increase_amount || 0
console.log(`validateRange: 加价金额=${additional}`)
const minAllowed = parseFloat(
(Number(productConfigData.value.cost_price) + Number(additional)).toFixed(2)
) // 使用成本价作为最小值
const maxAllowed = productConfigData.value.price_range_max // 使用产品配置中的最大价格作为最大值
console.log(`validateRange: 最低允许=${minAllowed}, 最高允许=${maxAllowed}`)
// 检查最低金额
if (configData.value.price_range_from < minAllowed) {
console.log(`validateRange: 定价区间最低金额(${configData.value.price_range_from})小于允许最低值(${minAllowed}),进行修正`)
configData.value.price_range_from = minAllowed
uni.showToast({
title: `定价区间最低金额不能低于成本价 ${minAllowed}`,
icon: 'none'
})
rangeError.value = true
closeRangeError()
configData.value.price_range_to = parseFloat(
(Number(configData.value.price_range_from) + Number(priceIncreaseMax.value)).toFixed(2)
)
console.log(`validateRange: 已调整最高金额为 ${configData.value.price_range_to}`)
return
}
// 检查最高金额是否小于最低金额
if (configData.value.price_range_to < configData.value.price_range_from) {
console.log(`validateRange: 定价区间最高金额(${configData.value.price_range_to})小于最低金额(${configData.value.price_range_from}),进行修正`)
uni.showToast({
title: '定价区间最高金额不能低于定价区间最低金额',
icon: 'none'
})
if (configData.value.price_range_from + priceIncreaseMax.value > maxAllowed) {
configData.value.price_range_to = maxAllowed
console.log(`validateRange: 最高值已修正为最大允许值 ${maxAllowed}`)
} else {
configData.value.price_range_to = configData.value.price_range_from + priceIncreaseMax.value
console.log(`validateRange: 最高值已修正为最低金额+最大增加值 ${configData.value.price_range_to}`)
}
rangeError.value = true
closeRangeError()
return
}
// 检查差值
const diff = parseFloat(
(configData.value.price_range_to - configData.value.price_range_from).toFixed(2)
)
console.log(`validateRange: 价格区间差值=${diff}, 最大允许差值=${priceIncreaseMax.value}`)
if (diff > priceIncreaseMax.value) {
console.log(`validateRange: 价格区间差值超过最大允许值,进行修正`)
uni.showToast({
title: `价格区间最大差值为${priceIncreaseMax.value}`,
icon: 'none'
})
configData.value.price_range_to = parseFloat(
(Number(configData.value.price_range_from) + Number(priceIncreaseMax.value)).toFixed(2)
)
console.log(`validateRange: 已调整最高金额为 ${configData.value.price_range_to}`)
closeRangeError()
return
}
// 检查最高金额是否超过上限
if (configData.value.price_range_to > maxAllowed) {
console.log(`validateRange: 定价区间最高金额(${configData.value.price_range_to})超过上限(${maxAllowed}),进行修正`)
configData.value.price_range_to = maxAllowed
uni.showToast({
title: `定价区间最高金额不能超过 ${maxAllowed}`,
icon: 'none'
})
closeRangeError()
}
if (!rangeError.value) {
rangeError.value = false
}
console.log('validateRange结束:',
`最终最低=${configData.value.price_range_from}`,
`最终最高=${configData.value.price_range_to}`)
}
// 收取比例验证(修改为保留两位小数,不再四舍五入取整)
const validateRatio = () => {
console.log(`validateRatio开始: 值=${configData.value.price_ratio}`)
let value = configData.value.price_ratio
if (value === null || value === undefined) {
console.log('validateRatio: 值为空,退出验证')
return
}
const numValue = Number(value)
if (isNaN(numValue)) {
console.log('validateRatio: 值无法转换为数字设置为null')
configData.value.price_ratio = null
ratioError.value = true
return
}
console.log(`validateRatio: 检查比例范围 ${numValue} vs 最大值${priceRatioMax.value}`)
if (numValue > priceRatioMax.value) {
console.log(`validateRatio: 比例超过最大值,已修正为${priceRatioMax.value}`)
configData.value.price_ratio = priceRatioMax.value
uni.showToast({
title: `收取比例最大为${priceRatioMax.value}%`,
icon: 'none'
})
ratioError.value = true
setTimeout(() => {
ratioError.value = false
}, 1000)
} else if (numValue < 0) {
console.log('validateRatio: 比例小于0已修正为0')
configData.value.price_ratio = 0
ratioError.value = true
} else {
configData.value.price_ratio = parseFloat(numValue.toFixed(2))
ratioError.value = false
}
console.log(`validateRatio结束: 最终值=${configData.value.price_ratio}`)
}
// 获取配置
const getConfig = async () => {
try {
console.log(`getConfig开始: 获取产品ID=${selectedReportId.value}的配置`)
const res = await getAgentMembershipUserConfig({ product_id: selectedReportId.value })
if (res.code === 200) {
const respConfigData = res.data.agent_membership_user_config
console.log("respConfigData", respConfigData)
configData.value = {
id: respConfigData.product_id,
price_range_from: respConfigData.price_range_from || null,
price_range_to: respConfigData.price_range_to || null,
price_ratio: respConfigData.price_ratio * 100 || null, // 转换为百分比
price_increase_amount: respConfigData.price_increase_amount || null,
}
productConfigData.value = res.data.product_config
// 设置动态限制值
priceIncreaseMax.value = res.data.price_increase_max
priceIncreaseAmountMax.value = res.data.price_increase_amount
priceRatioMax.value = res.data.price_ratio * 100
console.log('getConfig: 配置加载成功',
`最大差值=${priceIncreaseMax.value}`,
`最大加价=${priceIncreaseAmountMax.value}`,
`最大比例=${priceRatioMax.value}%`)
console.log('getConfig: 当前配置', configData.value)
}
} catch (error) {
console.error("getConfig错误:", error)
uni.showToast({
title: '配置加载失败',
icon: 'none'
})
}
}
// 提交处理
const handleSubmit = async () => {
try {
if (!finalValidation()) {
return
}
// 前端数据转换
const submitData = {
product_id: configData.value.id,
price_range_from: configData.value.price_range_from || 0,
price_range_to: configData.value.price_range_to || 0,
price_ratio: (configData.value.price_ratio || 0) / 100, // 转换为小数
price_increase_amount: configData.value.price_increase_amount || 0,
}
const res = await saveAgentMembershipUserConfig(submitData)
if (res.code === 200) {
uni.showToast({
title: '保存成功',
icon: 'success'
})
getConfig()
}
} catch (error) {
console.log("handleSubmit错误:", error)
uni.showToast({
title: '保存失败,请稍后重试',
icon: 'none'
})
}
}
// 最终验证函数
const finalValidation = () => {
// 校验最低金额不能为空且大于0
if (!configData.value.price_range_from || configData.value.price_range_from <= 0) {
uni.showToast({
title: "定价区间最低金额不能为空",
icon: 'none'
})
return false
}
// 校验最高金额不能为空且大于0
if (!configData.value.price_range_to || configData.value.price_range_to <= 0) {
uni.showToast({
title: "定价区间最高金额不能为空",
icon: 'none'
})
return false
}
// 校验收取比例不能为空且大于0
if (!configData.value.price_ratio || configData.value.price_ratio <= 0) {
uni.showToast({
title: "收取比例不能为空",
icon: 'none'
})
return false
}
// 验证最低金额必须小于最高金额
if (configData.value.price_range_from >= configData.value.price_range_to) {
uni.showToast({
title: "定价区间最低金额必须小于定价区间最高金额",
icon: 'none'
})
return false
}
// 验证价格区间差值不能超过最大允许差值
const finalDiff = parseFloat(
(configData.value.price_range_to - configData.value.price_range_from).toFixed(2)
)
if (finalDiff > priceIncreaseMax.value) {
uni.showToast({
title: `价格区间最大差值为${priceIncreaseMax.value}`,
icon: 'none'
})
return false
}
// 验证最高金额不能超过产品配置中设定的上限
if (configData.value.price_range_to > productConfigData.value.price_range_max) {
uni.showToast({
title: `定价区间最高金额不能超过${productConfigData.value.price_range_max}`,
icon: 'none'
})
return false
}
// 验证最低金额不能低于成本价+加价金额(加价金额允许为空)
const additional = configData.value.price_increase_amount || 0
if (configData.value.price_range_from < productConfigData.value.cost_price + additional) {
uni.showToast({
title: `定价区间最低金额不能低于成本价${productConfigData.value.cost_price + additional}`,
icon: 'none'
})
return false
}
return true
}
// 选择器确认
const onConfirm = (e) => {
const { selectedItems } = e
selectedReportId.value = selectedItems.value
selectedReportText.value = selectedItems.label
showPicker.value = false
// 重置错误状态
rangeError.value = false
ratioError.value = false
increaseError.value = false
getConfig()
}
const closeRangeError = () => {
setTimeout(() => {
rangeError.value = false
}, 2000)
}
onMounted(() => {
getConfig()
})
</script>
<style>
.card {
border-radius: 24rpx;
background-color: white;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
padding: 32rpx;
margin-bottom: 32rpx;
}
.space-y-6 {
display: flex;
flex-direction: column;
}
.space-y-6 > view {
margin-bottom: 48rpx;
}
.space-y-6 > view:last-child {
margin-bottom: 0;
}
</style>
<route type="page" lang="json">{
"layout": "page",
"title": "会员代理报告配置",
"agent": true,
"auth": true
}</route>

72
src/pages/agreement.vue Normal file
View File

@@ -0,0 +1,72 @@
<script setup>
const webviewStyles = ref({
top: `${uni.getSystemInfoSync().statusBarHeight + 44}px`, // 距离顶部的距离
height: `${uni.getSystemInfoSync().windowHeight - uni.getSystemInfoSync().statusBarHeight - 44}px`, // 高度
position: 'absolute', // 绝对定位
dock: 'bottom', // 停靠在底部
bounce: 'vertical', // 垂直方向的回弹效果
})
// 从环境变量获取基础URL
const BASE_URL = import.meta.env.VITE_APP_BASE_URL || 'https://www.tianyuandb.com'
// 协议路径和标题的映射
const agreementMap = {
user: {
path: '/app/userAgreement',
title: '用户协议'
},
privacy: {
path: '/app/privacyPolicy',
title: '隐私政策'
},
authorization: {
path: '/app/authorization',
title: '授权书'
},
service: {
path: '/app/agentSerivceAgreement',
title: '信息技术服务合同'
},
manage: {
path: '/app/agentManageAgreement',
title: '推广方管理制度协议'
}
}
const agreementUrl = ref('')
const pageTitle = ref('协议')
// 使用 uniapp 的 onLoad 生命周期钩子获取页面参数
onLoad((option) => {
const type = option.type || 'user'
if (agreementMap[type]) {
agreementUrl.value = `${BASE_URL}${agreementMap[type].path}`
pageTitle.value = agreementMap[type].title
// 设置标题 - 此标题会传递给 page 布局中的 wd-navbar
uni.setNavigationBarTitle({
title: pageTitle.value
})
}
})
function handleClickLeft() {
uni.navigateBack()
}
</script>
<template>
<view>
<wd-navbar :title="pageTitle" left-text="返回" placeholder left-arrow safe-area-inset-top fixed @click-left="handleClickLeft" />
<web-view :webview-styles="webviewStyles" :src="agreementUrl" />
</view>
</template>
<route type="page" lang="json">{
"layout": "page",
"title": "协议",
"style": {
"navigationBarTextStyle": "black",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#e3f0ff"
}
}</route>

123
src/pages/index.vue Normal file
View File

@@ -0,0 +1,123 @@
<script setup>
import { ref, onBeforeMount } from 'vue'
// 引入icon图片
import iconCard1 from '/static/image/icon_2.png'
import iconCard2 from '/static/image/icon_1.png'
// 从缓存中获取代理状态
const isAgent = ref(false)
onBeforeMount(() => {
// 从缓存获取代理信息
const agentInfo = uni.getStorageSync('agentInfo')
if (agentInfo?.isAgent) {
isAgent.value = agentInfo.isAgent
}
})
function toInvitation() {
uni.navigateTo({
url: '/pages/invitation',
})
}
function toPromote() {
uni.navigateTo({
url: '/pages/promote',
})
}
// 跳转到帮助中心页面
function toHelpCenter() {
uni.navigateTo({ url: '/pages/help' })
}
// 跳转到帮助详情
function toHelpDetail(title) {
uni.navigateTo({ url: `/pages/helpDetail?title=${encodeURIComponent(title)}` })
}
function getPhoneNumber(e) {
console.log("e", e)
console.log(e.detail.code) // 动态令牌
console.log(e.detail.errMsg) // 回调信息(成功失败都会返回)
console.log(e.detail.errno) // 错误码(失败时返回)
}
</script>
<template>
<!-- <button open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">test</button> -->
<view class="box-border min-h-screen bg-[#f5faff]">
<!-- 顶部Banner复用原有Banner部分 -->
<view class="relative h-[150px] rounded-2xl overflow-hidden m-4">
<image class="h-full w-full" src="/static/image/banner.png" />
</view>
<view class="mt-4 px-4">
<view class="text-base font-bold mb-2 text-gray-800">即刻赚佣金</view>
<!-- 卡片1直推报告 -->
<view class="rounded-2xl shadow-xl p-0 mb-4 flex items-center card-gradient-1" @click="toPromote">
<view class="flex-1 flex flex-col justify-center items-start pl-4 py-4">
<view class="text-[20px] font-bold text-blue-700 mb-1">直推报告</view>
<view class="text-gray-700 text-xs mb-4">选择所需报告类型灵活定价一键分享客户客户下单即结算佣金实时到账</view>
<wd-button plain>立即推广</wd-button>
</view>
<image class="w-20 h-20 mr-4 ml-4 my-4" :src="iconCard1" mode="aspectFit" />
</view>
<!-- 卡片2邀请下级代理 -->
<view class="rounded-2xl shadow-xl p-0 flex items-center card-gradient-2" @click="toInvitation">
<view class="flex-1 flex flex-col justify-center items-start pl-4 py-4">
<view class="text-[20px] font-bold text-teal-700 mb-1 ">邀请下级代理</view>
<view class="text-gray-700 text-xs mb-4">邀请好友成为代理好友推广获客客户支付即返佣团队收益轻松到手</view>
<wd-button plain>立即邀请</wd-button>
</view>
<image class="w-20 h-20 mr-4 ml-4 my-4" :src="iconCard2" mode="aspectFit" />
</view>
<!-- 帮助中心模块 -->
<!-- <view class="mt-6">
<view class="flex items-center justify-between mb-2">
<view class="font-bold text-base text-gray-800">帮助中心</view>
<view class="text-xs text-blue-500 px-2 py-1 rounded cursor-pointer" @click="toHelpCenter">更多</view>
</view>
<view class="bg-white rounded-2xl shadow p-2 divide-y divide-gray-100">
<view class="flex items-center py-3 px-2 justify-between" @click="toHelpDetail('直推报告页面引导')">
<view class="text-gray-800 text-sm">直推报告页面引导</view>
<view class="text-xs bg-blue-100 text-blue-600 px-2 py-0.5 rounded">引导指南</view>
</view>
<view class="flex items-center py-3 px-2 justify-between" @click="toHelpDetail('邀请下级页面引导')">
<view class="text-gray-800 text-sm">邀请下级页面引导</view>
<view class="text-xs bg-blue-100 text-blue-600 px-2 py-0.5 rounded">引导指南</view>
</view>
<view class="flex items-center py-3 px-2 justify-between" @click="toHelpDetail('如何成为天远数据代理')">
<view class="text-gray-800 text-sm">如何成为天远数据代理</view>
</view>
</view>
</view> -->
</view>
</view>
</template>
<style scoped>
.clip-left {
clip-path: polygon(0 0, 0 100%, 90% 100%, 0 100%);
}
.clip-right {
clip-path: polygon(0 0, 0 0, 90% 100%, 0 0);
}
.card-gradient-1 {
background: linear-gradient(135deg, #e3f0ff 0%, #fafdff 100%);
box-shadow: 0 6px 24px 0 rgba(60, 120, 255, 0.10), 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.10), 0 1.5px 4px 0 rgba(0, 200, 180, 0.08);
}
</style>
<route type="home" lang="json">{
"layout": "home",
"style": {
"navigationBarTextStyle": "black",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#e3f0ff"
}
}</route>

52
src/pages/invitation.vue Normal file
View File

@@ -0,0 +1,52 @@
<template>
<view>
<image src="/static/image/invitation.png" alt="邀请下级" mode="widthFix" class="w-full" />
<view @click="showQRcode = true"
class="bg-gradient-to-t from-orange-500 to-orange-300 fixed bottom-0 h-12 w-full shadow-xl text-white rounded-t-xl flex items-center justify-center font-bold">
立即邀请好友
</view>
<QRcode v-model:show="showQRcode" mode="invitation" :linkIdentifier="linkIdentifier" />
</view>
</template>
<script setup>
import { ref, onBeforeMount } from 'vue'
import { aesEncrypt } from "@/utils/crypto"
import QRcode from '@/components/QRcode.vue'
const showQRcode = ref(false)
const linkIdentifier = ref("")
const mobile = ref("")
const agentID = ref("")
onBeforeMount(() => {
// 从UniApp缓存获取用户信息
const userInfo = uni.getStorageSync('userInfo') || {}
mobile.value = userInfo.mobile || ''
agentID.value = userInfo.agentID || ''
encryptIdentifire(agentID.value, mobile.value)
})
const encryptIdentifire = (agentID, mobile) => {
const linkIdentifierJSON = {
agentID,
mobile
}
const linkIdentifierStr = JSON.stringify(linkIdentifierJSON)
const encodeData = aesEncrypt(linkIdentifierStr, "8e3e7a2f60edb49221e953b9c029ed10")
linkIdentifier.value = encodeURIComponent(encodeData)
}
</script>
<style>
/* 自定义样式 */
</style>
<route type="page" lang="json">{
"layout": "page",
"title": "邀请下级",
"auth": true,
"agent": true
}</route>

View File

@@ -0,0 +1,201 @@
<template>
<view class="min-h-screen bg-[#D1D6FF]">
<image src="/static/image/invitation_agent_apply.png" alt="邀请代理申请" mode="widthFix" class="w-full" />
<!-- 统一状态处理容器 -->
<view class="flex flex-col items-center justify-center">
<!-- 审核中状态 -->
<view v-if="displayStatus === 0" class="text-center">
<text class="text-xs text-gray-500 block">您的申请正在审核中</text>
<view class="bg-gray-200 p-1 rounded-3xl shadow-xl mt-1">
<view class="text-xl font-bold px-8 py-2 bg-gray-400 text-white rounded-3xl shadow-lg cursor-not-allowed">
审核进行中
</view>
</view>
</view>
<!-- 审核通过状态 -->
<view v-if="displayStatus === 1" class="text-center">
<text class="text-xs text-gray-500 block">您已成为认证代理方</text>
<view class="bg-green-100 p-1 rounded-3xl shadow-xl mt-1" @click="goToHome">
<view
class="text-xl font-bold px-8 py-2 bg-gradient-to-t from-green-500 to-green-300 text-white rounded-3xl shadow-lg cursor-pointer">
进入应用首页
</view>
</view>
</view>
<!-- 审核未通过状态 -->
<view v-if="displayStatus === 2" class="text-center">
<text class="text-xs text-red-500 block">审核未通过请重新提交</text>
<view class="bg-red-100 p-1 rounded-3xl shadow-xl mt-1" @click="agentApply">
<view
class="text-xl font-bold px-8 py-2 bg-gradient-to-t from-red-500 to-red-300 text-white rounded-3xl shadow-lg cursor-pointer">
重新提交申请
</view>
</view>
</view>
<!-- 未申请状态包含邀请状态 -->
<view v-if="displayStatus === 3" class="text-center">
<text class="text-xs text-gray-500 block">{{ isSelf ? '立即申请成为代理人' : '邀您注册代理人' }}</text>
<view class="bg-gray-100 p-1 rounded-3xl shadow-xl mt-1" @click="agentApply">
<view
class="text-xl font-bold px-8 py-2 bg-gradient-to-t from-blue-500 to-blue-300 text-white rounded-3xl shadow-lg cursor-pointer">
立即成为代理方
</view>
</view>
</view>
</view>
<AgentApplicationForm v-model:show="showApplyPopup" @submit="submitApplication" @close="showApplyPopup = false"
:ancestor="ancestor" />
</view>
</template>
<script setup>
import { ref, computed, onBeforeMount } from 'vue'
import { getAgentInfo, applyAgent } from '@/apis/agent'
import AgentApplicationForm from '@/components/AgentApplicationForm.vue'
const showApplyPopup = ref(false)
const status = ref(3) // 默认为未申请状态
const ancestor = ref("")
const isSelf = ref(true)
let intervalId = null // 保存定时器 ID
// 计算显示状态当isSelf为false时强制显示为3
const displayStatus = computed(() => {
return !isSelf.value ? 3 : status.value
})
// 打开申请表单
const agentApply = () => {
showApplyPopup.value = true
}
// 跳转到首页
const goToHome = () => {
clearInterval(intervalId)
uni.switchTab({
url: '/pages/index'
})
}
const getAgentInformation = async () => {
const token = uni.getStorageSync("token")
if (!token) {
return
}
try {
const res = await getAgentInfo()
if (res.code === 200 && res.data) {
// 将代理信息存入缓存
uni.setStorageSync("agentInfo", {
level: res.data.level,
isAgent: res.data.is_agent, // 判断是否是代理
status: res.data.status, // 获取代理状态 0=待审核1=审核通过2=审核未通过3=未申请
agentID: res.data.agent_id,
mobile: res.data.mobile,
isRealName: res.data.is_real_name,
expiryTime: res.data.expiry_time
})
status.value = res.data.status
console.log('代理信息已获取并存入缓存')
}
} catch (error) {
console.error('获取代理信息失败', error)
}
}
// 提交代理申请
const submitApplication = async (formData) => {
try {
const { region, mobile, wechat_id, code } = formData
let postData = {
region,
mobile,
wechat_id,
code,
}
if (!isSelf.value) {
postData.ancestor = ancestor.value
}
const res = await applyAgent(postData)
if (res.code === 200) {
showApplyPopup.value = false
uni.showToast({
title: "已提交申请",
icon: 'success'
})
if (res.data.accessToken) {
uni.setStorageSync('token', res.data.accessToken)
uni.setStorageSync('refreshAfter', res.data.refreshAfter)
uni.setStorageSync('accessExpire', res.data.accessExpire)
refreshAgentStatus()
}
} else {
uni.showToast({
title: res.msg || '申请失败',
icon: 'none'
})
}
} catch (error) {
uni.showToast({
title: '网络错误',
icon: 'none'
})
}
}
// 定时刷新代理状态
const refreshAgentStatus = () => {
if (status.value === 3) {
if (intervalId) clearInterval(intervalId)
intervalId = setInterval(() => {
if (status.value !== 3) {
clearInterval(intervalId)
intervalId = null
return
}
getAgentInformation()
}, 2000)
} else {
if (intervalId) {
clearInterval(intervalId)
intervalId = null
}
}
}
onLoad(() => {
const token = uni.getStorageSync('token')
if (token) {
// 从缓存中获取代理信息
const agentInfo = uni.getStorageSync('agentInfo')
if (agentInfo) {
status.value = agentInfo.status || 3
}
getAgentInformation()
}
})
// 页面卸载时清除定时器
onUnload(() => {
if (intervalId) {
clearInterval(intervalId)
intervalId = null
}
})
</script>
<route type="page" lang="json">{
"layout": "page",
"title": "代理申请",
"auth": true
}</route>

223
src/pages/login.vue Normal file
View File

@@ -0,0 +1,223 @@
<script setup>
import { getCode, bindMobile, getUserInfo } from '@/api/apis'
import { getAgentInfo } from '@/apis/agent'
const phoneNumber = ref('')
const verificationCode = ref('')
const password = ref('')
const isPasswordLogin = ref(false)
const isAgreed = ref(false)
const isCountingDown = ref(false)
const countdown = ref(60)
let timer = null
// 聚焦状态变量
const phoneFocused = ref(false)
const codeFocused = ref(false)
const passwordFocused = ref(false)
const isPhoneNumberValid = computed(() => {
return /^1[3-9]\d{9}$/.test(phoneNumber.value)
})
const canLogin = computed(() => {
if (!isPhoneNumberValid.value)
return false
if (isPasswordLogin.value) {
return password.value.length >= 6
}
else {
return verificationCode.value.length === 6
}
})
function sendVerificationCode() {
if (isCountingDown.value || !isPhoneNumberValid.value)
return
if (!isPhoneNumberValid.value) {
uni.showToast({ title: '请输入有效的手机号', icon: 'none' })
return
}
getCode({
mobile: phoneNumber.value,
actionType: 'bindMobile',
}).then((res) => {
if (res.code === 200) {
uni.showToast({ title: '获取成功', icon: 'none' })
startCountdown()
}
})
}
function startCountdown() {
isCountingDown.value = true
countdown.value = 60
timer = setInterval(() => {
if (countdown.value > 0) {
countdown.value--
}
else {
clearInterval(timer)
isCountingDown.value = false
}
}, 1000)
}
function handleLogin() {
if (!canLogin.value) {
uni.showToast({ title: '请完善信息', icon: 'none' })
return
}
if (!isAgreed.value) {
uni.showToast({ title: '请先同意用户协议', icon: 'none' })
return
}
bindMobile({ mobile: phoneNumber.value, code: verificationCode.value }).then((res) => {
if (res.code === 200) {
uni.setStorageSync('token', res.data.accessToken)
uni.setStorageSync('refreshAfter', res.data.refreshAfter)
uni.setStorageSync('accessExpire', res.data.accessExpire)
uni.showToast({ title: '绑定成功', icon: 'none' })
getUser()
getAgentInformation()
uni.reLaunch({
url: '/pages/index',
})
}
else {
uni.showToast({ title: res.msg, icon: 'none' })
}
})
}
function toUserAgreement() {
uni.navigateTo({
url: '/pages/agreement?type=user',
})
}
function toPrivacyPolicy() {
uni.navigateTo({
url: '/pages/agreement?type=privacy',
})
}
const getAgentInformation = async () => {
const token = uni.getStorageSync("token")
if (!token) {
return
}
try {
const res = await getAgentInfo()
if (res.code === 200 && res.data) {
// 将代理信息存入缓存
uni.setStorageSync("agentInfo", {
level: res.data.level,
isAgent: res.data.is_agent, // 判断是否是代理
status: res.data.status, // 获取代理状态 0=待审核1=审核通过2=审核未通过3=未申请
agentID: res.data.agent_id,
mobile: res.data.mobile
})
console.log('代理信息已获取并存入缓存')
}
} catch (error) {
console.error('获取代理信息失败', error)
}
}
const getUser = async () => {
const token = uni.getStorageSync("token")
if (!token) {
return
}
const res = await getUserInfo()
if (res.code === 200) {
console.log(res.data)
uni.setStorageSync("userInfo", res.data.userInfo)
}
}
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
})
</script>
<template>
<view class="login px-8">
<view class="mb-8 pt-8 text-left">
<view class="flex flex-col items-center">
<image class="h-18 w-18 rounded-full shadow" src="/static/image/logo.jpg" mode="scaleToFill" />
<view class="mt-4 text-3xl font-bold text-gray-800">天远数据</view>
</view>
</view>
<view class="space-y-5">
<view class="input-container bg-blue-300/20" :class="[phoneFocused ? 'focused' : '']">
<input v-model="phoneNumber" class="input-field" type="number" placeholder="请输入手机号" maxlength="11"
@focus="phoneFocused = true" @blur="phoneFocused = false">
</view>
<view v-if="!isPasswordLogin">
<view class="flex items-center justify-between">
<view class="input-container bg-blue-300/20" :class="[codeFocused ? 'focused' : '']">
<input v-model="verificationCode" class="input-field" type="number" placeholder="请输入验证码" maxlength="6"
@focus="codeFocused = true" @blur="codeFocused = false">
</view>
<view
class="ml-2 flex-shrink-0 rounded-lg px-4 py-2 text-sm font-bold transition duration-300 focus:outline-none"
:class="isCountingDown || !isPhoneNumberValid ? 'cursor-not-allowed bg-gray-300 text-gray-500' : 'bg-blue-500 text-white hover:bg-blue-600'"
@click="sendVerificationCode">
{{ isCountingDown ? `${countdown}s重新获取` : '获取验证码' }}
</view>
</view>
</view>
<view v-if="isPasswordLogin" class="input-container" :class="[passwordFocused ? 'focused' : '']">
<input v-model="password" class="input-field" type="password" placeholder="请输入密码"
@focus="passwordFocused = true" @blur="passwordFocused = false">
</view>
<view class="flex items-start space-x-2">
<wd-checkbox v-model="isAgreed" class="mt-1" />
<text class="text-xs text-gray-400 leading-tight">
绑定手机号后将自动成为代理并且代表您已阅读并同意
<text class="cursor-pointer text-blue-400" @click="toUserAgreement">
用户协议
</text>
<text class="cursor-pointer text-blue-400" @click="toPrivacyPolicy">
隐私政策
</text>
</text>
</view>
</view>
<button
class="mt-20 block w-full flex-shrink-0 rounded-full bg-blue-500 py-3 text-lg text-white font-bold transition duration-300"
@click="handleLogin">
绑定手机号
</button>
</view>
</template>
<style scoped>
.login {}
.input-container {
border: 2px solid rgba(125, 211, 252, 0.0);
border-radius: 1rem;
@apply transition duration-200
}
.input-container.focused {
border: 2px solid #3b82f6;
}
.input-field {
width: 100%;
padding: 1rem;
transition: border-color 0.3s ease;
outline: none;
background: transparent;
}
</style>
<route lang="json">{
"layout": "login",
"title": "绑定手机号"
}</route>

296
src/pages/me.vue Normal file
View File

@@ -0,0 +1,296 @@
<template>
<view class="safe-area-top box-border min-h-screen">
<view class="flex flex-col p-4 space-y-6">
<!-- 用户信息卡片 -->
<view
class="mb-4 profile-section group relative flex items-center gap-4 rounded-xl bg-white p-6 shadow-lg transition-all hover:shadow-xl"
@click="!isLoggedIn ? redirectToLogin() : null">
<view class="relative">
<!-- 头像容器添加overflow-hidden解决边框问题 -->
<view class="flex items-center justify-center overflow-hidden rounded-full p-0.5"
:class="levelGradient.border">
<image :src="userAvatar || getDefaultAvatar()" alt="User Avatar"
class="h-24 w-24 rounded-full border-4 border-white">
</image>
</view>
<!-- 代理标识 -->
<view v-if="isAgent" class="absolute -bottom-2 -right-2">
<view class="flex items-center justify-center rounded-full px-3 py-1 text-xs font-bold text-white shadow-sm"
:class="levelGradient.badge">
{{ levelNames[level] }}
</view>
</view>
</view>
<view class="space-y-1">
<!-- @click.stop="handleVersionClickForTest" -->
<view class="text-lg font-bold text-gray-800" @click="userType === 0 ? toBindPhone() : null"
:class="userType === 0 ? 'cursor-pointer text-blue-600' : ''">
{{ !isLoggedIn ? '点击登录' : (userType === 0 ? '绑定手机号' : maskName(userName)) }}
</view>
<view v-if="isAgent" class="text-sm font-medium" :class="levelGradient.text">
🎖 {{ levelText[level] }}
</view>
</view>
</view>
<VipBanner v-if="isAgent && level === 'normal'" />
<!-- 功能菜单 -->
<view class="features-section space-y-3">
<template v-if="isAgent && ['VIP', 'SVIP'].includes(level)">
<button
class=" flex items-center p-3 rounded-xl bg-gradient-to-r from-purple-200/80 to-pink-200/80 text-purple-700 font-medium shadow-sm transition-all active:shadow-md"
hover-class="opacity-80 scale-98" @click="toVipConfig">
<text class="mr-2"></text> 代理报告配置
</button>
</template>
<button
class=" flex items-center text-gray-600 p-3 rounded-xl bg-white font-medium shadow-sm transition-all active:shadow-md"
hover-class="opacity-80 bg-blue-50" @click="toUserAgreement">
<text class="mr-2">📜</text> 用户协议
</button>
<button
class=" flex items-center text-gray-600 p-3 rounded-xl bg-white font-medium shadow-sm transition-all active:shadow-md"
hover-class="opacity-80 bg-blue-50" @click="toPrivacyPolicy">
<text class="mr-2">🔒</text> 隐私政策
</button>
<button open-type="contact"
class=" flex items-center text-gray-600 p-3 rounded-xl bg-white font-medium shadow-sm transition-all active:shadow-md"
hover-class="opacity-80 bg-blue-50" @click="toAi">
<text class="mr-2">💬</text> 联系客服
</button>
</view>
</view>
<!-- 更新进度弹窗 -->
<view v-if="showUpdateProgress" class="update-progress-mask">
<view class="update-progress-dialog">
<view class="update-progress-title">应用更新中</view>
<view class="update-progress-bar-container">
<view class="update-progress-bar" :style="{ width: downloadProgress + '%' }"></view>
</view>
<view class="update-progress-text">{{ downloadProgress }}%</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onBeforeMount } from 'vue'
// 用户数据
const userName = ref('')
const userAvatar = ref('')
const isLoggedIn = ref(false)
const userType = ref(null)
// 代理数据
const isAgent = ref(false)
const level = ref('normal')
const showUpdateProgress = ref(false)
// 检查平台环境
onBeforeMount(() => {
// 从缓存获取用户信息
const token = uni.getStorageSync('token')
if (token) {
isLoggedIn.value = true
// 从缓存获取用户信息
const userInfo = uni.getStorageSync('userInfo')
console.log("userInfo", userInfo)
if (userInfo) {
userName.value = userInfo.nickName || userInfo.mobile || '微信用户'
userAvatar.value = userInfo.avatar || ''
userType.value = userInfo.userType
}
// 从缓存获取代理信息
const agentInfo = uni.getStorageSync('agentInfo')
if (agentInfo?.isAgent) {
isAgent.value = agentInfo.isAgent
level.value = agentInfo.level || 'normal'
}
}
})
const levelNames = {
normal: '普通代理',
'': '普通代理',
VIP: 'VIP代理',
SVIP: 'SVIP代理',
}
const levelText = {
normal: '基础代理特权',
'': '基础代理特权',
VIP: '高级代理特权',
SVIP: '尊享代理特权',
}
const levelGradient = computed(() => ({
border: {
normal: 'bg-green-300',
'': 'bg-green-300',
VIP: 'bg-gradient-to-r from-yellow-400 to-amber-500',
SVIP: 'bg-gradient-to-r from-purple-400 to-pink-400 shadow-[0_0_15px_rgba(163,51,200,0.2)]',
}[level.value],
badge: {
normal: 'bg-green-500',
'': 'bg-green-500',
VIP: 'bg-gradient-to-r from-yellow-500 to-amber-600',
SVIP: 'bg-gradient-to-r from-purple-500 to-pink-500',
}[level.value],
text: {
normal: 'text-green-600',
'': 'text-green-600',
VIP: 'text-amber-600',
SVIP: 'text-purple-600',
}[level.value],
}))
function maskName(name) {
if (!name || name.length < 11) return name
return name.substring(0, 3) + "****" + name.substring(7)
}
function toHistory() {
uni.navigateTo({
url: '/pages/queryHistory'
})
}
function toUserAgreement() {
uni.navigateTo({
url: '/pages/agreement?type=user'
})
}
function redirectToLogin() {
uni.navigateTo({
url: '/pages/login'
})
}
const toPrivacyPolicy = () => {
uni.navigateTo({
url: '/pages/agreement?type=privacy'
})
}
const toAi = () => {
uni.switchTab({
url: '/pages/ai'
})
}
function toVipConfig() {
uni.navigateTo({
url: '/pages/agentVipConfig'
})
}
function toBindPhone() {
uni.navigateTo({
url: '/pages/login'
})
}
const getDefaultAvatar = () => {
if (!isAgent.value) return '/static/image/head_shot.webp'
switch (level.value) {
case 'normal':
case '':
return '/static/image/shot_nonal.png'
case 'VIP':
return '/static/image/shot_vip.png'
case 'SVIP':
return '/static/image/shot_svip.png'
default:
return '/static/image/head_shot.webp'
}
}
</script>
<style scoped>
.profile-section {
background: linear-gradient(135deg, #ffffff 50%, rgba(236, 253, 245, 0.3));
border: 1px solid rgba(209, 213, 219, 0.2);
}
.profile-section .relative>view:first-child {
transition: all 0.3s ease;
}
.border-gradient-to-r {
border-image: linear-gradient(to right, var(--tw-gradient-from), var(--tw-gradient-to)) 1;
}
.shadow-glow {
box-shadow: 0 0 8px rgba(163, 51, 200, 0.2);
}
/* 更新进度样式 */
.update-progress-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.update-progress-dialog {
width: 80%;
background-color: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.update-progress-title {
font-size: 18px;
font-weight: bold;
text-align: center;
margin-bottom: 16px;
color: #333;
}
.update-progress-bar-container {
height: 10px;
background-color: #f0f0f0;
border-radius: 5px;
overflow: hidden;
margin-bottom: 8px;
}
.update-progress-bar {
height: 100%;
background: linear-gradient(to right, #4299e1, #667eea);
border-radius: 5px;
transition: width 0.3s ease;
}
.update-progress-text {
text-align: center;
font-size: 14px;
color: #666;
}
</style>
<route lang="json">{
"layout": "home",
"style": {
"navigationBarTextStyle": "black",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#e3f0ff"
}
}</route>

265
src/pages/promote.vue Normal file
View File

@@ -0,0 +1,265 @@
<template>
<view class="min-h-screen p-4 promote">
<view class="mb-4 card !bg-gradient-to-b from-orange-200 to-orange-200/80">
<view>
<text class="text-lg font-bold text-orange-500 block">直推用户查询</text>
<text class="font-bold text-orange-400 mt-1 block">自定义价格赚取差价</text>
</view>
<view class="mt-6">
<view class="mt-2 text-gray-600 bg-orange-100 rounded-xl px-4 py-2">
在下方 "自定义价格" 处选择报告类型设置客户查询价即可立即推广
</view>
</view>
</view>
<VipBanner />
<!-- 查看示例报告提示 -->
<view class="card mb-4 !bg-gradient-to-r from-blue-50 to-blue-100 border-l-4 border-blue-400">
<view class="flex items-center justify-between">
<view>
<text class="text-sm text-blue-700 font-medium">不知道如何推广</text>
<text class="text-xs text-blue-600 mt-1 block">查看示例报告了解产品效果</text>
</view>
<text class="text-blue-500 text-sm underline" @click="showGzhQrcodeModal">查看示例</text>
</view>
</view>
<!-- 推广内容 -->
<view>
<view class="card mb-4">
<view>
<text class="text-xl font-semibold mb-2 block">生成推广码</text>
<wd-form :model="formData" ref="promotionForm">
<wd-cell-group border>
<!-- 报告类型 -->
<wd-picker label="报告类型" label-width="100px" v-model="formData.productType" :columns="[reportTypes]"
title="选择报告类型" prop="productType" placeholder="请选择报告类型" @confirm="onConfirmType"
:rules="[{ required: true, message: '请选择报告类型' }]" />
<!-- 定价 -->
<wd-input label="客户查询价" label-width="100px" v-model="formData.clientPrice" placeholder="请输入价格" readonly
clickable @click="showPricePicker = true" prop="clientPrice" suffix-icon="arrow-right"
:rules="[{ required: true, message: '请输入客户查询价' }]" />
</wd-cell-group>
<view class="flex items-center justify-between my-2">
<text class="text-sm text-gray-500">推广收益为 <text class="text-orange-500">{{ promotionRevenue }}</text>
</text>
<text class="text-sm text-gray-500">我的成本为 <text class="text-orange-500">{{ costPrice }}</text> </text>
</view>
</wd-form>
</view>
<view class="mt-6">
<button type="primary" block @click="generatePromotionCode">点击立即推广</button>
</view>
</view>
</view>
<PriceInputPopup v-model:show="showPricePicker" :default-price="formData.clientPrice"
:product-config="pickerProductConfig" @change="onPriceChange" />
<QRcode v-model:show="showQRcode" :linkIdentifier="linkIdentifier" />
<GzhQrcode :visible="showGzhQrcode" @close="showGzhQrcode = false" />
</view>
</template>
<script setup>
import { getProductConfig, generatePromotionLink } from '@/apis/agent'
import PriceInputPopup from '@/components/PriceInputPopup.vue'
import VipBanner from '@/components/VipBanner.vue'
import QRcode from '@/components/QRcode.vue'
import GzhQrcode from '@/components/GzhQrcode.vue'
// 报告类型
const reportTypes = [
{ label: '人事背调', value: 'backgroundcheck', id: 1 },
{ label: '老板企业报告', value: 'companyinfo', id: 2 },
{ label: '家政风险', value: 'homeservice', id: 3 },
{ label: '婚恋风险', value: 'marriage', id: 4 },
{ label: '贷前背调', value: 'preloanbackgroundcheck', id: 5 },
{ label: '租赁风险', value: 'rentalrisk', id: 6 },
{ label: '个人风险', value: 'riskassessment', id: 7 }
]
// 状态管理
const promotionForm = ref(null)
const showPricePicker = ref(false)
const pickerProductConfig = ref(null)
const productConfig = ref(null)
const linkIdentifier = ref("")
const showQRcode = ref(false)
const showGzhQrcode = ref(false)
// 表单数据对象
const formData = ref({
productType: '',
clientPrice: null
})
// 计算成本价格
const costPrice = computed(() => {
if (!pickerProductConfig.value) return '0.00'
// 平台定价成本
let platformPricing = 0
platformPricing += pickerProductConfig.value.cost_price
if (formData.value.clientPrice > pickerProductConfig.value.p_pricing_standard) {
platformPricing += (formData.value.clientPrice - pickerProductConfig.value.p_pricing_standard) * pickerProductConfig.value.p_overpricing_ratio
}
if (pickerProductConfig.value.a_pricing_standard > platformPricing &&
pickerProductConfig.value.a_pricing_end > platformPricing &&
pickerProductConfig.value.a_overpricing_ratio > 0) {
if (formData.value.clientPrice > pickerProductConfig.value.a_pricing_standard) {
if (formData.value.clientPrice > pickerProductConfig.value.a_pricing_end) {
platformPricing += (pickerProductConfig.value.a_pricing_end - pickerProductConfig.value.a_pricing_standard) * pickerProductConfig.value.a_overpricing_ratio
} else {
platformPricing += (formData.value.clientPrice - pickerProductConfig.value.a_pricing_standard) * pickerProductConfig.value.a_overpricing_ratio
}
}
}
return safeTruncate(platformPricing)
})
// 计算推广收益
const promotionRevenue = computed(() => {
return safeTruncate(formData.value.clientPrice - costPrice.value)
})
// 安全截断数字保留2位小数
function safeTruncate(num, decimals = 2) {
if (isNaN(num) || !isFinite(num)) return "0.00"
const factor = 10 ** decimals
const scaled = Math.trunc(num * factor)
const truncated = scaled / factor
return truncated.toFixed(decimals)
}
// 生成推广码
const generatePromotionCode = async () => {
// 表单验证
try {
await promotionForm.value.validate()
} catch (e) {
return
}
try {
// 获取选中产品的完整信息
const reportType = reportTypes.find(item => item.value === formData.value.productType)
if (!reportType) {
uni.showToast({
title: '请选择有效的报告类型',
icon: 'none'
})
return
}
const res = await generatePromotionLink({
product: formData.value.productType,
price: formData.value.clientPrice
})
if (res.code === 200) {
linkIdentifier.value = res.data.link_identifier
showQRcode.value = true
} else {
uni.showToast({
title: res.msg || '生成推广码失败',
icon: 'none'
})
}
} catch (error) {
uni.showToast({
title: '网络错误',
icon: 'none'
})
}
}
// 选择类型
const selectProductType = (reportTypeValue) => {
const reportType = reportTypes.find(item => item.id === reportTypeValue || item.value === reportTypeValue)
if (!reportType) return
formData.value.productType = reportType.value
if (productConfig.value) {
for (let i of productConfig.value) {
if (i.product_id === reportType.id) {
pickerProductConfig.value = i
formData.value.clientPrice = i.p_pricing_standard.toString()
}
}
}
}
// 获取产品配置
const getPromoteConfig = async () => {
try {
const res = await getProductConfig()
if (res.code === 200) {
productConfig.value = res.data.AgentProductConfig
// 选择第一个报告类型
selectProductType(1) // 使用ID 1选择第一个报告类型
} else {
uni.showToast({
title: res.msg || '获取配置失败',
icon: 'none'
})
}
} catch (error) {
console.log(error)
uni.showToast({
title: '网络错误',
icon: 'none'
})
}
}
// 价格变更
const onPriceChange = (price) => {
formData.value.clientPrice = price
}
// 类型选择确认
const onConfirmType = (e) => {
// picker在单列模式下返回的是选中项的值
if (e && e.value && e.value.length > 0) {
const selectedValue = e.value[0]
selectProductType(selectedValue)
}
}
// 显示公众号二维码
const showGzhQrcodeModal = () => {
showGzhQrcode.value = true
}
// 页面加载
onMounted(() => {
getPromoteConfig()
})
</script>
<style>
.card {
border-radius: 12px;
background-color: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
padding: 16px;
margin-bottom: 16px;
}
</style>
<route type="page" lang="json">{
"layout": "page",
"title": "推广",
"agent": true,
"auth": true
}</route>

View File

@@ -0,0 +1,144 @@
<template>
<view class="min-h-screen bg-gray-50">
<!-- 收益列表 -->
<uni-list :loading="loading" :loadmore="loadMoreStatus" @loadmore="onLoadMore">
<!-- 空状态提示 -->
<view v-if="!loading && list.length === 0" class="flex flex-col items-center justify-center py-16">
<image src="/static/image/empty.svg" mode="aspectFit" class="w-48 h-48 mb-4" />
<text class="text-gray-400 text-base">暂无直推报告</text>
</view>
<view v-for="(item, index) in list" :key="index" class="mx-4 my-2 bg-white rounded-lg p-4 shadow-sm">
<view class="flex justify-between items-center mb-2">
<text class="text-gray-500 text-sm">{{ item.create_time || '-' }}</text>
<text class="text-green-500 font-bold">+{{ item.amount.toFixed(2) }}</text>
</view>
<view class="flex items-center">
<text class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium"
:class="getReportTypeStyle(item.product_name)">
<text class="w-2 h-2 rounded-full mr-1 inline-block" :class="getDotColor(item.product_name)"></text>
{{ item.product_name }}
</text>
</view>
</view>
<!-- 加载更多/加载完成提示 -->
<uni-load-more :status="loadMoreStatus" />
</uni-list>
</view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { getAgentCommission } from '@/apis/agent'
// 颜色配置(根据产品名称映射)
const typeColors = {
'老板企业报告': { bg: 'bg-blue-100', text: 'text-blue-800', dot: 'bg-blue-500' },
'人事背调': { bg: 'bg-green-100', text: 'text-green-800', dot: 'bg-green-500' },
'家政风险': { bg: 'bg-purple-100', text: 'text-purple-800', dot: 'bg-purple-500' },
'婚恋风险': { bg: 'bg-pink-100', text: 'text-pink-800', dot: 'bg-pink-500' },
'贷前背调': { bg: 'bg-orange-100', text: 'text-orange-800', dot: 'bg-orange-500' },
'租赁风险': { bg: 'bg-indigo-100', text: 'text-indigo-800', dot: 'bg-indigo-500' },
'个人风险': { bg: 'bg-red-100', text: 'text-red-800', dot: 'bg-red-500' },
// 默认类型
'default': { bg: 'bg-gray-100', text: 'text-gray-800', dot: 'bg-gray-500' }
}
const page = ref(1)
const pageSize = ref(10)
const total = ref(0)
const list = ref([])
const loading = ref(false)
const loadMoreStatus = ref('more') // 'more'|'loading'|'noMore'
// 获取颜色样式
const getReportTypeStyle = (name) => {
const color = typeColors[name] || typeColors.default
return `${color.bg} ${color.text}`
}
// 获取小圆点颜色
const getDotColor = (name) => {
return (typeColors[name] || typeColors.default).dot
}
// 加载更多数据
const onLoadMore = async () => {
if (loadMoreStatus.value === 'noMore') return
page.value++
await getData()
}
// 获取数据
const getData = async () => {
try {
loading.value = true
loadMoreStatus.value = 'loading'
const res = await getAgentCommission({
page: page.value,
page_size: pageSize.value
})
if (res.code === 200) {
// 首次加载
if (page.value === 1) {
list.value = res.data.list
total.value = res.data.total
} else {
// 分页加载
list.value.push(...res.data.list)
}
// 判断是否加载完成
if (list.value.length >= res.data.total || res.data.list.length < pageSize.value) {
loadMoreStatus.value = 'noMore'
} else {
loadMoreStatus.value = 'more'
}
} else {
uni.showToast({
title: res.msg || '加载失败',
icon: 'none'
})
}
} catch (error) {
uni.showToast({
title: '网络错误',
icon: 'none'
})
} finally {
loading.value = false
}
}
// 页面加载
onMounted(() => {
getData()
})
// 页面下拉刷新
const onPullDownRefresh = () => {
page.value = 1
loadMoreStatus.value = 'more'
getData().then(() => {
uni.stopPullDownRefresh()
})
}
// 导出页面生命周期方法
defineExpose({
onPullDownRefresh
})
</script>
<route type="page" lang="json">
{
"layout": "page",
"title": "直推报告",
"agent": true,
"auth": true
}
</route>

View File

@@ -0,0 +1,165 @@
<template>
<view class="min-h-screen bg-gray-50">
<!-- 收益列表 -->
<uni-list :loading="loading" :loadmore="loadMoreStatus" @loadmore="onLoadMore">
<!-- 空状态提示 -->
<view v-if="!loading && list.length === 0" class="flex flex-col items-center justify-center py-16">
<image src="/static/image/empty.svg" mode="aspectFit" class="w-48 h-48 mb-4" />
<text class="text-gray-400 text-base">暂无收益记录</text>
</view>
<view v-for="(item, index) in list" :key="index" class="mx-4 my-2 bg-white rounded-lg p-4 shadow-sm">
<view class="flex justify-between items-center mb-2">
<text class="text-gray-500 text-sm">{{ item.create_time || '-' }}</text>
<text class="text-green-500 font-bold">+{{ item.amount.toFixed(2) }}</text>
</view>
<view class="flex items-center">
<text class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium"
:class="getReportTypeStyle(item.type)">
<text class="w-2 h-2 rounded-full mr-1 inline-block" :class="getDotColor(item.type)"></text>
{{ typeToChinese(item.type) }}
</text>
</view>
</view>
<!-- 加载更多/加载完成提示 -->
<uni-load-more :status="loadMoreStatus" />
</uni-list>
</view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { getAgentRewards } from '@/apis/agent'
// 类型映射配置
const typeConfig = {
descendant_promotion: {
chinese: '下级推广奖励',
color: { bg: 'bg-blue-100', text: 'text-blue-800', dot: 'bg-blue-500' }
},
descendant_upgrade_vip: {
chinese: '下级升级VIP奖励',
color: { bg: 'bg-green-100', text: 'text-green-800', dot: 'bg-green-500' }
},
descendant_upgrade_svip: {
chinese: '下级升级SVIP奖励',
color: { bg: 'bg-purple-100', text: 'text-purple-800', dot: 'bg-purple-500' }
},
descendant_stay_activedescendant: {
chinese: '下级活跃奖励',
color: { bg: 'bg-pink-100', text: 'text-pink-800', dot: 'bg-pink-500' }
},
new_active: {
chinese: '新增活跃奖励',
color: { bg: 'bg-orange-100', text: 'text-orange-800', dot: 'bg-orange-500' }
},
descendant_withdraw: {
chinese: '下级提现奖励',
color: { bg: 'bg-indigo-100', text: 'text-indigo-800', dot: 'bg-indigo-500' }
},
default: {
chinese: '其他奖励',
color: { bg: 'bg-gray-100', text: 'text-gray-800', dot: 'bg-gray-500' }
}
}
const page = ref(1)
const pageSize = ref(10)
const total = ref(0)
const list = ref([])
const loading = ref(false)
const loadMoreStatus = ref('more') // 'more'|'loading'|'noMore'
// 类型转中文
const typeToChinese = (type) => {
return typeConfig[type]?.chinese || typeConfig.default.chinese
}
// 获取颜色样式
const getReportTypeStyle = (type) => {
const config = typeConfig[type] || typeConfig.default
return `${config.color.bg} ${config.color.text}`
}
// 获取小圆点颜色
const getDotColor = (type) => {
return typeConfig[type]?.color.dot || typeConfig.default.color.dot
}
// 加载更多数据
const onLoadMore = async () => {
if (loadMoreStatus.value === 'noMore') return
page.value++
await getData()
}
// 获取数据
const getData = async () => {
try {
loading.value = true
loadMoreStatus.value = 'loading'
const res = await getAgentRewards({
page: page.value,
page_size: pageSize.value
})
if (res.code === 200) {
if (page.value === 1) {
list.value = res.data.list
total.value = res.data.total
} else {
list.value.push(...res.data.list)
}
if (list.value.length >= res.data.total || res.data.list.length < pageSize.value) {
loadMoreStatus.value = 'noMore'
} else {
loadMoreStatus.value = 'more'
}
} else {
uni.showToast({
title: res.msg || '加载失败',
icon: 'none'
})
}
} catch (error) {
uni.showToast({
title: '网络错误',
icon: 'none'
})
} finally {
loading.value = false
}
}
// 页面加载
onMounted(() => {
getData()
})
// 页面下拉刷新
const onPullDownRefresh = () => {
page.value = 1
loadMoreStatus.value = 'more'
getData().then(() => {
uni.stopPullDownRefresh()
})
}
// 导出页面生命周期方法
defineExpose({
onPullDownRefresh
})
</script>
<route type="page" lang="json">
{
"layout": "page",
"title": "收益明细",
"agent": true,
"auth": true
}
</route>

26
src/pages/vip.vue Normal file
View File

@@ -0,0 +1,26 @@
<template>
<view class="relative">
<image class="w-full" src="/static/images/vip_bg.png" mode="widthFix" />
<view @click="toService"
class="absolute left-[50%] translate-x-[-50%] bottom-80 bg-gradient-to-r from-gray-900 via-black to-gray-900 py-2 px-4 rounded-lg text-white text-[24px] font-bold shadow-[0_0_15px_rgba(255,255,255,0.3)]"
hover-class="scale-105">
点击马上报名
</view>
</view>
</template>
<script setup>
function toService() {
// 跳转到客服页面
uni.navigateTo({
url: 'https://work.weixin.qq.com/kfid/kfc5c19b2b93a5e73b9'
})
}
</script>
<style>
.scale-105 {
transform: scale(1.05);
transition: transform 0.3s;
}
</style>

View File

@@ -0,0 +1,181 @@
<template>
<view class="min-h-screen bg-gray-50">
<!-- 提现记录列表 -->
<uni-list :loading="loading" :loadmore="loadMoreStatus" @loadmore="onLoadMore">
<!-- 空状态提示 -->
<view v-if="!loading && list.length === 0" class="flex flex-col items-center justify-center py-16">
<image src="/static/image/empty.svg" mode="aspectFit" class="w-48 h-48 mb-4" />
<text class="text-gray-400 text-base">暂无提现记录</text>
</view>
<view v-for="(item, index) in list" :key="index" class="mx-4 my-2 bg-white rounded-lg p-4 shadow-sm">
<view class="flex justify-between items-center mb-2">
<text class="text-gray-500 text-sm">{{ item.create_time || '-' }}</text>
<text class="font-bold" :class="getAmountColor(item.status)">{{ item.amount.toFixed(2) }}</text>
</view>
<view class="flex items-center mb-2">
<text class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium"
:class="getStatusStyle(item.status)">
<text class="w-2 h-2 rounded-full mr-1 inline-block" :class="getDotColor(item.status)"></text>
{{ statusToChinese(item.status) }}
</text>
</view>
<view class="text-xs text-gray-500">
<text v-if="item.payee_account" class="block">收款账户{{ maskName(item.payee_account) }}</text>
<text v-if="item.remark" class="block">备注{{ item.remark }}</text>
</view>
</view>
<!-- 加载更多/加载完成提示 -->
<uni-load-more :status="loadMoreStatus" />
</uni-list>
</view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { getWithdrawalRecords } from '@/apis/agent'
// 状态映射配置
const statusConfig = {
1: {
chinese: '处理中',
color: {
bg: 'bg-yellow-100',
text: 'text-yellow-800',
dot: 'bg-yellow-500',
amount: 'text-yellow-500'
}
},
2: {
chinese: '提现成功',
color: {
bg: 'bg-green-100',
text: 'text-green-800',
dot: 'bg-green-500',
amount: 'text-green-500'
}
},
3: {
chinese: '提现失败',
color: {
bg: 'bg-red-100',
text: 'text-red-800',
dot: 'bg-red-500',
amount: 'text-red-500'
}
}
}
const page = ref(1)
const pageSize = ref(10)
const total = ref(0)
const list = ref([])
const loading = ref(false)
const loadMoreStatus = ref('more') // 'more'|'loading'|'noMore'
// 账户脱敏处理
const maskName = (name) => {
if (!name || typeof name !== 'string') return ''
if (name.length <= 7) return name
return name.substring(0, 3) + '****' + name.substring(7)
}
// 状态转中文
const statusToChinese = (status) => {
return statusConfig[status]?.chinese || '未知状态'
}
// 获取状态样式
const getStatusStyle = (status) => {
const config = statusConfig[status] || {}
return `${config.color?.bg || 'bg-gray-100'} ${config.color?.text || 'text-gray-800'}`
}
// 获取小圆点颜色
const getDotColor = (status) => {
return statusConfig[status]?.color.dot || 'bg-gray-500'
}
// 获取金额颜色
const getAmountColor = (status) => {
return statusConfig[status]?.color.amount || 'text-gray-500'
}
// 加载更多数据
const onLoadMore = async () => {
if (loadMoreStatus.value === 'noMore') return
page.value++
await getData()
}
// 获取数据
const getData = async () => {
try {
loading.value = true
loadMoreStatus.value = 'loading'
const res = await getWithdrawalRecords({
page: page.value,
page_size: pageSize.value
})
if (res.code === 200) {
if (page.value === 1) {
list.value = res.data.list
total.value = res.data.total
} else {
list.value.push(...res.data.list)
}
if (list.value.length >= res.data.total || res.data.list.length < pageSize.value) {
loadMoreStatus.value = 'noMore'
} else {
loadMoreStatus.value = 'more'
}
} else {
uni.showToast({
title: res.msg || '加载失败',
icon: 'none'
})
}
} catch (error) {
uni.showToast({
title: '网络错误',
icon: 'none'
})
} finally {
loading.value = false
}
}
// 页面加载
onMounted(() => {
getData()
})
// 页面下拉刷新
const onPullDownRefresh = () => {
page.value = 1
loadMoreStatus.value = 'more'
getData().then(() => {
uni.stopPullDownRefresh()
})
}
// 导出页面生命周期方法
defineExpose({
onPullDownRefresh
})
</script>
<style>
/* 自定义样式 */
</style>
<route type="page" lang="json">{
"layout": "page",
"title": "提现记录",
"auth": true,
"agent": true
}</route>