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
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:
287
src/pages/agent.vue
Normal file
287
src/pages/agent.vue
Normal 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
63
src/pages/agentVip.vue
Normal 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
816
src/pages/agentVipApply.vue
Normal 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">
|
||||
下级成为VIP、SVIP,高额奖励立马发放,<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>
|
||||
513
src/pages/agentVipConfig.vue
Normal file
513
src/pages/agentVipConfig.vue
Normal 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
72
src/pages/agreement.vue
Normal 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
123
src/pages/index.vue
Normal 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
52
src/pages/invitation.vue
Normal 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>
|
||||
201
src/pages/invitationAgentApply.vue
Normal file
201
src/pages/invitationAgentApply.vue
Normal 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
223
src/pages/login.vue
Normal 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
296
src/pages/me.vue
Normal 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
265
src/pages/promote.vue
Normal 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>
|
||||
144
src/pages/promoteDetails.vue
Normal file
144
src/pages/promoteDetails.vue
Normal 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>
|
||||
165
src/pages/rewardsDetails.vue
Normal file
165
src/pages/rewardsDetails.vue
Normal 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
26
src/pages/vip.vue
Normal 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>
|
||||
181
src/pages/withdrawDetails.vue
Normal file
181
src/pages/withdrawDetails.vue
Normal 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>
|
||||
Reference in New Issue
Block a user