a
This commit is contained in:
378
src/views/Agent.vue
Normal file
378
src/views/Agent.vue
Normal file
@@ -0,0 +1,378 @@
|
||||
<template>
|
||||
<div class="p-4 bg-gradient-to-b from-gray-50/50 to-gray-100/30 min-h-screen">
|
||||
<!-- 非代理用户提示 -->
|
||||
<div v-if="isLoggedIn && !isAgent" class="rounded-xl shadow-lg mb-4 bg-white p-6 text-center">
|
||||
<div class="text-lg font-bold mb-2" style="color: var(--van-text-color);">您还不是代理</div>
|
||||
<div class="text-sm mb-4" style="color: var(--van-text-color-2);">注册成为代理后即可查看团队统计和转化率数据</div>
|
||||
<button @click="toRegister" class="px-6 py-2 rounded-full text-white"
|
||||
style="background: linear-gradient(135deg, #8b5cf6, #7c3aed);">
|
||||
注册成为代理
|
||||
</button>
|
||||
</div>
|
||||
<!-- 团队统计 -->
|
||||
<div v-if="isAgent" class="rounded-xl shadow-lg mb-4 bg-gradient-to-r from-purple-50/60 to-purple-100/50 p-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="flex items-center">
|
||||
<van-icon name="friends-o" class="text-xl mr-2" style="color: #8b5cf6;" />
|
||||
<span class="text-lg font-bold" style="color: var(--van-text-color);">团队统计</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 第二行:总数/今日新增/本月新增 -->
|
||||
<div class="grid grid-cols-3 gap-3 mb-4">
|
||||
<div class="text-center p-2 rounded-lg bg-white/50">
|
||||
<div class="text-lg font-bold" style="color: #8b5cf6;">{{ teamStats?.total_count || 0 }}</div>
|
||||
<div class="text-sm mt-1" style="color: var(--van-theme-primary);">团队人数</div>
|
||||
</div>
|
||||
<div class="text-center p-2 rounded-lg bg-white/50">
|
||||
<div class="text-lg font-bold" style="color: #8b5cf6;">{{ teamStats?.today_new_members || 0 }}</div>
|
||||
<div class="text-sm mt-1" style="color: var(--van-theme-primary);">今日新增</div>
|
||||
</div>
|
||||
<div class="text-center p-2 rounded-lg bg-white/50">
|
||||
<div class="text-lg font-bold" style="color: #8b5cf6;">{{ teamStats?.month_new_members || 0 }}</div>
|
||||
<div class="text-sm mt-1" style="color: var(--van-theme-primary);">本月新增</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 第一行:直接/间接、黄金/普通 -->
|
||||
<div class="grid grid-cols-4 gap-3 mb-4">
|
||||
<div class="text-center p-2 rounded-lg bg-white/50">
|
||||
<div class="text-lg font-bold" style="color: #8b5cf6;">{{ teamStats?.direct_count || 0 }}</div>
|
||||
<div class="text-sm mt-1" style="color: var(--van-theme-primary);">直接下级</div>
|
||||
</div>
|
||||
<div class="text-center p-2 rounded-lg bg-white/50">
|
||||
<div class="text-lg font-bold" style="color: #8b5cf6;">{{ teamStats?.indirect_count || 0 }}</div>
|
||||
<div class="text-sm mt-1" style="color: var(--van-theme-primary);">间接下级</div>
|
||||
</div>
|
||||
<div class="text-center p-2 rounded-lg bg-white/50">
|
||||
<div class="text-lg font-bold" style="color: #f59e0b;">{{ teamStats?.gold_count || 0 }}</div>
|
||||
<div class="text-sm mt-1" style="color: var(--van-theme-primary);">黄金下级</div>
|
||||
</div>
|
||||
<div class="text-center p-2 rounded-lg bg-white/50">
|
||||
<div class="text-lg font-bold" style="color: #6b7280;">{{ teamStats?.normal_count || 0 }}</div>
|
||||
<div class="text-sm mt-1" style="color: var(--van-theme-primary);">普通下级</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<button @click="toTeamList"
|
||||
class="w-full text-white rounded-full py-2 px-4 shadow-md flex items-center justify-center"
|
||||
style="background: linear-gradient(135deg, #8b5cf6, #7c3aed);">
|
||||
<van-icon name="friends" class="mr-1" />
|
||||
查看我的团队
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 我的转化率 -->
|
||||
<div v-if="isAgent" class="relative rounded-xl shadow-lg mb-3 bg-white overflow-hidden">
|
||||
<!-- 标题和Tab区域 -->
|
||||
<div class="relative p-5 pb-0">
|
||||
<div class="flex items-center mb-4">
|
||||
<van-icon name="chart-trending-o" class="text-lg mr-2" style="color: var(--van-theme-primary);" />
|
||||
<span class="text-lg font-bold" style="color: var(--van-text-color);">我的转化率</span>
|
||||
</div>
|
||||
<!-- 自定义 Tab,与title对齐 -->
|
||||
<div class="absolute top-3 right-3 flex p-1 rounded-2xl"
|
||||
style="background-color: var(--van-theme-primary);">
|
||||
<button @click="myConversionActiveTab = 'daily'" :class="[
|
||||
'px-6 py-2 text-sm font-medium rounded-lg mx-1',
|
||||
myConversionActiveTab === 'daily'
|
||||
? 'bg-white text-gray-800'
|
||||
: 'text-white'
|
||||
]">
|
||||
日
|
||||
</button>
|
||||
<button @click="myConversionActiveTab = 'weekly'" :class="[
|
||||
'px-6 py-2 text-sm font-medium rounded-lg mx-1',
|
||||
myConversionActiveTab === 'weekly'
|
||||
? 'bg-white text-gray-800'
|
||||
: 'text-white'
|
||||
]">
|
||||
周
|
||||
</button>
|
||||
<button @click="myConversionActiveTab = 'monthly'" :class="[
|
||||
'px-6 py-2 text-sm font-medium rounded-lg mx-1',
|
||||
myConversionActiveTab === 'monthly'
|
||||
? 'bg-white text-gray-800'
|
||||
: 'text-white'
|
||||
]">
|
||||
月
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="p-5 pt-0">
|
||||
<div v-if="!isLoggedIn"
|
||||
class="p-4 rounded-lg bg-gradient-to-r from-gray-50 to-white border border-gray-200 text-center">
|
||||
<div class="text-sm mb-3" style="color: var(--van-text-color);">请先登录后查看数据</div>
|
||||
<button @click="toLogin" class="px-4 py-2 rounded-full text-white"
|
||||
style="background: linear-gradient(135deg, #8b5cf6, #7c3aed);">
|
||||
去登录
|
||||
</button>
|
||||
</div>
|
||||
<template v-else>
|
||||
<template v-if="isLoadingConversion">
|
||||
<div class="p-3">
|
||||
<van-skeleton :title="false" :row="3" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-if="myConversionActiveTab === 'daily'" class="">
|
||||
<div v-for="item in conversionData?.my_conversion_rate?.daily || []"
|
||||
:key="item.period_label" class="p-4 flex items-center border-b border-gray-200 ">
|
||||
<div class="text-lg font-semibold mr-4" style="color: var(--van-text-color);">
|
||||
{{ item.period_label }}
|
||||
</div>
|
||||
<div class="flex items-center gap-6 text-base">
|
||||
<div style="color: var(--van-theme-primary);">
|
||||
{{ item.query_user_count || 0 }}人查询
|
||||
</div>
|
||||
<div style="color: var(--van-theme-primary);">
|
||||
{{ item.paid_user_count || 0 }}人付费
|
||||
</div>
|
||||
<div style="color: var(--van-theme-primary);">
|
||||
总金额: {{ (item.total_amount || 0).toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="myConversionActiveTab === 'weekly'" class="">
|
||||
<div v-for="item in conversionData?.my_conversion_rate?.weekly || []"
|
||||
:key="item.period_label" class="p-4 flex items-center border-b border-gray-200 ">
|
||||
<div class="text-lg font-semibold mr-4" style="color: var(--van-text-color);">
|
||||
{{ item.period_label }}
|
||||
</div>
|
||||
<div class="flex items-center gap-6 text-base">
|
||||
<div style="color: var(--van-theme-primary);">
|
||||
{{ item.query_user_count || 0 }}人查询
|
||||
</div>
|
||||
<div style="color: var(--van-theme-primary);">
|
||||
{{ item.paid_user_count || 0 }}人付费
|
||||
</div>
|
||||
<div style="color: var(--van-theme-primary);">
|
||||
总金额: {{ (item.total_amount || 0).toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="myConversionActiveTab === 'monthly'" class="">
|
||||
<div v-for="item in conversionData?.my_conversion_rate?.monthly || []"
|
||||
:key="item.period_label" class="p-4 flex items-center border-b border-gray-200 ">
|
||||
<div class="text-lg font-semibold mr-4" style="color: var(--van-text-color);">
|
||||
{{ item.period_label }}
|
||||
</div>
|
||||
<div class="flex items-center gap-6 text-base">
|
||||
<div style="color: var(--van-theme-primary);">
|
||||
{{ item.query_user_count || 0 }}人查询
|
||||
</div>
|
||||
<div style="color: var(--van-theme-primary);">
|
||||
{{ item.paid_user_count || 0 }}人付费
|
||||
</div>
|
||||
<div style="color: var(--van-theme-primary);">
|
||||
总金额: {{ (item.total_amount || 0).toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 我的下级转化率 -->
|
||||
<div v-if="isAgent" class="relative rounded-xl shadow-lg mb-3 bg-white overflow-hidden">
|
||||
<!-- 标题和Tab区域 -->
|
||||
<div class="relative p-5 pb-0">
|
||||
<div class="flex items-center mb-4">
|
||||
<van-icon name="bar-chart-o" class="text-lg mr-2" style="color: var(--color-success);" />
|
||||
<span class="text-lg font-bold" style="color: var(--van-text-color);">我的下级转化率</span>
|
||||
</div>
|
||||
<!-- 自定义 Tab,与title对齐 -->
|
||||
<div class="absolute top-3 right-3 flex p-1 rounded-2xl"
|
||||
style="background-color: var(--van-theme-primary);">
|
||||
<button @click="subordinateConversionActiveTab = 'daily'" :class="[
|
||||
'px-6 py-2 text-sm font-medium rounded-lg mx-1',
|
||||
subordinateConversionActiveTab === 'daily'
|
||||
? 'bg-white text-gray-800'
|
||||
: 'text-white'
|
||||
]">
|
||||
日
|
||||
</button>
|
||||
<button @click="subordinateConversionActiveTab = 'weekly'" :class="[
|
||||
'px-6 py-2 text-sm font-medium rounded-lg mx-1',
|
||||
subordinateConversionActiveTab === 'weekly'
|
||||
? 'bg-white text-gray-800'
|
||||
: 'text-white'
|
||||
]">
|
||||
周
|
||||
</button>
|
||||
<button @click="subordinateConversionActiveTab = 'monthly'" :class="[
|
||||
'px-6 py-2 text-sm font-medium rounded-lg mx-1',
|
||||
subordinateConversionActiveTab === 'monthly'
|
||||
? 'bg-white text-gray-800'
|
||||
: 'text-white'
|
||||
]">
|
||||
月
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="p-5 pt-0">
|
||||
<div v-if="!isLoggedIn"
|
||||
class="p-4 rounded-lg bg-gradient-to-r from-gray-50 to-white border border-gray-200 text-center">
|
||||
<div class="text-sm mb-3" style="color: var(--van-text-color);">请先登录后查看数据</div>
|
||||
<button @click="toLogin" class="px-4 py-2 rounded-full text-white"
|
||||
style="background: linear-gradient(135deg, #8b5cf6, #7c3aed);">
|
||||
去登录
|
||||
</button>
|
||||
</div>
|
||||
<template v-else>
|
||||
<template v-if="isLoadingConversion">
|
||||
<div class="p-3">
|
||||
<van-skeleton :title="false" :row="3" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-if="subordinateConversionActiveTab === 'daily'" class="">
|
||||
<div v-for="item in conversionData?.subordinate_conversion_rate?.daily || []"
|
||||
:key="item.period_label" class="p-4 flex items-center border-b border-gray-200 ">
|
||||
<div class="text-lg font-semibold mr-4" style="color: var(--van-text-color);">
|
||||
{{ item.period_label }}
|
||||
</div>
|
||||
<div class="flex items-center gap-6 text-base">
|
||||
<div style="color: var(--van-theme-primary);">
|
||||
{{ item.query_user_count || 0 }}人查询
|
||||
</div>
|
||||
<div style="color: var(--van-theme-primary);">
|
||||
{{ item.paid_user_count || 0 }}人付费
|
||||
</div>
|
||||
<div style="color: var(--van-theme-primary);">
|
||||
总金额: {{ (item.total_amount || 0).toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="subordinateConversionActiveTab === 'weekly'" class="">
|
||||
<div v-for="item in conversionData?.subordinate_conversion_rate?.weekly || []"
|
||||
:key="item.period_label" class="p-4 flex items-center border-b border-gray-200 ">
|
||||
<div class="text-lg font-semibold mr-4" style="color: var(--van-text-color);">
|
||||
{{ item.period_label }}
|
||||
</div>
|
||||
<div class="flex items-center gap-6 text-base">
|
||||
<div style="color: var(--van-theme-primary);">
|
||||
{{ item.query_user_count || 0 }}人查询
|
||||
</div>
|
||||
<div style="color: var(--van-theme-primary);">
|
||||
{{ item.paid_user_count || 0 }}人付费
|
||||
</div>
|
||||
<div style="color: var(--van-theme-primary);">
|
||||
总金额: {{ (item.total_amount || 0).toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="subordinateConversionActiveTab === 'monthly'" class="">
|
||||
<div v-for="item in conversionData?.subordinate_conversion_rate?.monthly || []"
|
||||
:key="item.period_label" class="p-4 flex items-center border-b border-gray-200 ">
|
||||
<div class="text-lg font-semibold mr-4" style="color: var(--van-text-color);">
|
||||
{{ item.period_label }}
|
||||
</div>
|
||||
<div class="flex items-center gap-6 text-base">
|
||||
<div style="color: var(--van-theme-primary);">
|
||||
{{ item.query_user_count || 0 }}人查询
|
||||
</div>
|
||||
<div style="color: var(--van-theme-primary);">
|
||||
{{ item.paid_user_count || 0 }}人付费
|
||||
</div>
|
||||
<div style="color: var(--van-theme-primary);">
|
||||
总金额: {{ (item.total_amount || 0).toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { storeToRefs } from "pinia";
|
||||
import { ref, computed, onMounted, watch } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getTeamStatistics, getConversionRate } from '@/api/agent'
|
||||
import { useAgentStore } from "@/stores/agentStore";
|
||||
import { useUserStore } from "@/stores/userStore";
|
||||
import { showToast } from "vant";
|
||||
|
||||
const agentStore = useAgentStore();
|
||||
const { isAgent } = storeToRefs(agentStore);
|
||||
const userStore = useUserStore();
|
||||
const { isLoggedIn, mobile } = storeToRefs(userStore);
|
||||
const router = useRouter();
|
||||
const teamStats = ref(null);
|
||||
const conversionData = ref(null);
|
||||
const myConversionActiveTab = ref('daily');
|
||||
const subordinateConversionActiveTab = ref('daily');
|
||||
const isLoadingConversion = ref(true);
|
||||
const hasLoaded = ref(false);
|
||||
|
||||
const getData = async () => {
|
||||
// 获取团队统计
|
||||
const { data: teamData, error: teamError } = await getTeamStatistics();
|
||||
if (teamData.value?.code === 200 && !teamError.value) {
|
||||
teamStats.value = teamData.value.data;
|
||||
}
|
||||
|
||||
// 获取转化率统计
|
||||
const { data: conversionRateData, error: conversionError } = await getConversionRate();
|
||||
if (conversionRateData.value?.code === 200 && !conversionError.value) {
|
||||
conversionData.value = conversionRateData.value.data;
|
||||
}
|
||||
isLoadingConversion.value = false;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (isAgent.value && !hasLoaded.value) {
|
||||
isLoadingConversion.value = true;
|
||||
hasLoaded.value = true;
|
||||
getData();
|
||||
}
|
||||
if (!isAgent.value) {
|
||||
isLoadingConversion.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
watch(isAgent, (val) => {
|
||||
if (val && !hasLoaded.value) {
|
||||
isLoadingConversion.value = true;
|
||||
hasLoaded.value = true;
|
||||
getData();
|
||||
}
|
||||
});
|
||||
|
||||
// 添加跳转到团队列表的方法
|
||||
const toTeamList = () => {
|
||||
router.push("/agent/teamList");
|
||||
};
|
||||
|
||||
const toPromotionQueryList = () => {
|
||||
router.push({ name: "agentPromotionQueryList" });
|
||||
};
|
||||
|
||||
const toRegister = () => {
|
||||
if (mobile.value) {
|
||||
router.push({ path: "/register", query: { mobile: mobile.value } });
|
||||
} else {
|
||||
router.push("/register");
|
||||
}
|
||||
};
|
||||
|
||||
const toLogin = () => {
|
||||
router.push("/login");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
852
src/views/AgentManageAgreement.vue
Normal file
852
src/views/AgentManageAgreement.vue
Normal file
@@ -0,0 +1,852 @@
|
||||
<template>
|
||||
<div class="container mx-auto p-4 text-gray-800">
|
||||
<div class="box">
|
||||
<p class="text-center font-bold text-xl mb-4">代理管理制度</p>
|
||||
|
||||
<p class="indent-8 mb-2"><span>一、</span><strong>前言</strong></p>
|
||||
<p class="indent-8 mb-2">
|
||||
{{ companyName }}为加强对全国代理的统一管理,规范各代理行为,确保"{{ appName }}"的顺利推广,特依据如下原则制定代理管理制度,望各级代理认真贯彻、严格遵守。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">1.谨慎性原则</p>
|
||||
<p class="indent-8 mb-2">
|
||||
本着对双方负责的态度,请各级代理务必认真贯彻执行本管理制度的工作程序,不可草率行事。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">2.用心协助原则</p>
|
||||
<p class="indent-8 mb-2">
|
||||
{{ companyName }}配合各代理的工作,对于代理在推广工作中遇到的问题用心配合解决。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">3.诚信的原则</p>
|
||||
<p class="indent-8 mb-2">双方务必诚实有信用,决不提供虚假信息。</p>
|
||||
<p class="indent-8 mb-2">4.严格管理原则</p>
|
||||
<p class="indent-8 mb-2">
|
||||
认真贯彻执行各项管理制度。对违反管理制度的代理,坚决按制度规定予以处罚,直至取消代理资格,决不姑息迁就。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">5.双方共赢原则</p>
|
||||
<p class="indent-8 mb-2">
|
||||
{{ companyName }}的目标是与代理共赢,共同发展。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">6.长期性原则</p>
|
||||
<p class="indent-8 mb-2">
|
||||
立足市场,与代理长期协作,确保代理用心放心地进行市场推广工作。
|
||||
</p>
|
||||
|
||||
<p class="indent-8 mb-2"><strong>二、总则</strong></p>
|
||||
<p class="indent-8 mb-2">
|
||||
第一条 代理期限为一年,代理协议实行一年一签制。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
第二条
|
||||
本制度规定{{ companyName }}代理(以下称代理)权限、运作及业务处理等相关事项,旨在使{{ companyName }}与各代理之间持续良好合作关系,促进双方共同发展;
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
第三条
|
||||
代理经{{ companyName }}授权并自代理协议书生效之日起,应严格依照代理协议及本制度的规定履行义务,享受权利。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
第四条
|
||||
{{ companyName }}确定的代理应遵循{{ companyName }}的规定从事代理活动,不得做出损害{{ companyName }}利益和形象的行为;
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
第五条
|
||||
代理在代理推广过程中,应妥善处理做好售前、售中、售后的咨询维护工作。
|
||||
</p>
|
||||
|
||||
<p class="indent-8 mb-2"><strong>三、开通代理账户要求</strong></p>
|
||||
<p class="indent-8 mb-2"><strong>个人类:</strong></p>
|
||||
<p class="indent-8 mb-2">1、完全民事行为能力人。</p>
|
||||
<p class="indent-8 mb-2">2、本人实名认证的手机号。</p>
|
||||
<p class="indent-8 mb-2">
|
||||
3、首次提现时必须进行本人实名认证并进行人脸识别。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
4、全面赞同{{ appName }}的各项制度,并能积极参加{{ appName }}为各代理所举办的各种活动;
|
||||
</p>
|
||||
|
||||
<p class="indent-8 mb-2">企业类:</p>
|
||||
<p class="indent-8 mb-2">
|
||||
1、具有独立法人资格,并能提供有效营业执照、组织代码证等相关文件复印件,经审查合格签定代理协议后即成为{{ companyName }}认证代理。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
2、应具备良好的经营规模、办公条件、设备及人员,有固定的营业场所,良好的资信潜力和商业信誉。并提供以下资料:
|
||||
</p>
|
||||
<p class="indent-8 mb-2">◆营业执照复印件</p>
|
||||
<p class="indent-8 mb-2">◆身份证复印件</p>
|
||||
<p class="indent-8 mb-2">◆代理合作协议</p>
|
||||
<p class="indent-8 mb-2">◆业务场景展示</p>
|
||||
<p class="indent-8 mb-2">
|
||||
3、全面赞同{{ appName }}的各项制度,并能积极参加{{ appName }}为各代理所举办的各种活动;
|
||||
</p>
|
||||
|
||||
<p class="indent-8 mb-4"><strong>四、代理权利和义务</strong></p>
|
||||
<p class="indent-8 mb-2">
|
||||
在成为{{ companyName }}的认证代理后,可享有如下权利并承担相应的义务:
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
1、使用{{ appName }}开展广告宣传、市场推广活动;
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
2、维护{{ companyName }}及其产品的良好形象;
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
3、开拓下级业务推广并负责对其定期进行业务培训;
|
||||
</p>
|
||||
<p class="indent-8 mb-2">4、推广过程中做好售前、售中、售后工作。</p>
|
||||
<p class="indent-8 mb-2">
|
||||
5、如用户需要开具发票,代理则需向用户开具(咨询费)发票。如代理未开具发票,{{ appName }}有义务配合税务机关采取相关措施。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
6、代理业务推广过程中,未经{{ companyName }}授权,不得使用"{{ appName }}官方"词汇用于广告宣传。
|
||||
</p>
|
||||
|
||||
<p class="font-bold mb-2">五、推广管理</p>
|
||||
<p class="indent-8 mb-2">
|
||||
1、{{ appName }}负责建立与代理之间的沟通与联系渠道,不定期地向代理提供宣传资料、信息、政策以及推广方案与管理制度等方面的支持。
|
||||
</p>
|
||||
<p class="indent-8 mb-2"><strong>5.1 如何推广报告</strong></p>
|
||||
<p class="indent-8 mb-2">
|
||||
(1)登录代理账户后,进入"推广"页面,选择要推广的报告类型(如个人大数据、婚恋风险、入职背调等)。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
(2)系统将自动生成专属推广链接,您可以将该链接分享给潜在用户。用户通过您的推广链接购买查询服务后,您即可获得推广佣金。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
(3)您可以在推广页面设置查询服务的售价(需在平台设定的价格范围内),售价与成本价的差额即为您的推广收益。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
(4)推广链接支持多种分享方式,包括复制链接、生成二维码、生成短链等,方便您在不同渠道进行推广。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
(5)您可以在"推广查询记录"中查看所有通过您推广链接产生的订单详情,包括订单金额、收益金额、订单状态等信息。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
(6)推广佣金将在用户完成支付后自动结算到您的账户余额中,您可以在"我的"页面查看收益统计。
|
||||
</p>
|
||||
<p class="indent-8 mb-2"><strong>5.2 如何邀请下级代理</strong></p>
|
||||
<p class="indent-8 mb-2">
|
||||
(1)登录代理账户后,进入"邀请码管理"页面,系统会为您生成专属邀请码。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
(2)您可以将邀请码分享给想要成为代理的用户。用户通过您的邀请码注册成为代理后,将自动成为您的下级代理。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
(3)当您的下级代理推广产品产生订单时,您将获得一定比例的下级推广返佣。返佣比例根据您的代理等级而定,等级越高,返佣比例越高。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
(4)您可以在"我的团队"页面查看所有下级代理的信息,包括直接下级和间接下级,以及团队统计数据。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
(5)当您的下级代理付费升级为更高等级时(如普通代理升级为黄金代理),您将获得下级升级返佣。只有黄金代理和钻石代理才能获得下级升级返佣。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
(6)钻石代理拥有特殊权限,可以将普通下级代理直接升级为黄金代理,无需下级代理付费。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
(7)您可以在"下级推广收益"中查看所有来自下级代理的返佣明细,包括推广返佣和升级返佣。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
(8)邀请下级代理是扩大团队规模、增加收益的重要方式,建议您积极发展下级代理,建立稳定的推广团队。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
2、{{ companyName }}充分尊重代理代理推广权,但有下列状况之一时,{{ companyName }}将保留或者取消该代理的权利:
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
a代理经营管理不善,造成工作无法正常开展的;
|
||||
</p>
|
||||
<p class="indent-8 mb-2">b国家政策变化等不可抗力发生时;</p>
|
||||
<p class="indent-8 mb-2">c遇有客户投诉,经确认属代理操作不当的;</p>
|
||||
<p class="indent-8 mb-2">
|
||||
d其他严重损害{{ companyName }}形象与产品形象的行为发生时;
|
||||
</p>
|
||||
<p class="indent-8 mb-2">e违反国家法律法规时;</p>
|
||||
<p class="indent-8 mb-2">
|
||||
3、当代理名下发生投诉时,代理需配合相关的协调。否则{{ companyName }}有权无条件取消其代理资格,终止其代理协议。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
4、代理应合规宣传{{ companyName }}产品形象。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
5、市场运作过程中,各代理在接到市场投诉时,应及时做好记录,并报{{ companyName }}相关部门妥善处理。
|
||||
</p>
|
||||
|
||||
<p class="indent-8 mb-2"><strong>六、违规处罚</strong></p>
|
||||
<p class="indent-8 mb-2">
|
||||
1、各代理在推广{{ companyName }}过程中,有损害{{ companyName }}产品信誉行为时,视情节轻重,{{ companyName }}将对其提出书面警告直至取消其代理资格;
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
2、未按{{ companyName }}有关规定和本制度开展工作的,{{ companyName }}将提出书面警告并限期整改;
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
3、不遵守{{ companyName }}的相关规章制度,造成与其他推广代理纠纷时,{{ companyName }}将视其情节轻重,处以20000元以上50000元以下的罚款,并取消其代理资格。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
4、违反保密义务,导致{{ companyName }}重大损失的,{{ companyName }}将对其处以5000-20000元罚款,情节严重者将直接取消其代理资格。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
5、代理如严重违反{{ companyName }}相关规章制度,{{ companyName }}可随时解除双方约定的部分或全部协议。
|
||||
</p>
|
||||
|
||||
<p class="indent-8 mb-2"><strong>七、推广收益及提现</strong></p>
|
||||
<p class="indent-8 mb-2">
|
||||
1、用户通过平台推广产品/服务所获得的佣金收益,须在平台规定的条件下申请提现;
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
2、平台有权根据国家税收法律法规,对用户佣金收入依法代扣代缴个人所得税;
|
||||
</p>
|
||||
<p class="indent-8 mb-4">
|
||||
3、若用户未通过实名认证或未完成相关信息认证,平台有权暂缓或拒绝佣金发放。
|
||||
</p>
|
||||
|
||||
<p class="indent-8 mb-2"><strong>八、税务处理说明</strong></p>
|
||||
<p class="indent-8 mb-2">
|
||||
1、用户确认并同意,其通过本平台获得的推广佣金、奖励、分润等收入,依法属于"个人所得税"征收范畴;
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
2、用户同意授权平台代为完成相关税务申报及代扣代缴义务,平台有权依据国家相关税收标准,在佣金发放前先行扣除应缴税款;
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
3、用户理解,因未能完成实名认证、税务资料提交、或不配合税务处理流程所导致的提现延迟、失败或法律后果,平台不承担任何责任;
|
||||
</p>
|
||||
<p class="indent-8 mb-4">
|
||||
4、在法律允许的范围内,平台有权委托第三方(如税务服务平台、灵活用工平台等)代为完成税务申报、发放结算等合规流程。
|
||||
</p>
|
||||
|
||||
<p class="indent-8 mb-2"><strong>九、信息收集与使用说明</strong></p>
|
||||
<p class="indent-8 mb-2">
|
||||
1、用户在申请提现、实名认证或佣金结算过程中,需向平台提供包括但不限于姓名、身份证号、银行卡号、手机号、税务身份信息等个人资料;
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
2、用户同意平台为履行合同义务、税务申报、身份核验、财务结算等必要目的,收集、使用、存储并在必要范围内共享该等信息;
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
3、平台承诺遵守国家相关法律法规,在取得用户同意的前提下,对用户个人信息进行合理保护和使用;
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
4、在进行税务代扣代缴、结算服务时,平台有权将必要信息提供给依法合作的第三方税务服务商、结算服务商,前提是该第三方承担同等信息保护义务;
|
||||
</p>
|
||||
<p class="indent-8 mb-4">
|
||||
5、用户有权查询、更正其个人信息,也可以根据平台流程申请注销账户或停止使用相关服务,平台将根据法律要求妥善处理相关信息。
|
||||
</p>
|
||||
|
||||
<p class="indent-8 mb-4"><strong>十、投诉类处罚</strong></p>
|
||||
<p class="indent-8 mb-2">1、代理账户累计投诉率处罚措施</p>
|
||||
<p class="indent-8 mb-2">a.月查询报告数量≥200单</p>
|
||||
|
||||
<div class="mb-4 overflow-x-auto">
|
||||
<table class="w-full border-collapse border border-gray-300">
|
||||
<thead>
|
||||
<tr class="bg-gray-100">
|
||||
<th class="border border-gray-300 p-2 w-40">
|
||||
处理类型
|
||||
</th>
|
||||
<th class="border border-gray-300 p-2">提高底价</th>
|
||||
<th class="border border-gray-300 p-2">
|
||||
限制修改查询售价
|
||||
</th>
|
||||
<th class="border border-gray-300 p-2">罚款</th>
|
||||
<th class="border border-gray-300 p-2">禁止提现</th>
|
||||
<th class="border border-gray-300 p-2">封号</th>
|
||||
<th class="border border-gray-300 p-2">黑名单</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">
|
||||
投诉率≥5%<8%
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
+1
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">
|
||||
投诉率≥8%<10%
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
+3
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">
|
||||
投诉率≥10%
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
+5
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="indent-8 mb-2">b.月查询报告数量<100单</p>
|
||||
<div class="mb-4 overflow-x-auto">
|
||||
<table class="w-full border-collapse border border-gray-300">
|
||||
<thead>
|
||||
<tr class="bg-gray-100">
|
||||
<th class="border border-gray-300 p-2 w-40">
|
||||
处理类型
|
||||
</th>
|
||||
<th class="border border-gray-300 p-2">提高底价</th>
|
||||
<th class="border border-gray-300 p-2">
|
||||
限制修改查询售价
|
||||
</th>
|
||||
<th class="border border-gray-300 p-2">罚款</th>
|
||||
<th class="border border-gray-300 p-2">禁止提现</th>
|
||||
<th class="border border-gray-300 p-2">封号</th>
|
||||
<th class="border border-gray-300 p-2">黑名单</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">
|
||||
投诉率≥6%<8%
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
+1
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">
|
||||
投诉率≥8%<15%
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
+5
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
49
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">
|
||||
投诉率≥15%
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="indent-8 mb-2">c.月查询报告数量<50单</p>
|
||||
<div class="mb-4 overflow-x-auto">
|
||||
<table class="w-full border-collapse border border-gray-300">
|
||||
<thead>
|
||||
<tr class="bg-gray-100">
|
||||
<th class="border border-gray-300 p-2 w-40">
|
||||
处理类型
|
||||
</th>
|
||||
<th class="border border-gray-300 p-2">提高底价</th>
|
||||
<th class="border border-gray-300 p-2">
|
||||
限制修改查询售价
|
||||
</th>
|
||||
<th class="border border-gray-300 p-2">罚款</th>
|
||||
<th class="border border-gray-300 p-2">禁止提现</th>
|
||||
<th class="border border-gray-300 p-2">封号</th>
|
||||
<th class="border border-gray-300 p-2">黑名单</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">
|
||||
投诉率≥15%<20%
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
+3
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">
|
||||
投诉率≥20%<50%
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
+5
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
39
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">
|
||||
投诉率≥50%
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="indent-8 mb-2">
|
||||
备注:针对客户自身原因投诉,对于产生投诉的代理账户只有三次加底价机会(底价只加不减),到第四次时直接封号。
|
||||
</p>
|
||||
<p class="indent-8 mb-4">执行时间:每月1号出数据统计,2号执行。</p>
|
||||
|
||||
<p class="indent-8 mb-2">2、代理单笔投诉处罚措施</p>
|
||||
<div class="mb-4 overflow-x-auto">
|
||||
<table class="w-full border-collapse border border-gray-300">
|
||||
<thead>
|
||||
<tr class="bg-gray-100">
|
||||
<th class="border border-gray-300 p-2 w-40">
|
||||
处理类型
|
||||
</th>
|
||||
<th class="border border-gray-300 p-2">提高底价</th>
|
||||
<th class="border border-gray-300 p-2">
|
||||
冻结推广收益
|
||||
</th>
|
||||
<th class="border border-gray-300 p-2">
|
||||
单笔风险资金冻结
|
||||
</th>
|
||||
<th class="border border-gray-300 p-2">罚款</th>
|
||||
<th class="border border-gray-300 p-2">禁止提现</th>
|
||||
<th class="border border-gray-300 p-2">封号</th>
|
||||
<th class="border border-gray-300 p-2">黑名单</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">
|
||||
网络公开恶意投诉(非欺诈类可解)
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">
|
||||
支付宝投诉
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">
|
||||
政务部门投诉
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">
|
||||
(非产品质量类)客户一般退款
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
1+退款次数(最高10元)
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">
|
||||
受代理教唆客户恶意退款
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
5+退款次数
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
√
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
<td class="border border-gray-300 p-2 text-center">
|
||||
×
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="indent-8 mb-2">3、扬言给客户做"数据修复"类投诉措施</p>
|
||||
<p class="indent-8 mb-2">
|
||||
第一步:每接到此类投诉一次,所属代理底价+10元/次,并警告。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
第二步:警告无效后,依旧发生则采取直接封号并加入黑名单。
|
||||
</p>
|
||||
<p class="indent-8 mb-2 text-red-600">备注:</p>
|
||||
<p class="indent-8 mb-2 text-red-600">
|
||||
1)√为执行项 ×为不执行项
|
||||
</p>
|
||||
<p class="indent-8 mb-2 text-red-600">
|
||||
2)一般投诉罚款投诉金额1倍(除退还投诉金额外,另行按投诉金额1倍的标准进行罚款)。
|
||||
</p>
|
||||
<p class="indent-8 mb-2 text-red-600">
|
||||
如:投诉金额为45元,则退还用户投诉金额:45元,代理罚款:45*1=45元
|
||||
</p>
|
||||
<p class="indent-8 mb-2 text-red-600">
|
||||
3)公开投诉,代理罚款2倍/封号(依据具体严重情况而定,除退还投诉金额外,另行按投诉金额2倍的标准进行罚款)。
|
||||
</p>
|
||||
<p class="indent-8 mb-2 text-red-600">
|
||||
如:投诉金额为45元,退还用户金额:45元,则代理罚款:45*2=90元
|
||||
</p>
|
||||
<p class="indent-8 mb-2 text-red-600">
|
||||
4)如代理发生单笔投诉涉及到本制度第六条所规定的事由,将按第六条、第七条处罚规则合并执行。
|
||||
</p>
|
||||
|
||||
<p class="indent-8 mb-2">4、冻结、封禁类代理处罚措施</p>
|
||||
<p class="indent-8 mb-2">
|
||||
a.自冻结、封禁之日起,3个月之内无任何新的投诉、舆情升级、违法犯罪情形,代理可向平台提交"提现申请函",平台根据处罚规则先行处罚后,对可提现余额再追加10%的罚款。代理可在7个工作日内完成相关的提现操作。但该代理账户的推广功能,则进入到审核期。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
b.自冻结、封禁之日起,3个月之内有新的投诉、舆情升级、违法犯罪情形,则平台对该账户的审查期将会延长,审查期间无法提现。
|
||||
</p>
|
||||
|
||||
<p class="indent-8 mb-4"><strong>十一、封号规则</strong></p>
|
||||
<p class="mb-2">
|
||||
1、同一个设备频繁更换账号登录,或同一个账号频繁在多个设备登陆,系统自动自动检测手机登录IP和设备信息,有封号风险(一机一号,不要频繁切换设备或者账户);
|
||||
</p>
|
||||
<p class="mb-2">2、欺诈用户(诱导用户);</p>
|
||||
<p class="mb-2">3、先付款后退款等承诺;</p>
|
||||
<p class="mb-2">4、保证高额下款;</p>
|
||||
<p class="mb-2">5、使用数据优化(征信优化)等骗取用户钱财。</p>
|
||||
<p class="mb-2">6、发布涉嫌性骚扰的文字、图片;</p>
|
||||
<p class="mb-2">
|
||||
7、使用含色情、淫秽意味或其他令人不适的头像或资料;
|
||||
</p>
|
||||
<p class="mb-2">8、触犯新广告法;</p>
|
||||
<p class="mb-2">9、在朋友圈中使用辱骂、恐吓、威胁等言论;</p>
|
||||
<p class="mb-2">10、发布各类垃圾广告、恶意信息、诱骗信息;</p>
|
||||
<p class="mb-2">11、盗用他人头像或资料,伪装他人身份;</p>
|
||||
<p class="mb-2">12、多人举报的账号并涉及恶意诈骗;</p>
|
||||
<p class="mb-2">13、频繁被举报,每月超过20次以上的代理账户。</p>
|
||||
<p class="mb-2">
|
||||
14、恶意投诉,比如没有异议非说有异议且无法提供有效证明材料,各种奇葩投诉。
|
||||
</p>
|
||||
<p class="mb-2">15、租用账号,发布不良言论,诈骗信息。</p>
|
||||
<p class="mb-4">
|
||||
16、发布不当政治言论或者任何违反国家法规政策的言论。
|
||||
</p>
|
||||
<p class="mb-4">更多详细内容请认真阅读{{ appName }}《代理协议》。</p>
|
||||
|
||||
<h3 class="font-bold mb-2">退款的规则及途径</h3>
|
||||
<h4 class="font-bold mb-2">【退款规则】</h4>
|
||||
<p class="mb-2">
|
||||
1、自订单支付完成后3天内为有效期,在3天内可申请退款。
|
||||
</p>
|
||||
<p class="mb-2">2、超过报告有效期3天,则无法办理退款。</p>
|
||||
<p class="mb-2">
|
||||
3、符合相关退款条件的用户,退款时仅退还实付金额。
|
||||
</p>
|
||||
<p class="mb-2">
|
||||
4、用户购买报告成功后,因不可抗力等法定原因或平台原因,导致平台无法提供服务,用户可联系客服,发起退款。
|
||||
</p>
|
||||
<p class="mb-2">5、若因用户的失误重复付款,则支持退款重复金额。</p>
|
||||
<p class="mb-2">6、服务已发生且不符合退款情形的费用不予退款。</p>
|
||||
<p class="mb-2">
|
||||
7、如代理在市场推广中存在欺诈等相关行为,用户可提供有效的凭证办理退款事宜。
|
||||
</p>
|
||||
<p class="mb-4">
|
||||
8、产品呈现的情况与用户本人实际情况不符,用户可提供有效的凭证发起退款申请。
|
||||
</p>
|
||||
|
||||
<h4 class="font-bold mb-2">【用户发起投诉】</h4>
|
||||
<p class="mb-2">
|
||||
1.当代理拒绝退款,用户与代理双方线下也未达成一致时,用户可联系客服发起投诉。
|
||||
</p>
|
||||
<p class="mb-2">
|
||||
2.用户提交投诉后,请用户和代理按照相关提示举证,完成举证后客服将介入处理纠纷。
|
||||
</p>
|
||||
<p class="mb-2">
|
||||
3.平台客服介入前,若用户与代理双方已对退款协商一致,商家可直接联系平台客服说明情况,或者用户联系平台提供撤销投诉函并说明情况即可,同时投诉会关闭。
|
||||
</p>
|
||||
<p class="mb-2">
|
||||
4.平台客服介入后,若需要用户与代理提供举证信息,可发送相关材料至邮箱,方便客服及时处理。
|
||||
</p>
|
||||
<p class="mb-4">
|
||||
5.平台客服会根据举证信息,联系用户与代理双方处理投诉。
|
||||
</p>
|
||||
|
||||
<h5 class="indent-8 font-bold mb-2">十二、退款服务以及流程</h5>
|
||||
<p class="indent-8 mb-4">
|
||||
自用户购买查询报告成功之日起,无论由于何种原因,用户均可向平台申请退款(不适用退款服务的情况除外)。
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col items-center mb-4">
|
||||
<p class="mb-2">用户发起退款申请</p>
|
||||
<p class="mb-2">↓</p>
|
||||
<p class="mb-2">提供有效证明凭证</p>
|
||||
<p class="mb-2">↓</p>
|
||||
<p class="mb-2">平台审核是否符合退款标准</p>
|
||||
<p class="mb-2">↓</p>
|
||||
<p class="mb-2">确认无误后退款完成</p>
|
||||
<p class="mb-2">(预计1-7个工作日内完成)</p>
|
||||
</div>
|
||||
|
||||
<h4 class="font-bold mb-2">【退款流程说明】</h4>
|
||||
<p class="indent-8 mb-2">
|
||||
原路返回----直接把金额退回到用户付款的来源方,包括但不限于支付宝帐户,暂不收取手续费。
|
||||
</p>
|
||||
<p class="indent-8 mb-4">
|
||||
具体操作流程:在发生退款时,用户可在查询页面点击"联系客服"发起"申请退款"申请,并提供有效的证明凭证,客服提交至平台系统,系统通过审核后,相关的退款金额将在1-7个工作日内原路返回对应的付款账户中。通过网银或支付宝等第三方支付平台进行支付的费用将直接退到原账户。
|
||||
</p>
|
||||
|
||||
<h4 class="font-bold mb-2">【不适用退款服务的情况】</h4>
|
||||
<p class="indent-8 mb-2">1、已超过退款期限;</p>
|
||||
<p class="indent-8 mb-2">2、恶意投诉;</p>
|
||||
<p class="indent-8 mb-4">3、违反《用户使用协议》相关规则。</p>
|
||||
|
||||
<h4 class="font-bold mb-2">【补充说明】</h4>
|
||||
<p class="indent-8 mb-4">
|
||||
如您需要退款的产品类型不在以上3天,或者超出了30
|
||||
天限制,则无法办理退款。如您有产品使用方面的疑问,您可以通过联系客服进行反馈。
|
||||
</p>
|
||||
|
||||
<p class="indent-8 mb-2"><strong>十三、附则</strong></p>
|
||||
<p class="indent-8 mb-2">
|
||||
1、本制度作为《代理协议》之附件与《代理协议》具有同等法律效力。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
2、{{ companyName }}将本着"诚信为本、长期服务"的宗旨和"公平合理"的原则对代理进行合理布局和调整,以实现互利互惠、共同快速发展的目的。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
3、因其他原因需终止代理关系,需向{{ companyName }}提出书面申请。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
4、代理之间发生业务竞争和冲突,{{ companyName }}将依据公平、公正、公开的原则按相关制度予以调解、处理。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
5、如{{ companyName }}与各代理之间出现协议上的纠纷,由{{ companyName }}所在地法院裁决。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
6、本制度的制定、修改与废止皆经由{{ companyName }}讨论决定,解释权归{{ companyName }}所有。
|
||||
</p>
|
||||
<p class="indent-8 mb-4">
|
||||
7、本制度于2022年1月1日起实施,公司将根据实施情况对本制度进行修正和调整。
|
||||
</p>
|
||||
|
||||
<p class="indent-8 mb-8">
|
||||
本制度一经网上点击/勾选,即代表理解并同意勾选遵守。
|
||||
</p>
|
||||
|
||||
<p class="text-right">本制度于 {{ effectiveDate }} 生效。</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const companyName = import.meta.env.VITE_COMPANY_NAME || '';
|
||||
const appName = import.meta.env.VITE_APP_NAME || '{{ appName }}';
|
||||
// 获取当前日期并格式化为中文格式
|
||||
const getFormattedDate = () => {
|
||||
const date = new Date();
|
||||
const year = date.getFullYear();
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||
const day = date.getDate().toString().padStart(2, "0");
|
||||
return `${year} 年 ${month} 月 ${day} 日`;
|
||||
};
|
||||
|
||||
const effectiveDate = ref(getFormattedDate());
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
133
src/views/AgentPromoteDetails.vue
Normal file
133
src/views/AgentPromoteDetails.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
<!-- 收益列表 -->
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
|
||||
<div v-for="(item, index) in data.list" :key="index" class="mx-4 my-2 bg-white rounded-lg p-4 shadow-sm">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span class="text-gray-500 text-sm">{{ item.create_time || '-' }}</span>
|
||||
<span class="text-green-500 font-bold">+{{ item.amount.toFixed(2) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium"
|
||||
:class="getReportTypeStyle(item.product_name)">
|
||||
<span class="w-2 h-2 rounded-full mr-1" :class="getDotColor(item.product_name)"></span>
|
||||
{{ item.product_name }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="item.order_no" class="text-xs text-gray-400">
|
||||
订单号:{{ item.order_no }}
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getCommissionList } from '@/api/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 data = ref({
|
||||
total: 0,
|
||||
list: []
|
||||
})
|
||||
const loading = ref(false)
|
||||
const finished = ref(false)
|
||||
|
||||
// 获取颜色样式
|
||||
const getReportTypeStyle = (name) => {
|
||||
const color = typeColors[name] || typeColors.default
|
||||
return `${color.bg} ${color.text}`
|
||||
}
|
||||
|
||||
// 获取小圆点颜色
|
||||
const getDotColor = (name) => {
|
||||
return (typeColors[name] || typeColors.default).dot
|
||||
}
|
||||
|
||||
// 加载更多数据
|
||||
const onLoad = async () => {
|
||||
if (!finished.value) {
|
||||
page.value++
|
||||
await getData()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
const getData = async () => {
|
||||
if (loading.value || finished.value) return
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const { data: res, error } = await getCommissionList({
|
||||
page: page.value,
|
||||
page_size: pageSize.value
|
||||
})
|
||||
|
||||
if (res.value?.code === 200 && !error.value) {
|
||||
// 首次加载
|
||||
if (page.value === 1) {
|
||||
data.value = res.value.data
|
||||
} else {
|
||||
// 分页加载
|
||||
data.value.list.push(...res.value.data.list)
|
||||
}
|
||||
|
||||
// 判断是否加载完成
|
||||
if (data.value.list.length >= res.value.data.total ||
|
||||
res.value.data.list.length < pageSize.value) {
|
||||
finished.value = true
|
||||
}
|
||||
} else {
|
||||
// 接口返回错误或请求失败,停止翻页
|
||||
finished.value = true
|
||||
console.error('获取佣金列表失败:', res.value?.msg || error.value || '未知错误')
|
||||
}
|
||||
} catch (err) {
|
||||
// 捕获异常,停止翻页
|
||||
finished.value = true
|
||||
console.error('获取佣金列表失败:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化加载
|
||||
onMounted(() => {
|
||||
getData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 列表项入场动画 */
|
||||
.list-enter-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.list-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
/* 适配vant组件 */
|
||||
:deep(.van-list__finished-text) {
|
||||
@apply py-4 text-gray-400 text-sm;
|
||||
}
|
||||
|
||||
:deep(.van-list__loading) {
|
||||
@apply py-4;
|
||||
}
|
||||
</style>
|
||||
113
src/views/AgentPromotionHistory.vue
Normal file
113
src/views/AgentPromotionHistory.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
const router = useRouter()
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
const reportList = ref([])
|
||||
const loading = ref(false)
|
||||
const finished = ref(false)
|
||||
|
||||
async function fetchData() {
|
||||
if (loading.value || finished.value) return
|
||||
loading.value = true
|
||||
try {
|
||||
const { data, error } = await useApiFetch(`/agent/promotion/query/list?page=${page.value}&page_size=${pageSize.value}`).get().json()
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
total.value = data.value.data.total
|
||||
if (data.value.data.list && data.value.data.list.length > 0) {
|
||||
reportList.value.push(...data.value.data.list)
|
||||
page.value += 1
|
||||
}
|
||||
if (reportList.value.length >= total.value) {
|
||||
finished.value = true
|
||||
}
|
||||
} else {
|
||||
finished.value = true
|
||||
console.error('获取推广查询列表失败:', data.value.msg || '未知错误')
|
||||
}
|
||||
} else {
|
||||
finished.value = true
|
||||
console.error('获取推广查询列表失败:', error.value || '请求失败')
|
||||
}
|
||||
} catch (err) {
|
||||
finished.value = true
|
||||
console.error('获取推广查询列表失败:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
})
|
||||
|
||||
const onLoad = () => {
|
||||
if (!finished.value) {
|
||||
fetchData()
|
||||
}
|
||||
}
|
||||
|
||||
function toDetail(item) {
|
||||
if (item.query_state != 'success') return
|
||||
router.push({ path: '/report', query: { orderId: item.order_id } })
|
||||
}
|
||||
|
||||
function stateText(state) {
|
||||
switch (state) {
|
||||
case 'pending':
|
||||
return '查询中'
|
||||
case 'success':
|
||||
return '查询成功'
|
||||
case 'failed':
|
||||
return '查询失败'
|
||||
case 'refunded':
|
||||
return '已退款'
|
||||
default:
|
||||
return '未知状态'
|
||||
}
|
||||
}
|
||||
|
||||
function statusClass(state) {
|
||||
switch (state) {
|
||||
case 'pending':
|
||||
return 'status-pending'
|
||||
case 'success':
|
||||
return 'status-success'
|
||||
case 'failed':
|
||||
return 'status-failed'
|
||||
case 'refunded':
|
||||
return 'status-refunded'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
|
||||
<div v-for="item in reportList" :key="item.id" @click="toDetail(item)"
|
||||
class="bg-white rounded-lg shadow-sm p-4 mb-4 relative cursor-pointer">
|
||||
<div class="flex flex-col">
|
||||
<div class="text-xl text-black mb-1">{{ item.product_name }}</div>
|
||||
<div class="text-sm text-[#999999]">{{ item.create_time }}</div>
|
||||
</div>
|
||||
<div class="absolute top-0 right-0 rounded-bl-lg rounded-tr-lg px-2 py-[1px] text-white text-sm font-medium"
|
||||
:class="[statusClass(item.query_state)]">
|
||||
{{ stateText(item.query_state) }}
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
</div>
|
||||
|
||||
<van-empty v-if="!loading && reportList.length === 0" description="暂无推广查询记录" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.status-pending { background-color: #1976d2; color: white; }
|
||||
.status-success { background-color: #1FBE5D; color: white; }
|
||||
.status-failed { background-color: #EB3C3C; color: white; }
|
||||
.status-refunded { background-color: #999999; color: white; }
|
||||
</style>
|
||||
269
src/views/AgentRewardsDetails.vue
Normal file
269
src/views/AgentRewardsDetails.vue
Normal file
@@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
<!-- Tab 标签页 -->
|
||||
<van-tabs v-model:active="activeTab" @change="onTabChange" class="bg-white sticky top-0 z-10">
|
||||
<van-tab title="推广返佣" name="promote"></van-tab>
|
||||
<van-tab title="升级返佣" name="upgrade"></van-tab>
|
||||
</van-tabs>
|
||||
|
||||
<!-- 收益列表 -->
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
|
||||
<div v-for="(item, index) in data.list" :key="index" class="mx-4 my-2 bg-white rounded-lg p-4 shadow-sm">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span class="text-gray-500 text-sm">{{ item.create_time || '-' }}</span>
|
||||
<span class="text-green-500 font-bold">+{{ item.amount.toFixed(2) }}</span>
|
||||
</div>
|
||||
<!-- 推广返佣显示返佣类型 tag -->
|
||||
<!-- <div class="flex items-center mb-2" v-if="activeTab === 'promote' && item.rebate_type">
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium"
|
||||
:class="getReportTypeStyle(item.rebate_type)">
|
||||
<span class="w-2 h-2 rounded-full mr-1" :class="getDotColor(item.rebate_type)"></span>
|
||||
{{ typeToChinese(item.rebate_type) }}
|
||||
</span>
|
||||
</div> -->
|
||||
<!-- 升级返佣显示升级信息 tag -->
|
||||
<div class="flex items-center mb-2" v-if="activeTab === 'upgrade' && item.to_level">
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium"
|
||||
:class="getUpgradeTagStyle(item.to_level)">
|
||||
<span class="w-2 h-2 rounded-full mr-1" :class="getUpgradeTagDot(item.to_level)"></span>
|
||||
升级为{{ getLevelName(item.to_level) }}代理
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-400 space-y-1">
|
||||
<div v-if="item.order_no">
|
||||
订单号:{{ item.order_no }}
|
||||
</div>
|
||||
<div v-if="item.source_agent_mobile" class="flex items-center gap-2">
|
||||
<span>来源代理:{{ item.source_agent_mobile }}</span>
|
||||
<!-- 只在升级返佣 tab 显示来源代理等级 -->
|
||||
<span v-if="activeTab === 'upgrade' && item.source_agent_level"
|
||||
class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium"
|
||||
:class="getLevelTagStyle(item.source_agent_level)">
|
||||
{{ getLevelName(item.source_agent_level) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { getRebateList, getUpgradeRebateList } from '@/api/agent'
|
||||
|
||||
// 返佣类型映射配置(推广返佣)
|
||||
const typeConfig = {
|
||||
1: {
|
||||
chinese: '直接上级返佣',
|
||||
color: { bg: 'bg-blue-100', text: 'text-blue-800', dot: 'bg-blue-500' }
|
||||
},
|
||||
2: {
|
||||
chinese: '钻石上级返佣',
|
||||
color: { bg: 'bg-purple-100', text: 'text-purple-800', dot: 'bg-purple-500' }
|
||||
},
|
||||
3: {
|
||||
chinese: '黄金上级返佣',
|
||||
color: { bg: 'bg-yellow-100', text: 'text-yellow-800', dot: 'bg-yellow-500' }
|
||||
},
|
||||
default: {
|
||||
chinese: '其他返佣',
|
||||
color: { bg: 'bg-gray-100', text: 'text-gray-800', dot: 'bg-gray-500' }
|
||||
}
|
||||
}
|
||||
|
||||
// 等级 tag 样式配置
|
||||
const levelTagConfig = {
|
||||
1: {
|
||||
chinese: '普通',
|
||||
color: { bg: 'bg-gray-100', text: 'text-gray-700' }
|
||||
},
|
||||
2: {
|
||||
chinese: '黄金',
|
||||
color: { bg: 'bg-yellow-100', text: 'text-yellow-700' }
|
||||
},
|
||||
3: {
|
||||
chinese: '钻石',
|
||||
color: { bg: 'bg-purple-100', text: 'text-purple-700' }
|
||||
}
|
||||
}
|
||||
|
||||
// 升级 tag 样式配置
|
||||
const upgradeTagConfig = {
|
||||
2: {
|
||||
color: { bg: 'bg-yellow-100', text: 'text-yellow-700', dot: 'bg-yellow-500' }
|
||||
},
|
||||
3: {
|
||||
color: { bg: 'bg-purple-100', text: 'text-purple-700', dot: 'bg-purple-500' }
|
||||
}
|
||||
}
|
||||
|
||||
const activeTab = ref('promote') // 'promote' 推广返佣, 'upgrade' 升级返佣
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const data = ref({
|
||||
total: 0,
|
||||
list: []
|
||||
})
|
||||
const loading = ref(false)
|
||||
const finished = ref(false)
|
||||
|
||||
// 类型转中文
|
||||
const typeToChinese = (rebateType) => {
|
||||
return typeConfig[rebateType]?.chinese || typeConfig.default.chinese
|
||||
}
|
||||
|
||||
// 获取颜色样式
|
||||
const getReportTypeStyle = (rebateType) => {
|
||||
const config = typeConfig[rebateType] || typeConfig.default
|
||||
return `${config.color.bg} ${config.color.text}`
|
||||
}
|
||||
|
||||
// 获取小圆点颜色
|
||||
const getDotColor = (rebateType) => {
|
||||
return typeConfig[rebateType]?.color.dot || typeConfig.default.color.dot
|
||||
}
|
||||
|
||||
// 获取等级名称
|
||||
const getLevelName = (level) => {
|
||||
return levelTagConfig[level]?.chinese || '未知'
|
||||
}
|
||||
|
||||
// 获取等级 tag 样式
|
||||
const getLevelTagStyle = (level) => {
|
||||
const config = levelTagConfig[level] || levelTagConfig[1]
|
||||
return `${config.color.bg} ${config.color.text}`
|
||||
}
|
||||
|
||||
// 获取升级 tag 样式
|
||||
const getUpgradeTagStyle = (toLevel) => {
|
||||
const config = upgradeTagConfig[toLevel] || upgradeTagConfig[2]
|
||||
return `${config.color.bg} ${config.color.text}`
|
||||
}
|
||||
|
||||
// 获取升级 tag 小圆点颜色
|
||||
const getUpgradeTagDot = (toLevel) => {
|
||||
const config = upgradeTagConfig[toLevel] || upgradeTagConfig[2]
|
||||
return config.color.dot
|
||||
}
|
||||
|
||||
// Tab 切换
|
||||
const onTabChange = (name) => {
|
||||
// 重置分页和数据
|
||||
page.value = 1
|
||||
finished.value = false
|
||||
data.value = {
|
||||
total: 0,
|
||||
list: []
|
||||
}
|
||||
// 重新加载数据
|
||||
getData()
|
||||
}
|
||||
|
||||
// 加载更多数据
|
||||
const onLoad = async () => {
|
||||
if (!finished.value) {
|
||||
page.value++
|
||||
await getData()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
const getData = async () => {
|
||||
if (loading.value || finished.value) return
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
|
||||
const params = {
|
||||
page: page.value,
|
||||
page_size: pageSize.value
|
||||
}
|
||||
|
||||
let res, error
|
||||
|
||||
// 根据 tab 类型调用不同的接口
|
||||
if (activeTab.value === 'upgrade') {
|
||||
// 升级返佣:调用升级返佣接口
|
||||
const result = await getUpgradeRebateList(params)
|
||||
res = result.data
|
||||
error = result.error
|
||||
} else {
|
||||
// 推广返佣:调用推广返佣接口
|
||||
const result = await getRebateList(params)
|
||||
res = result.data
|
||||
error = result.error
|
||||
}
|
||||
|
||||
if (res.value?.code === 200 && !error.value) {
|
||||
let list = res.value.data.list || []
|
||||
let total = res.value.data.total
|
||||
|
||||
// 升级返佣需要转换数据格式
|
||||
if (activeTab.value === 'upgrade') {
|
||||
list = list.map(item => ({
|
||||
id: item.id,
|
||||
source_agent_id: item.source_agent_id,
|
||||
source_agent_mobile: item.source_agent_mobile,
|
||||
source_agent_level: 0, // 升级返佣不需要显示来源代理等级
|
||||
order_no: item.order_no,
|
||||
rebate_type: 4, // 升级返佣类型(用于内部标识,不显示)
|
||||
amount: item.amount,
|
||||
create_time: item.create_time,
|
||||
from_level: item.from_level,
|
||||
to_level: item.to_level
|
||||
}))
|
||||
}
|
||||
|
||||
if (page.value === 1) {
|
||||
data.value = {
|
||||
total: total,
|
||||
list: list
|
||||
}
|
||||
} else {
|
||||
data.value.list.push(...list)
|
||||
}
|
||||
|
||||
// 判断是否加载完成
|
||||
if (data.value.list.length >= total || list.length < pageSize.value) {
|
||||
finished.value = true
|
||||
}
|
||||
} else {
|
||||
// 接口返回错误或请求失败,停止翻页
|
||||
finished.value = true
|
||||
console.error('获取返佣列表失败:', res.value?.msg || error.value || '未知错误')
|
||||
}
|
||||
} catch (err) {
|
||||
// 捕获异常,停止翻页
|
||||
finished.value = true
|
||||
console.error('获取返佣列表失败:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化加载
|
||||
onMounted(() => {
|
||||
getData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 保持原有样式不变 */
|
||||
.list-enter-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.list-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
:deep(.van-list__finished-text) {
|
||||
@apply py-4 text-gray-400 text-sm;
|
||||
}
|
||||
|
||||
:deep(.van-list__loading) {
|
||||
@apply py-4;
|
||||
}
|
||||
</style>
|
||||
278
src/views/AgentServiceAgreement.vue
Normal file
278
src/views/AgentServiceAgreement.vue
Normal file
@@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="container mx-auto p-4 text-gray-800">
|
||||
<p class="text-center font-bold text-lg">信息技术服务合同
|
||||
</p>
|
||||
<p class="text-left"><span></span></p>
|
||||
<p class="text-left"><span class="text-black">甲方:</span></p>
|
||||
<p class="text-left"><span class="text-black">乙方:</span><span class="text-black">{{ companyName
|
||||
}}</span>
|
||||
</p>
|
||||
<p class="text-left"> </p>
|
||||
<p class="text-left"><span class="text-black">鉴于:</span></p>
|
||||
<p class="text-left"><span class="text-black">1.
|
||||
甲方为其合法合规经营之业务,依法需对甲方最终用户或有关交易和交往利害关系主体有关信息进行识别;</span></p>
|
||||
<p class="text-left"><span class="text-black">2.
|
||||
乙方具备相关信息技术之专业能力,能够为甲方提供相应服务;</span></p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">
|
||||
现双方根据《中华人民共和国</span><span class="text-black">民法典</span><span
|
||||
class="text-black">》等相关法律法规,本着诚实、信用、公平,促进社会诚信发展为原则,经友好协商就</span><span
|
||||
class="text-black">{{ companyName }}</span><span
|
||||
class="text-black">信息技术服务事宜达成一致,签订本合同。</span></p>
|
||||
<p class="text-left"><span></span></p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">一、
|
||||
释义</span></p>
|
||||
<p class="text-left"><span class="text-black">
|
||||
除上下文另有约定外,下列用语具有如下含义:</span></p>
|
||||
<p class="text-left"><span class="text-black">1.1
|
||||
</span><span class="text-black">{{ companyName }}</span><span
|
||||
class="text-black">信息技术服务/服务
|
||||
指乙方通过信息化、人工智能和信息科技等技术手段对</span><span class="text-black">大数据</span><span
|
||||
class="text-black">进行以公众号、小程序、APP、web页面(以下简称平台)或标准接口形式为客户提供的服务,协助客户完成信息的整理、管理等业务流程。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">1.2
|
||||
本合同 指本文本协议,及其形成本合同不可分割的附件。</span></p>
|
||||
<p class="text-left"><span class="text-black">1.3
|
||||
本合同标题仅供方便参考之用,不影响本合同的含义与解释。</span></p>
|
||||
<p class="text-left"><span class="text-black">1.4
|
||||
本合同甲方、乙方单独称为“一方”,合称为“双方”。</span></p>
|
||||
<p class="text-left"><span class="text-black">1.5
|
||||
法律法规 指中国法律、行政法规、部门规章、地方性法规、地方性政府部门规章以及由政府机构颁布的其他规范性文件。</span></p>
|
||||
<p class="text-left"><span class="text-black">1.6
|
||||
工作日 指法定节假日、休息日之外的日期。</span></p>
|
||||
<p class="text-left"><span class="text-black">1.7
|
||||
服务有效期 指乙方依据本合同提供服务的期限。</span></p>
|
||||
<p class="text-left"><span class="text-black">1.8
|
||||
授权
|
||||
指甲方最终用户以书面签名或法律效力等同于书面的电子签名等方式明确同意甲方向第三方服务商(指乙方并含其关联方,下同)提供本人/单位相关数据信息(包括但不限于个人信息、行为、交易、设备、不良信息,下同),及同意该第三方服务提供商查询、核实、搜集、处理、共享、使用(含合法业务应用)其本人/单位相关数据的行为,但是法律法规规定可以不经同意的除外。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">1.9
|
||||
API(Application Programming Interface,应用程序编程接口)
|
||||
指一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。</span></p>
|
||||
<p class="text-left"><span class="text-black">1.10
|
||||
Web应用 指一种可以通过Web访问的应用程序,由完成特定任务的各种Web组件(web
|
||||
components)构成的并通过Web将服务展示给客户。</span></p>
|
||||
<p class="text-left"><span class="text-black">1.11
|
||||
SFTP(Secure File Transfer Protocol,安全文件传送协议)
|
||||
指可以为传输文件提供一种安全的加密方法。</span></p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">二、
|
||||
合作内容与方式</span></p>
|
||||
<p class="text-left"><span class="text-black">2.1
|
||||
根据本合同约定的条件和条款,甲方使用乙方提供的【</span><span class="text-black">{{
|
||||
companyName }}</span><span class="text-black">相关</span><span
|
||||
class="text-black">信息技术】服务(简称“乙方服务”或“本服务”)。</span></p>
|
||||
<p class="text-left"><span class="text-black">2.2
|
||||
|
||||
乙方负责提供服务平台或接口,供甲方通过平台或接口使用本服务。乙方向甲方提供的平台或接口是使用本服务的重要凭证,除非另有约定或说明,甲方通过该平台或接口向乙方所发出的指令及相关行为均视为甲方的行为。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">2.3
|
||||
甲方通过乙方提供的平台或API/Web应用向乙方发起服务需求,乙方通过平台或API/Web应用将服务结果返回给甲方。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">三、
|
||||
费用及支付</span></p>
|
||||
<p class="text-left"><span class="text-black">3.1
|
||||
本合同采用先付费,后使用的计价模式。甲方依据自主选择的服务项目对应费用向乙方支付服务费用。</span></p>
|
||||
<p class="text-left"><span class="text-black">3.2
|
||||
服务有效期及支付方式</span></p>
|
||||
<p class="text-left"><span class="text-black">3.2.1
|
||||
服务有效期以1年为准。
|
||||
除非另有约定,充值金额使用完毕/流量限额全部用完或服务有效期届满之日(以先到者为准)且未在服务有效期内续费或者续流量的,则本合同即终止;在服务有效期内继续充值的,则续充的服务有效期为该次充值之后12个月;如当期服务有效期届满而预付金额/流量限额未用完,乙方无需退还服务费并不予以延期使用。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">3.3
|
||||
支付方式:</span></p>
|
||||
<p class="text-left"><span class="text-black">3.3.1
|
||||
甲方向乙方一次性</span><span class="text-black">或分多次</span><span
|
||||
class="text-black">支付服务费。</span></p>
|
||||
<p class="text-left"><span class="text-black">3.3.2
|
||||
乙方根据甲方使用功能和次数实时计费,并从甲方已支付费用中扣除。</span></p>
|
||||
<p class="text-left"><span class="text-black">3.3.3
|
||||
若甲方拟继续使用服务的,则须在预付费/流量使用完毕后,继续充值。具体充值的服务项目和价格见平台展示。</span></p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">四、
|
||||
权利与义务</span></p>
|
||||
<p class="text-left"><span class="text-black">4.1
|
||||
甲方权利与义务</span></p>
|
||||
<p class="text-left"><span class="text-black">4.1.1
|
||||
甲方有权根据本合同约定向乙方提起服务需求</span></p>
|
||||
<p class="text-left"><span class="text-black">4.1.2
|
||||
甲方</span><span
|
||||
class="text-black">使用乙方服务,不得违反《中华人民共和国个人信息保护法》及《征信业务管理办法》的相关规定,</span><span
|
||||
class="text-black">对乙方所提供的服务信息予以保密,且严格遵守法律法规,不从事任何侵犯个人信息或商业秘密的活动。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">4.1.3
|
||||
甲方向乙方提交的服务需求及相关数据,均已经过被查询用户、最终用户充分有效的书面或电子授权,同意乙方及乙方的关联合作方对相关数据进行获取,并在乙方系统中或提交数据源方用于该合同项下的识别并进行储存、分析、处理及使用(含业务应用)等合法行为,甲方对此充分理解并认可。</span>
|
||||
</p>
|
||||
<p class="text-left"><span
|
||||
class="text-black">根据乙方要求,甲方应当向乙方提供由最终用户签字的授权文件,乙方仅予以形式审查;乙方对授权文件的审查不免除甲方就授权的真实性、合法性、完整性、与本合同约定的一致性所承担的责任。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">4.1.4
|
||||
甲方需向乙方提供经乙方服务识别有相关用户之信息及验证结果反馈。</span></p>
|
||||
<p class="text-left"><span class="text-black">4.1.5
|
||||
未经乙方同意,甲方不得将乙方提供的服务进行宣传或使用乙方名称、业务介绍、标识、商标、知识产权等。</span></p>
|
||||
<p class="text-left"><span class="text-black">4.1.6
|
||||
甲方应限于自身使用乙方提供的产品和服务,不得从事与乙方有竞争关系的业务。</span></p>
|
||||
<p class="text-left"><span class="text-black">4.1.7
|
||||
除为实现本合同目的确有必要外,甲方不将获得的服务信息进行存储、复制、下载、打印。</span></p>
|
||||
<p class="text-left"><span class="text-black">4.2
|
||||
乙方的权利和义务</span></p>
|
||||
<p class="text-left"><span class="text-black">4.2.1
|
||||
负责提供服务,向甲方提供标准平台服务或接口文件,为甲方开通服务账号。</span></p>
|
||||
<p class="text-left"><span class="text-black">4.2.2
|
||||
乙方负责其系统的设计、开发,使系统能够支撑合作业务正常运行,并有义务对甲方提出的维护请求给予支持。</span></p>
|
||||
<p class="text-left"><span class="text-black">4.2.3
|
||||
乙方为优化对甲方提供的服务,可对其服务系统进行升级、调试等处理。</span></p>
|
||||
<p class="text-left"><span class="text-black">4.2.4
|
||||
在乙方受甲方委托为甲方提供信息技术服务中所产生的后果(包括但不限于因甲方提供数据缺乏真实性或准确性导致服务偏差、乙方在受托服务中与甲方用户发生纠纷或遭投诉的),由甲方负责处理与承担。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">4.2.5
|
||||
如甲方未按约定支付服务费时,乙方有权中止服务。</span></p>
|
||||
<p class="text-left"><span class="text-black">4.2.6
|
||||
因甲方违反合同保密、授权条款或违反约定使用本服务(包括但不限于5.1.6、5.1.7、5.1.8)的,乙方可以中止或终止服务,并向甲方索赔对乙方造成损失的违约费用;</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">4.2.7
|
||||
乙方为履行本合同在不影响甲方权利的前提下可以通过乙方关联公司向甲方提供服务。</span></p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">五、
|
||||
知识产权</span></p>
|
||||
<p class="text-left"><span class="text-black">5.1
|
||||
|
||||
乙方享有本合同产品和服务相关内容之知识产权与所有权包括但不限于:软件、程序、源代码、文档、专利、商标、著作权、域名、专有技术、商业秘密、文字表达及其组合、数据、数据变量、数据算法、数据/模型、图标、图饰、图表、色彩、界面设计;除非经乙方许可,本合同并不赋予甲方享有乙方任何知识产权上的权利。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">5.2
|
||||
除非另有约定,任何一方均不可凭借本合同取得另一方所拥有的著作权、专利权、商标权或其他知识产权。</span></p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">六、
|
||||
保密与信息安全</span></p>
|
||||
<p class="text-left"><span class="text-black">
|
||||
除非双方签订《商业保密协议》对保密另有约定外,需履行以下保密约定:
|
||||
</span></p>
|
||||
<p class="text-left"><span class="text-black">6.1
|
||||
|
||||
保密信息:提供方(或其母公司、子公司、关联公司)向接受方披露的信息包括但不限于本合同内容、合作模式、商业计划、投资、经营方案、分析或计算方法、系统、数据、数据变量、数据算法、数据/模型、程序、装置、规格、序列、设计、研究或开发活动和方案、知识产权、专有技术、服务信息与服务说明、业务与营销方案、推广方法、销售额、客户名单、商业机会、成本、价格及其他财务信息。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">6.2
|
||||
|
||||
保密信息不包括:(1)在收到保密信息之时或之前已合法知悉的信息且该信息不受保密义务约束;(2)非因接受方违约而成为公众信息的信息;(3)接受方从披露方及其关联方以外的其他信息源所获知的信息;(4)提供方未明示为保密信息的信息;(5)接受方未利用任何保密信息而合法独立开发的信息,或通过接受方信息数据分析而获知的信息。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">6.3
|
||||
|
||||
上述保密信息可以以数据、文字及记载上述内容的书面资料、图书、录音资料、录像资料、光盘、软件、网页、客户端等有形媒介体现,也可通过口头等形式体现。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">6.4
|
||||
任何一方在提供保密信息时,如以书面形式提供,应注明‘保密’等相关字样;如以口头或可视形式透露,应在披露前明示接受方为保密信息。</span></p>
|
||||
<p class="text-left"><span class="text-black">6.5
|
||||
|
||||
除另有约定外,未经披露方书面同意,须将保密信息严格保密,并不得直接或间接导致、准许或容许向任何第三方披露、公布、转移、挪用或泄露保密信息;接受方均不向第三方及其不必要知悉的员工披露保密信息。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">6.6
|
||||
|
||||
除另有约定外,承担保密义务的范围不包括:(1)由乙方提供给数据源方的;(2)提供给乙方关联公司的;(3)将经适当汇总、编辑、修改、整理的保密信息在必要与合理范围内提供给律师和会计师及其他专业服务提供者;(4)按照法律、法规、监管、有管辖权的法院要求须提供的信息,但在不禁止的情况下应立即向保密信息提供方予以通报。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">6.7
|
||||
双方确认除非保密信息依法公开,保密义务自本合同签订时开始持续有效;甲方是否继续使用乙方的服务,不影响保密义务的承担。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">6.8
|
||||
|
||||
双方均应当遵守法律法规关于信息安全的管理规定,并采取有效措施保障信息安全包括但不限于保障计算机系统及其相关配套设备、设施(含网络)、运行环境、信息系统功能的安全运行等。</span>
|
||||
</p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">七、
|
||||
反商业贿赂</span></p>
|
||||
<p class="text-left"><span class="text-black">7.1
|
||||
双方都清楚并愿意严格遵守反商业贿赂的法律规定,任何形式的贿赂和贪渎行为都将触犯法律,并将受到不利后果。</span></p>
|
||||
<p class="text-left"><span class="text-black">7.2
|
||||
|
||||
双方均不得向对方或对方经办人或其他相关人员索要、收受、提供给予本合同约定外的任何利益,包括但不限于明扣、暗扣、现金、购物卡、实物、有价证券旅游或其他非物质利益等,但如该等利益属于行业惯例或通常礼仪做法的除外。</span>
|
||||
</p>
|
||||
<p class="text-left"><span
|
||||
class="text-black">本款“相关人员”是指双方经办人以外的与本合同有直接或间接利益关系的人员,如经办人的亲友等。</span>
|
||||
</p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">八、
|
||||
违约责任</span></p>
|
||||
<p class="text-left"><span class="text-black">8.1
|
||||
|
||||
除非另有约定,任何一方未履行本合同或违反约定且未及时改正或采取补救措施的,违约方应向守约方按照已发生合同金额的</span><span
|
||||
class="text-black">30</span><span
|
||||
class="text-black">%向守约方承担违约责任,并赔偿守约方因此受到的直接经济损失(包括调查、仲裁、诉讼、律师等合理费用)。</span></p>
|
||||
<p class="text-left"><span class="text-black">8.2
|
||||
|
||||
如因甲方原因导致本合同终止的,其无权要求乙方退回已收取而未使用的服务费;若一方因对方或其它非己方原因未能履行本合同下的义务,一方无需赔偿对方承受的损失。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">8.3
|
||||
一方未行使、迟延行使或部分行使其权利,并不意味该权利被放弃;某一权利不行使并不意味着其它权利被放弃。</span></p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">九、
|
||||
免责事由</span></p>
|
||||
<p class="text-left"><span class="text-black">除特别指明外,一方无须就下列情形承担责任:</span></p>
|
||||
<p class="text-left"><span class="text-black">9.1
|
||||
|
||||
不可抗力。不可抗力是指合同双方当事人不能预见、不能避免并不能克服的客观情况,包括但不限于:战争、骚乱、恐怖主义、洪水、地震、台风、国家公布的疫情等事件。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">9.2
|
||||
|
||||
因法律、法规、规章、规定、指引、通知、政策、命令及其他规范性文件或政府行为原因导致本合同不能履行的,适用关于不可抗力的规定。</span></p>
|
||||
<p class="text-left"><span class="text-black">9.3
|
||||
乙方为改善服务质量或数据源方对系统进行调整、升级、扩容等措施而导致服务中断、延时等情况。</span></p>
|
||||
<p class="text-left"><span class="text-black">9.4
|
||||
因网络、设备、黑客攻击、计算机病毒侵入或发作、通信或电力故障等不可预测因素造成不能提供服务的情形。</span></p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">十、
|
||||
争议解决与法律适用</span></p>
|
||||
<p class="text-left"><span class="text-black">10.1
|
||||
因履行本合同而发生的一切争议,甲乙双方应协商解决。协商不成的,任何一方均有权向乙方所在地有管辖权法院提起诉讼。</span></p>
|
||||
<p class="text-left"><span class="text-black">十一、
|
||||
通知与送达</span></p>
|
||||
<p class="text-left"><span class="text-black">11.1
|
||||
|
||||
本合同项下的通知应以电子邮件、快递、传真、专人递送按合同签署页所列示联系方式发出,除非任何一方已书面通知对方变更联系方式并经对方确认。</span></p>
|
||||
<p class="text-left"><span class="text-black">11.2
|
||||
|
||||
书面通知的形式包括在网站公告、电子邮件、站内信、微信、手机短信和传真等电子方式及纸质文件。通知在下列日期视为送达被通知方:如以电子邮件发送,显示成功发送确认时,或发送后第一个工作日内未被退回;如是以快递发送,以交邮后第五个工作日;如以传真发送,于发件人传真机记录传输确认时;如以专人递送,被通知方签收日。</span>
|
||||
</p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">十二、
|
||||
其他</span></p>
|
||||
<p class="text-left"><span class="text-black">12.1
|
||||
|
||||
本合同附件作为本合同的组成部分,与本合同具有同等的法律效力;未尽事宜经双方协商后签订补充合同,补充合同与本合同条款如有冲突,以补充合同为准。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">12.2
|
||||
本合同终止日与服务有效期终止日相同,在合同期限届满前由双方友好协商是否续约。</span></p>
|
||||
<p class="text-left"><span class="text-black">12.3
|
||||
|
||||
本合同终止不影响合同中有关授权、付款、保密、知识产权、反商业贿赂、违约责任、争议解决与法律适用等可以独立存在的条款的效力,一方未履行完毕的义务仍需继续履行。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">12.4
|
||||
本合同自甲方支付/充值费用之日</span><span class="text-black">计算服务期限</span><span
|
||||
class="text-black">,合同期限为12个月。</span></p>
|
||||
<p style="text-indent:24pt;"><span>本协议通过点击同意/勾选的方式签署,自签署之日生效。</span>
|
||||
</p>
|
||||
<p style="text-indent: 24pt; text-align: right;"><span>本协议于 {{ effectiveDate }}生效。</span></p>
|
||||
<p class="text-center"></p>
|
||||
<p></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const companyName = import.meta.env.VITE_COMPANY_NAME
|
||||
const getFormattedDate = () => {
|
||||
const date = new Date()
|
||||
const year = date.getFullYear()
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||
const day = date.getDate().toString().padStart(2, '0')
|
||||
return `${year} 年 ${month} 月 ${day} 日`
|
||||
}
|
||||
|
||||
const effectiveDate = ref(getFormattedDate())
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
p {
|
||||
@apply my-1
|
||||
}
|
||||
</style>
|
||||
530
src/views/AgentSystemGuide.vue
Normal file
530
src/views/AgentSystemGuide.vue
Normal file
@@ -0,0 +1,530 @@
|
||||
<template>
|
||||
<div class="agent-system-guide min-h-screen" style="background-color: #f7f8fa;">
|
||||
<div class="px-4 pt-6 pb-4">
|
||||
<h1 class="text-2xl font-bold mb-1" style="color: var(--van-text-color);">代理系统指南</h1>
|
||||
<p class="text-sm" style="color: var(--van-text-color-2);">了解代理政策、等级特权与收益计算</p>
|
||||
</div>
|
||||
|
||||
<div class="px-4 pb-6 space-y-4">
|
||||
<!-- 代理政策概述 -->
|
||||
<section class="bg-white rounded-lg p-4" style="box-shadow: 0 2px 8px rgba(0,0,0,0.08);">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-1 h-5 rounded-full mr-2" style="background-color: var(--van-theme-primary);"></div>
|
||||
<h2 class="text-lg font-semibold" style="color: var(--van-text-color);">代理政策概述</h2>
|
||||
</div>
|
||||
<div class="space-y-2.5 text-sm leading-relaxed" style="color: var(--van-text-color-2);">
|
||||
<p>
|
||||
真爱查代理系统采用三级代理体系,为合作伙伴提供多层级、多收益的推广模式。
|
||||
通过推广平台产品和服务,代理可获得推广佣金、下级返佣、升级返佣等多种收益。
|
||||
</p>
|
||||
<p>
|
||||
系统分为<strong>普通代理</strong>、<strong>黄金代理</strong>、<strong>钻石代理</strong>三个等级,
|
||||
不同等级享有不同的权限和收益比例。
|
||||
</p>
|
||||
<p class="text-sm mt-2" style="color: var(--van-text-color-3);">
|
||||
详细政策请查看《代理管理协议》
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 代理等级说明 -->
|
||||
<section class="bg-white rounded-lg p-4" style="box-shadow: 0 2px 8px rgba(0,0,0,0.08);">
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="w-1 h-5 rounded-full mr-2" style="background-color: var(--van-theme-primary);"></div>
|
||||
<h2 class="text-lg font-semibold" style="color: var(--van-text-color);">代理等级体系</h2>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- 普通代理 -->
|
||||
<div class="border rounded-lg p-4 mb-3" style="border-color: #e5e7eb; background-color: #fafafa;">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
class="w-10 h-10 rounded-full bg-gray-400 flex items-center justify-center text-white font-semibold text-base mr-3">
|
||||
1
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-base font-semibold" style="color: var(--van-text-color);">普通代理</h3>
|
||||
<p class="text-sm" style="color: var(--van-text-color-2);">基础代理特权</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="px-2.5 py-1 rounded-full text-sm font-medium"
|
||||
style="background-color: #f3f4f6; color: #6b7280;">
|
||||
入门级
|
||||
</span>
|
||||
</div>
|
||||
<div class="space-y-1.5 text-sm" style="color: var(--van-text-color-2);">
|
||||
<div class="flex items-start">
|
||||
<span class="text-green-500 mr-1.5 mt-0.5">✓</span>
|
||||
<span>推广产品获得推广佣金</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-green-500 mr-1.5 mt-0.5">✓</span>
|
||||
<span>邀请下级代理,获得下级推广返佣</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-green-500 mr-1.5 mt-0.5">✓</span>
|
||||
<span>查看团队统计和转化率数据</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-green-500 mr-1.5 mt-0.5">✓</span>
|
||||
<span>生成和管理邀请码</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-green-500 mr-1.5 mt-0.5">✓</span>
|
||||
<span>申请升级为黄金或钻石代理</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 黄金代理 -->
|
||||
<div class="border rounded-lg p-4 mb-3" style="border-color: #fbbf24; background-color: #fffbeb;">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="flex items-center">
|
||||
<div class="w-10 h-10 rounded-full flex items-center justify-center text-white font-semibold text-base mr-3"
|
||||
style="background: linear-gradient(135deg, #f59e0b, #d97706);">
|
||||
2
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-base font-semibold" style="color: #92400e;">黄金代理</h3>
|
||||
<p class="text-sm" style="color: #b45309;">高级代理特权</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="px-2.5 py-1 rounded-full text-sm font-medium"
|
||||
style="background-color: #fef3c7; color: #92400e;">
|
||||
进阶级
|
||||
</span>
|
||||
</div>
|
||||
<div class="space-y-1.5 text-sm" style="color: var(--van-text-color-2);">
|
||||
<div class="flex items-start">
|
||||
<span class="text-green-500 mr-1.5 mt-0.5">✓</span>
|
||||
<span class="font-medium">享有普通代理所有权限</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-yellow-500 mr-1.5 mt-0.5">★</span>
|
||||
<span>更高的推广佣金比例</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-yellow-500 mr-1.5 mt-0.5">★</span>
|
||||
<span>更高的下级推广返佣比例</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-yellow-500 mr-1.5 mt-0.5">★</span>
|
||||
<span>获得下级升级返佣(当普通下级升级为黄金时)</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-yellow-500 mr-1.5 mt-0.5">★</span>
|
||||
<span>可申请升级为钻石代理</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 钻石代理 -->
|
||||
<div class="border rounded-lg p-4" style="border-color: #a855f7; background-color: #faf5ff;">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="flex items-center">
|
||||
<div class="w-10 h-10 rounded-full flex items-center justify-center text-white font-semibold text-base mr-3"
|
||||
style="background: linear-gradient(135deg, #9333ea, #7c3aed);">
|
||||
💎
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-base font-semibold" style="color: #6b21a8;">钻石代理</h3>
|
||||
<p class="text-sm" style="color: #7c3aed;">尊享代理特权</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="px-2.5 py-1 rounded-full text-sm font-medium"
|
||||
style="background-color: #e9d5ff; color: #6b21a8;">
|
||||
最高级
|
||||
</span>
|
||||
</div>
|
||||
<div class="space-y-1.5 text-sm" style="color: var(--van-text-color-2);">
|
||||
<div class="flex items-start">
|
||||
<span class="text-green-500 mr-1.5 mt-0.5">✓</span>
|
||||
<span class="font-medium">享有黄金代理所有权限</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-purple-500 mr-1.5 mt-0.5">★</span>
|
||||
<span>最高推广佣金比例</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-purple-500 mr-1.5 mt-0.5">★</span>
|
||||
<span>最高下级推广返佣比例</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-purple-500 mr-1.5 mt-0.5">★</span>
|
||||
<span>获得下级升级返佣(当普通/黄金下级升级时)</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-purple-500 mr-1.5 mt-0.5">★</span>
|
||||
<span class="font-medium">可调整下级代理等级(将普通代理升级为黄金代理)</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-purple-500 mr-1.5 mt-0.5">★</span>
|
||||
<span>专属客服支持</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 收益计算说明 -->
|
||||
<section class="bg-white rounded-lg p-4" style="box-shadow: 0 2px 8px rgba(0,0,0,0.08);">
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="w-1 h-5 rounded-full mr-2" style="background-color: var(--van-theme-primary);"></div>
|
||||
<h2 class="text-lg font-semibold" style="color: var(--van-text-color);">收益计算方式</h2>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<!-- 推广佣金 -->
|
||||
<div class="rounded-lg p-3 border-l-2" style="background-color: #eff6ff; border-color: #3b82f6;">
|
||||
<div class="flex items-center mb-2">
|
||||
<van-icon name="gold-coin" class="text-base mr-2" style="color: #3b82f6;" />
|
||||
<h3 class="text-base font-semibold" style="color: #1e40af;">推广佣金</h3>
|
||||
</div>
|
||||
<p class="text-sm mb-2.5" style="color: var(--van-text-color-2);">
|
||||
当您推广的产品被用户购买并支付成功后,您将获得推广佣金。
|
||||
</p>
|
||||
<div class="bg-white rounded p-2.5 text-sm">
|
||||
<div class="font-medium mb-1.5" style="color: var(--van-text-color);">计算公式:</div>
|
||||
<div class="mb-1.5" style="color: var(--van-text-color-2);">
|
||||
推广佣金 = 订单金额 × 佣金比例
|
||||
</div>
|
||||
<div class="text-sm" style="color: var(--van-text-color-3);">
|
||||
佣金比例根据您的代理等级和产品类型而定,等级越高,佣金比例越高
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 下级推广返佣 -->
|
||||
<div class="rounded-lg p-3 border-l-2" style="background-color: #f0fdf4; border-color: #22c55e;">
|
||||
<div class="flex items-center mb-2">
|
||||
<van-icon name="gift-o" class="text-base mr-2" style="color: #22c55e;" />
|
||||
<h3 class="text-base font-semibold" style="color: #15803d;">下级推广返佣</h3>
|
||||
</div>
|
||||
<p class="text-sm mb-2.5" style="color: var(--van-text-color-2);">
|
||||
当您的下级代理推广产品产生订单时,您将获得一定比例的返佣。
|
||||
</p>
|
||||
<div class="bg-white rounded p-2.5 text-sm">
|
||||
<div class="font-medium mb-1.5" style="color: var(--van-text-color);">返佣规则:</div>
|
||||
<ul class="space-y-1 mb-1.5" style="color: var(--van-text-color-2);">
|
||||
<li>• 直接下级:获得直接下级推广订单的返佣</li>
|
||||
<li>• 间接下级:获得间接下级推广订单的返佣</li>
|
||||
<li>• 返佣比例根据您的代理等级而定</li>
|
||||
</ul>
|
||||
<div class="text-sm" style="color: var(--van-text-color-3);">
|
||||
返佣金额 = 下级订单金额 × 返佣比例
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 下级升级返佣 -->
|
||||
<div class="rounded-lg p-3 border-l-2" style="background-color: #faf5ff; border-color: #a855f7;">
|
||||
<div class="flex items-center mb-2">
|
||||
<van-icon name="arrow-up" class="text-base mr-2" style="color: #a855f7;" />
|
||||
<h3 class="text-base font-semibold" style="color: #6b21a8;">下级升级返佣</h3>
|
||||
</div>
|
||||
<p class="text-sm mb-2.5" style="color: var(--van-text-color-2);">
|
||||
当您的下级代理付费升级为更高等级时,您将获得升级返佣。
|
||||
</p>
|
||||
<div class="bg-white rounded p-2.5 text-sm">
|
||||
<div class="font-medium mb-1.5" style="color: var(--van-text-color);">返佣规则:</div>
|
||||
<ul class="space-y-1 mb-1.5" style="color: var(--van-text-color-2);">
|
||||
<li>• 普通代理升级为黄金代理:上级获得返佣</li>
|
||||
<li>• 普通/黄金代理升级为钻石代理:上级获得返佣</li>
|
||||
<li>• 返佣金额由系统配置,在升级时显示</li>
|
||||
</ul>
|
||||
<div class="text-sm" style="color: var(--van-text-color-3);">
|
||||
只有黄金代理和钻石代理才能获得下级升级返佣
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 收益统计 -->
|
||||
<div class="rounded-lg p-3 border-l-2" style="background-color: #fff7ed; border-color: #f97316;">
|
||||
<div class="flex items-center mb-2">
|
||||
<van-icon name="balance-list" class="text-base mr-2" style="color: #f97316;" />
|
||||
<h3 class="text-base font-semibold" style="color: #c2410c;">收益统计</h3>
|
||||
</div>
|
||||
<div class="bg-white rounded p-2.5 text-sm" style="color: var(--van-text-color-2);">
|
||||
<p class="mb-1.5">您可以在"我的"页面查看详细的收益统计:</p>
|
||||
<ul class="space-y-1">
|
||||
<li>• <span class="font-medium">累计总收益</span>:所有收益的总和</li>
|
||||
<li>• <span class="font-medium">今日收益</span>:当天的收益金额</li>
|
||||
<li>• <span class="font-medium">本月收益</span>:本月的收益金额</li>
|
||||
<li>• <span class="font-medium">余额</span>:可提现的金额</li>
|
||||
<li>• <span class="font-medium">风险保障金</span>:冻结的保障金</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 功能玩法 -->
|
||||
<section class="bg-white rounded-lg p-4" style="box-shadow: 0 2px 8px rgba(0,0,0,0.08);">
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="w-1 h-5 rounded-full mr-2" style="background-color: var(--van-theme-primary);"></div>
|
||||
<h2 class="text-lg font-semibold" style="color: var(--van-text-color);">功能玩法</h2>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<!-- 推广查询 -->
|
||||
<div class="rounded-lg p-3 border" style="background-color: #eff6ff; border-color: #bfdbfe;">
|
||||
<div class="flex items-center mb-1.5">
|
||||
<van-icon name="share-o" class="text-sm mr-1.5" style="color: #3b82f6;" />
|
||||
<h3 class="font-semibold text-sm" style="color: #1e40af;">推广查询</h3>
|
||||
</div>
|
||||
<p class="text-sm leading-relaxed" style="color: var(--van-text-color-2);">
|
||||
生成推广链接,分享给用户购买查询服务,获得推广佣金
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 邀请下级 -->
|
||||
<div class="rounded-lg p-3 border" style="background-color: #f0fdf4; border-color: #bbf7d0;">
|
||||
<div class="flex items-center mb-1.5">
|
||||
<van-icon name="friends-o" class="text-sm mr-1.5" style="color: #22c55e;" />
|
||||
<h3 class="font-semibold text-sm" style="color: #15803d;">邀请下级</h3>
|
||||
</div>
|
||||
<p class="text-sm leading-relaxed" style="color: var(--van-text-color-2);">
|
||||
通过邀请码邀请他人成为您的下级代理,获得下级推广返佣
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 团队管理 -->
|
||||
<div class="rounded-lg p-3 border" style="background-color: #faf5ff; border-color: #e9d5ff;">
|
||||
<div class="flex items-center mb-1.5">
|
||||
<van-icon name="manager-o" class="text-sm mr-1.5" style="color: #a855f7;" />
|
||||
<h3 class="font-semibold text-sm" style="color: #6b21a8;">团队管理</h3>
|
||||
</div>
|
||||
<p class="text-sm leading-relaxed" style="color: var(--van-text-color-2);">
|
||||
查看团队统计、转化率数据,了解团队发展情况
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 升级代理 -->
|
||||
<div class="rounded-lg p-3 border" style="background-color: #fffbeb; border-color: #fde68a;">
|
||||
<div class="flex items-center mb-1.5">
|
||||
<van-icon name="arrow-up" class="text-sm mr-1.5" style="color: #f59e0b;" />
|
||||
<h3 class="font-semibold text-sm" style="color: #92400e;">升级代理</h3>
|
||||
</div>
|
||||
<p class="text-sm leading-relaxed" style="color: var(--van-text-color-2);">
|
||||
付费升级为更高等级,享受更高佣金和返佣比例
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 邀请码管理 -->
|
||||
<div class="rounded-lg p-3 border" style="background-color: #eef2ff; border-color: #c7d2fe;">
|
||||
<div class="flex items-center mb-1.5">
|
||||
<van-icon name="qr" class="text-sm mr-1.5" style="color: #6366f1;" />
|
||||
<h3 class="font-semibold text-sm" style="color: #4338ca;">邀请码管理</h3>
|
||||
</div>
|
||||
<p class="text-sm leading-relaxed" style="color: var(--van-text-color-2);">
|
||||
生成、查看和管理邀请码,用于邀请下级代理
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 实名认证 -->
|
||||
<div class="rounded-lg p-3 border" style="background-color: #fef2f2; border-color: #fecaca;">
|
||||
<div class="flex items-center mb-1.5">
|
||||
<van-icon name="passed" class="text-sm mr-1.5" style="color: #ef4444;" />
|
||||
<h3 class="font-semibold text-sm" style="color: #b91c1c;">实名认证</h3>
|
||||
</div>
|
||||
<p class="text-sm leading-relaxed" style="color: var(--van-text-color-2);">
|
||||
完成实名认证后,才能申请提现收益
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 提现功能 -->
|
||||
<div class="rounded-lg p-3 border" style="background-color: #f0fdfa; border-color: #a7f3d0;">
|
||||
<div class="flex items-center mb-1.5">
|
||||
<van-icon name="cash" class="text-sm mr-1.5" style="color: #14b8a6;" />
|
||||
<h3 class="font-semibold text-sm" style="color: #0d9488;">提现功能</h3>
|
||||
</div>
|
||||
<p class="text-sm leading-relaxed" style="color: var(--van-text-color-2);">
|
||||
将收益提现到银行卡,需要完成实名认证
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 调整级别(仅钻石) -->
|
||||
<div class="rounded-lg p-3 border" style="background-color: #f5f3ff; border-color: #ddd6fe;">
|
||||
<div class="flex items-center mb-1.5">
|
||||
<van-icon name="setting-o" class="text-sm mr-1.5" style="color: #8b5cf6;" />
|
||||
<h3 class="font-semibold text-sm" style="color: #6b21a8;">调整级别</h3>
|
||||
</div>
|
||||
<p class="text-sm leading-relaxed" style="color: var(--van-text-color-2);">
|
||||
<span class="font-medium" style="color: #7c3aed;">钻石代理专属</span>:可将普通下级升级为黄金代理
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 操作指南 -->
|
||||
<section class="bg-white rounded-lg p-4" style="box-shadow: 0 2px 8px rgba(0,0,0,0.08);">
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="w-1 h-5 rounded-full mr-2" style="background-color: var(--van-theme-primary);"></div>
|
||||
<h2 class="text-lg font-semibold" style="color: var(--van-text-color);">操作指南</h2>
|
||||
</div>
|
||||
|
||||
<!-- 如何推广报告 -->
|
||||
<div class="mb-4">
|
||||
<div class="flex items-center mb-2.5">
|
||||
<van-icon name="share-o" class="text-base mr-2" style="color: #3b82f6;" />
|
||||
<h3 class="text-base font-semibold" style="color: var(--van-text-color);">如何推广报告</h3>
|
||||
</div>
|
||||
<div class="space-y-2 text-sm leading-relaxed" style="color: var(--van-text-color-2);">
|
||||
<div class="flex items-start">
|
||||
<span class="text-blue-500 mr-1.5 mt-0.5 font-medium">1.</span>
|
||||
<span>登录代理账户后,进入"推广"页面,选择要推广的报告类型(如个人大数据、婚恋风险、入职背调等)。</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-blue-500 mr-1.5 mt-0.5 font-medium">2.</span>
|
||||
<span>系统将自动生成专属推广链接,您可以将该链接分享给潜在用户。用户通过您的推广链接购买查询服务后,您即可获得推广佣金。</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-blue-500 mr-1.5 mt-0.5 font-medium">3.</span>
|
||||
<span>您可以在推广页面设置查询服务的售价(需在平台设定的价格范围内),售价与成本价的差额即为您的推广收益。</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-blue-500 mr-1.5 mt-0.5 font-medium">4.</span>
|
||||
<span>推广链接支持多种分享方式,包括复制链接、生成二维码、生成短链等,方便您在不同渠道进行推广。</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-blue-500 mr-1.5 mt-0.5 font-medium">5.</span>
|
||||
<span>您可以在"推广查询记录"中查看所有通过您推广链接产生的订单详情,包括订单金额、收益金额、订单状态等信息。</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-blue-500 mr-1.5 mt-0.5 font-medium">6.</span>
|
||||
<span>推广佣金将在用户完成支付后自动结算到您的账户余额中,您可以在"我的"页面查看收益统计。</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 如何邀请下级代理 -->
|
||||
<div class="border-t pt-4" style="border-color: #e5e7eb;">
|
||||
<div class="flex items-center mb-2.5">
|
||||
<van-icon name="friends-o" class="text-base mr-2" style="color: #22c55e;" />
|
||||
<h3 class="text-base font-semibold" style="color: var(--van-text-color);">如何邀请下级代理</h3>
|
||||
</div>
|
||||
<div class="space-y-2 text-sm leading-relaxed" style="color: var(--van-text-color-2);">
|
||||
<div class="flex items-start">
|
||||
<span class="text-green-500 mr-1.5 mt-0.5 font-medium">1.</span>
|
||||
<span>登录代理账户后,进入"邀请码管理"页面,系统会为您生成专属邀请码。</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-green-500 mr-1.5 mt-0.5 font-medium">2.</span>
|
||||
<span>您可以将邀请码分享给想要成为代理的用户。用户通过您的邀请码注册成为代理后,将自动成为您的下级代理。</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-green-500 mr-1.5 mt-0.5 font-medium">3.</span>
|
||||
<span>当您的下级代理推广产品产生订单时,您将获得一定比例的下级推广返佣。返佣比例根据您的代理等级而定,等级越高,返佣比例越高。</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-green-500 mr-1.5 mt-0.5 font-medium">4.</span>
|
||||
<span>您可以在"我的团队"页面查看所有下级代理的信息,包括直接下级和间接下级,以及团队统计数据。</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-green-500 mr-1.5 mt-0.5 font-medium">5.</span>
|
||||
<span>当您的下级代理付费升级为更高等级时(如普通代理升级为黄金代理),您将获得下级升级返佣。只有黄金代理和钻石代理才能获得下级升级返佣。</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-green-500 mr-1.5 mt-0.5 font-medium">6.</span>
|
||||
<span>钻石代理拥有特殊权限,可以将普通下级代理直接升级为黄金代理,无需下级代理付费。</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-green-500 mr-1.5 mt-0.5 font-medium">7.</span>
|
||||
<span>您可以在"下级推广收益"中查看所有来自下级代理的返佣明细,包括推广返佣和升级返佣。</span>
|
||||
</div>
|
||||
<div class="flex items-start">
|
||||
<span class="text-green-500 mr-1.5 mt-0.5 font-medium">8.</span>
|
||||
<span>邀请下级代理是扩大团队规模、增加收益的重要方式,建议您积极发展下级代理,建立稳定的推广团队。</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 提现说明 -->
|
||||
<section class="bg-white rounded-lg p-4" style="box-shadow: 0 2px 8px rgba(0,0,0,0.08);">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="w-1 h-5 rounded-full mr-2" style="background-color: var(--van-theme-primary);"></div>
|
||||
<h2 class="text-lg font-semibold" style="color: var(--van-text-color);">提现说明</h2>
|
||||
</div>
|
||||
<div class="space-y-2.5 text-sm leading-relaxed">
|
||||
<div class="rounded p-2.5 border-l-2" style="background-color: #fffbeb; border-color: #fbbf24;">
|
||||
<p class="font-medium mb-1" style="color: #92400e;">提现条件:</p>
|
||||
<ul class="space-y-0.5" style="color: #78350f;">
|
||||
<li>1. 必须完成实名认证(三要素核验)</li>
|
||||
<li>2. 单笔提现金额需达到 50 元及以上</li>
|
||||
<li>3. 风险保障金需满足平台要求</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="rounded p-2.5 border-l-2" style="background-color: #eff6ff; border-color: #60a5fa;">
|
||||
<p class="font-medium mb-1" style="color: #1e40af;">注意事项:</p>
|
||||
<ul class="space-y-0.5" style="color: #1e3a8a;">
|
||||
<li>• 提现到账账户的实名信息必须与实名认证信息完全一致(同一身份证/同一持卡人),否则可能提现失败或原路退回</li>
|
||||
<li>• 实名认证时请务必确认使用的证件信息与后续用于收款的账户为同一人</li>
|
||||
<li>• 平台会根据国家税收法律法规代扣代缴个人所得税</li>
|
||||
<li>• 提现申请提交后,平台会在规定时间内处理</li>
|
||||
<li>• 可在\"提现记录\"中查看提现状态</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="flex gap-2.5 pb-6">
|
||||
<button v-if="!isAgent && isLoggedIn" @click="toRegister"
|
||||
class="flex-1 py-2.5 rounded-lg text-white text-sm font-medium"
|
||||
style="background-color: var(--van-theme-primary);">
|
||||
立即注册成为代理
|
||||
</button>
|
||||
<button v-if="!isLoggedIn" @click="toLogin"
|
||||
class="flex-1 py-2.5 rounded-lg text-white text-sm font-medium"
|
||||
style="background-color: var(--van-theme-primary);">
|
||||
登录后注册代理
|
||||
</button>
|
||||
<button v-if="isAgent && level < 3" @click="toUpgrade"
|
||||
class="flex-1 py-2.5 rounded-lg text-white text-sm font-medium" style="background-color: #8b5cf6;">
|
||||
升级代理等级
|
||||
</button>
|
||||
<button @click="toAgreement" class="px-4 py-2.5 rounded-lg border text-sm font-medium"
|
||||
style="border-color: var(--van-theme-primary); color: var(--van-theme-primary);">
|
||||
查看协议
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter } from "vue-router";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useAgentStore } from "@/stores/agentStore";
|
||||
import { useUserStore } from "@/stores/userStore";
|
||||
|
||||
const router = useRouter();
|
||||
const agentStore = useAgentStore();
|
||||
const userStore = useUserStore();
|
||||
const { isAgent, level } = storeToRefs(agentStore);
|
||||
const { isLoggedIn } = storeToRefs(userStore);
|
||||
|
||||
const toRegister = () => {
|
||||
router.push("/register");
|
||||
};
|
||||
|
||||
const toLogin = () => {
|
||||
router.push("/login");
|
||||
};
|
||||
|
||||
const toUpgrade = () => {
|
||||
router.push({ name: "agentUpgrade" });
|
||||
};
|
||||
|
||||
const toAgreement = () => {
|
||||
router.push("/agentManageAgreement");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.agent-system-guide {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
444
src/views/AgentUpgrade.vue
Normal file
444
src/views/AgentUpgrade.vue
Normal file
@@ -0,0 +1,444 @@
|
||||
<template>
|
||||
<div class="p-4 min-h-screen pb-24" style="background: linear-gradient(to bottom, #fef3c7, #fef9e7);">
|
||||
<!-- 当前等级卡片 -->
|
||||
<div class="rounded-xl shadow-lg mb-4 bg-white p-6">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="level-badge" :class="getLevelBadgeClass(level)">
|
||||
{{ getLevelName(level) }}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex justify-between items-start gap-2">
|
||||
<div>
|
||||
<p class="text-lg font-bold" style="color: var(--van-text-color);">当前等级</p>
|
||||
<p class="text-sm mt-1" style="color: var(--van-text-color-2);">{{ getLevelDesc(level) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 当前等级权益展示(与标题并排) -->
|
||||
<div v-if="getPrivilegeInfo(level)" class="space-y-1 text-sm text-right"
|
||||
style="color: var(--van-text-color);">
|
||||
<div class="flex items-center gap-1 justify-end">
|
||||
<span>设置查询价最高{{ getPrivilegeInfo(level).max_set_price }}元</span>
|
||||
<van-icon name="checked" size="14" color="#f59e0b" />
|
||||
</div>
|
||||
<div class="flex items-center gap-1 justify-end">
|
||||
<span>邀请黄金代理,奖励{{ getPrivilegeInfo(level).invite_gold_reward }}元</span>
|
||||
<van-icon name="checked" size="14" color="#f59e0b" />
|
||||
</div>
|
||||
<div class="flex items-center gap-1 justify-end">
|
||||
<span>邀请钻石代理,奖励{{ getPrivilegeInfo(level).invite_diamond_reward }}元</span>
|
||||
<van-icon name="checked" size="14" color="#f59e0b" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 升级选项 -->
|
||||
<div class="mb-4">
|
||||
<h2 class="text-lg font-bold mb-4" style="color: #92400e;">选择升级等级</h2>
|
||||
|
||||
<!-- 黄金代理选项 -->
|
||||
<div v-if="canUpgradeToGold" class="rounded-xl shadow-lg mb-4 bg-white p-6 cursor-pointer border-2"
|
||||
:style="selectedToLevel === 2 ? 'border-color: #d4af37;' : 'border-color: transparent;'"
|
||||
@click="selectUpgrade(2)">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<van-icon name="medal" size="20" color="#f59e0b" />
|
||||
<span class="text-lg font-bold" style="color: var(--van-text-color);">黄金代理</span>
|
||||
<van-icon v-if="selectedToLevel === 2" name="checked" size="20" color="#f59e0b" />
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="text-xs mb-1" style="color: var(--van-text-color-2);">升级费用</div>
|
||||
<div class="text-2xl font-bold" style="color: #f59e0b;">¥{{ goldPrice }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="getPrivilegeInfo(2)" class="space-y-2">
|
||||
<!-- 设置查询价最高 -->
|
||||
<div class="flex items-center gap-2 text-sm" style="color: var(--van-text-color);">
|
||||
<van-icon name="checked" size="14" color="#f59e0b" />
|
||||
<span>设置查询价最高{{ getPrivilegeInfo(2).max_set_price }}元</span>
|
||||
</div>
|
||||
<!-- 查询底价降低 -->
|
||||
<div class="flex items-center gap-2 text-sm" style="color: var(--van-text-color);">
|
||||
<van-icon name="checked" size="14" color="#f59e0b" />
|
||||
<span>查询底价降低{{ formatAmount(getPrivilegeInfo(2).price_reduction) }}元每单</span>
|
||||
</div>
|
||||
<!-- 下级代理查询奖励最高 -->
|
||||
<div class="flex items-center gap-2 text-sm" style="color: var(--van-text-color);">
|
||||
<van-icon name="checked" size="14" color="#f59e0b" />
|
||||
<span>下级代理查询奖励最高{{ getPrivilegeInfo(2).subordinate_reward_max }}元每单</span>
|
||||
</div>
|
||||
<!-- 邀请黄金代理奖励 -->
|
||||
<div class="flex items-center gap-2 text-sm" style="color: var(--van-text-color);">
|
||||
<van-icon name="checked" size="14" color="#f59e0b" />
|
||||
<span>邀请黄金代理奖励{{ getPrivilegeInfo(2).invite_gold_reward }}元</span>
|
||||
</div>
|
||||
<!-- 邀请钻石代理奖励 -->
|
||||
<div class="flex items-center gap-2 text-sm" style="color: var(--van-text-color);">
|
||||
<van-icon name="checked" size="14" color="#f59e0b" />
|
||||
<span>邀请钻石代理奖励{{ getPrivilegeInfo(2).invite_diamond_reward }}元</span>
|
||||
</div>
|
||||
<!-- 更多权益 -->
|
||||
<div class="flex items-center gap-2 text-sm" style="color: var(--van-text-color);">
|
||||
<van-icon name="checked" size="14" color="#f59e0b" />
|
||||
<span>更多权益敬请期待</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 钻石代理选项 -->
|
||||
<div v-if="canUpgradeToDiamond" class="rounded-xl shadow-lg mb-4 bg-white p-6 cursor-pointer border-2"
|
||||
:style="selectedToLevel === 3 ? 'border-color: #d4af37;' : 'border-color: transparent;'"
|
||||
@click="selectUpgrade(3)">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<van-icon name="medal" size="20" color="#f59e0b" />
|
||||
<span class="text-lg font-bold" style="color: var(--van-text-color);">钻石代理</span>
|
||||
<van-icon v-if="selectedToLevel === 3" name="checked" size="20" color="#f59e0b" />
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="text-xs mb-1" style="color: var(--van-text-color-2);">升级费用</div>
|
||||
<div class="text-2xl font-bold" style="color: #f59e0b;">¥{{ diamondPrice }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="getPrivilegeInfo(3)" class="space-y-2">
|
||||
<!-- 升级费用 -->
|
||||
<div class="flex items-center gap-2 text-sm" style="color: var(--van-text-color);">
|
||||
<van-icon name="checked" size="14" color="#f59e0b" />
|
||||
<span>升级费用¥{{ formatAmount(getUpgradeInfo(3).upgradeFee) }}</span>
|
||||
</div>
|
||||
<!-- 设置查询价最高 -->
|
||||
<div class="flex items-center gap-2 text-sm" style="color: var(--van-text-color);">
|
||||
<van-icon name="checked" size="14" color="#f59e0b" />
|
||||
<span>设置查询价最高{{ getPrivilegeInfo(3).max_set_price }}元</span>
|
||||
</div>
|
||||
<!-- 查询底价降低 -->
|
||||
<div class="flex items-center gap-2 text-sm" style="color: var(--van-text-color);">
|
||||
<van-icon name="checked" size="14" color="#f59e0b" />
|
||||
<span>查询底价降低{{ formatAmount(getPrivilegeInfo(3).price_reduction) }}元每单</span>
|
||||
</div>
|
||||
<!-- 下级代理查询奖励最高 -->
|
||||
<div class="flex items-center gap-2 text-sm" style="color: var(--van-text-color);">
|
||||
<van-icon name="checked" size="14" color="#f59e0b" />
|
||||
<span>下级代理查询奖励最高{{ getPrivilegeInfo(3).subordinate_reward_max }}元每单</span>
|
||||
</div>
|
||||
<!-- 邀请黄金代理奖励 -->
|
||||
<div class="flex items-center gap-2 text-sm" style="color: var(--van-text-color);">
|
||||
<van-icon name="checked" size="14" color="#f59e0b" />
|
||||
<span>邀请黄金代理奖励{{ getPrivilegeInfo(3).invite_gold_reward }}元</span>
|
||||
</div>
|
||||
<!-- 邀请钻石代理奖励 -->
|
||||
<div class="flex items-center gap-2 text-sm" style="color: var(--van-text-color);">
|
||||
<van-icon name="checked" size="14" color="#f59e0b" />
|
||||
<span>邀请钻石代理奖励{{ getPrivilegeInfo(3).invite_diamond_reward }}元</span>
|
||||
</div>
|
||||
<!-- 调整下级代理级别权限 -->
|
||||
<div class="flex items-center gap-2 text-sm" style="color: var(--van-text-color);">
|
||||
<van-icon name="checked" size="14" color="#f59e0b" />
|
||||
<span>调整下级代理级别权限</span>
|
||||
</div>
|
||||
<!-- 系统内白名单权限 -->
|
||||
<div class="flex items-center gap-2 text-sm" style="color: var(--van-text-color);">
|
||||
<van-icon name="checked" size="14" color="#f59e0b" />
|
||||
<span>系统内白名单权限</span>
|
||||
</div>
|
||||
<!-- 更多权益 -->
|
||||
<div class="flex items-center gap-2 text-sm" style="color: var(--van-text-color);">
|
||||
<van-icon name="checked" size="14" color="#f59e0b" />
|
||||
<span>更多权益敬请期待</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 已是最高等级提示 -->
|
||||
<div v-if="!canUpgradeToGold && !canUpgradeToDiamond" class="rounded-xl shadow-lg bg-white p-8 text-center">
|
||||
<van-icon name="medal" size="48" color="#f59e0b" />
|
||||
<p class="text-lg font-bold mt-4" style="color: var(--van-text-color);">您已是最高等级</p>
|
||||
<p class="text-sm mt-2" style="color: var(--van-text-color-2);">钻石代理享有最高权益</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部固定按钮区域 -->
|
||||
<div class="fixed bottom-0 left-0 right-0 bg-white border-t p-4"
|
||||
style="border-color: #fef3c7; box-shadow: 0 -2px 10px rgba(0,0,0,0.05);">
|
||||
<div class="flex items-center gap-3">
|
||||
<!-- 联系客服按钮 -->
|
||||
<van-button round size="small" class="flex-shrink-0"
|
||||
style="background-color: #fef3c7; color: #92400e; border: 1px solid #fde68a;" @click="toService">
|
||||
<van-icon name="service" class="mr-1" />
|
||||
联系客服
|
||||
</van-button>
|
||||
|
||||
<!-- 确认开通按钮 -->
|
||||
<van-button type="primary" block round :disabled="!selectedToLevel" :loading="isSubmitting"
|
||||
style="background: linear-gradient(135deg, #f59e0b, #d97706); border: none;"
|
||||
@click="confirmUpgrade">
|
||||
确认开通
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 支付弹窗 -->
|
||||
<Payment v-model="showPayment" :data="paymentData" :id="paymentId" type="agent_upgrade" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useAgentStore } from '@/stores/agentStore'
|
||||
import { applyUpgrade, getAgentInfo, getLevelPrivilege } from '@/api/agent'
|
||||
import { showSuccessToast, showFailToast, showConfirmDialog } from 'vant'
|
||||
import Payment from '@/components/Payment.vue'
|
||||
import useApiFetch from '@/composables/useApiFetch'
|
||||
|
||||
const router = useRouter()
|
||||
const agentStore = useAgentStore()
|
||||
const { level } = storeToRefs(agentStore)
|
||||
|
||||
// 升级价格与返佣(由后端配置提供)
|
||||
const goldPrice = ref(0)
|
||||
const diamondPrice = ref(0)
|
||||
const goldRebate = ref(0)
|
||||
const diamondRebate = ref(0)
|
||||
|
||||
const showPayment = ref(false)
|
||||
const paymentData = ref({})
|
||||
const paymentId = ref('')
|
||||
const selectedToLevel = ref(0)
|
||||
const isSubmitting = ref(false)
|
||||
const levelPrivileges = ref([])
|
||||
|
||||
// 计算可升级选项
|
||||
const canUpgradeToGold = computed(() => {
|
||||
return level.value === 1 // 普通代理可以升级为黄金
|
||||
})
|
||||
|
||||
const canUpgradeToDiamond = computed(() => {
|
||||
return level.value === 1 || level.value === 2 // 普通或黄金可以升级为钻石
|
||||
})
|
||||
|
||||
// 获取等级名称
|
||||
const getLevelName = (lvl) => {
|
||||
const names = {
|
||||
1: '普通代理',
|
||||
2: '黄金代理',
|
||||
3: '钻石代理'
|
||||
}
|
||||
return names[lvl] || '普通代理'
|
||||
}
|
||||
|
||||
// 获取等级描述
|
||||
const getLevelDesc = (lvl) => {
|
||||
const descs = {
|
||||
1: '基础代理特权',
|
||||
2: '高级代理特权',
|
||||
3: '尊享代理特权'
|
||||
}
|
||||
return descs[lvl] || '基础代理特权'
|
||||
}
|
||||
|
||||
// 获取等级徽章样式类
|
||||
const getLevelBadgeClass = (lvl) => {
|
||||
const classes = {
|
||||
1: 'normal',
|
||||
2: 'gold',
|
||||
3: 'diamond'
|
||||
}
|
||||
return classes[lvl] || 'normal'
|
||||
}
|
||||
|
||||
// 选择升级等级(只设置选中状态)
|
||||
const selectUpgrade = (toLevel) => {
|
||||
if (selectedToLevel.value === toLevel) {
|
||||
// 如果已选中,再次点击取消选中
|
||||
selectedToLevel.value = 0
|
||||
} else {
|
||||
selectedToLevel.value = toLevel
|
||||
}
|
||||
}
|
||||
|
||||
// 计算升级费用和返佣
|
||||
const getUpgradeInfo = (toLevel) => {
|
||||
let upgradeFee = 0
|
||||
let rebateAmount = 0
|
||||
|
||||
if (level.value === 1) {
|
||||
// 普通代理
|
||||
if (toLevel === 2) {
|
||||
upgradeFee = goldPrice.value
|
||||
rebateAmount = goldRebate.value
|
||||
} else if (toLevel === 3) {
|
||||
upgradeFee = diamondPrice.value
|
||||
rebateAmount = diamondRebate.value
|
||||
}
|
||||
} else if (level.value === 2) {
|
||||
// 黄金代理
|
||||
if (toLevel === 3) {
|
||||
upgradeFee = diamondPrice.value
|
||||
rebateAmount = diamondRebate.value
|
||||
}
|
||||
}
|
||||
|
||||
return { upgradeFee, rebateAmount }
|
||||
}
|
||||
|
||||
// 确认开通
|
||||
const confirmUpgrade = async () => {
|
||||
if (!selectedToLevel.value) {
|
||||
showFailToast('请选择要升级的等级')
|
||||
return
|
||||
}
|
||||
|
||||
const { upgradeFee, rebateAmount } = getUpgradeInfo(selectedToLevel.value)
|
||||
|
||||
// 确认升级
|
||||
let confirmed = false
|
||||
try {
|
||||
await showConfirmDialog({
|
||||
title: '确认升级',
|
||||
message: `确定要升级为${getLevelName(selectedToLevel.value)}吗?\n升级费用:¥${upgradeFee.toFixed(2)}`,
|
||||
})
|
||||
confirmed = true
|
||||
} catch {
|
||||
confirmed = false
|
||||
}
|
||||
|
||||
if (!confirmed) return
|
||||
|
||||
isSubmitting.value = true
|
||||
|
||||
// 申请升级
|
||||
try {
|
||||
const { data, error } = await applyUpgrade({
|
||||
to_level: selectedToLevel.value
|
||||
})
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
const upgradeId = data.value.data.upgrade_id
|
||||
const orderNo = data.value.data.order_no
|
||||
|
||||
// 如果返回了订单号,说明已经创建了支付订单,直接跳转到支付结果
|
||||
if (orderNo) {
|
||||
router.push({
|
||||
path: '/payment/result',
|
||||
query: { orderNo }
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 否则打开支付弹窗
|
||||
paymentData.value = {
|
||||
product_name: `升级为${getLevelName(selectedToLevel.value)}`,
|
||||
sell_price: upgradeFee
|
||||
}
|
||||
paymentId.value = String(upgradeId)
|
||||
showPayment.value = true
|
||||
} else {
|
||||
showFailToast(data.value.msg || '申请升级失败')
|
||||
}
|
||||
} else {
|
||||
showFailToast('申请升级失败,请重试')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('申请升级失败:', err)
|
||||
showFailToast('申请升级失败,请重试')
|
||||
} finally {
|
||||
isSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取特权信息
|
||||
const getPrivilegeInfo = (targetLevel) => {
|
||||
return levelPrivileges.value.find(p => p.level === targetLevel)
|
||||
}
|
||||
|
||||
// 数值格式化,统一保留整数金额
|
||||
const formatAmount = (value) => {
|
||||
const num = Number(value || 0)
|
||||
if (Number.isNaN(num)) return '0'
|
||||
return num.toFixed(0)
|
||||
}
|
||||
|
||||
// 加载特权信息
|
||||
const loadPrivilegeInfo = async () => {
|
||||
try {
|
||||
const { data, error } = await getLevelPrivilege()
|
||||
if (data.value && !error.value && data.value.code === 200) {
|
||||
levelPrivileges.value = data.value.data.levels || []
|
||||
goldPrice.value = Number(data.value.data.upgrade_to_gold_fee) || 0
|
||||
diamondPrice.value = Number(data.value.data.upgrade_to_diamond_fee) || 0
|
||||
goldRebate.value = Number(data.value.data.upgrade_to_gold_rebate) || 0
|
||||
diamondRebate.value = Number(data.value.data.upgrade_to_diamond_rebate) || 0
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取特权信息失败:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 联系客服
|
||||
const serviceUrl = import.meta.env.VITE_SERVICE_URL || '';
|
||||
const toService = () => {
|
||||
if (serviceUrl) {
|
||||
window.location.href = serviceUrl;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 加载特权信息
|
||||
loadPrivilegeInfo()
|
||||
|
||||
// 检查是否是代理
|
||||
if (!agentStore.isAgent) {
|
||||
showFailToast('您还不是代理')
|
||||
router.back()
|
||||
return
|
||||
}
|
||||
|
||||
// 如果已是最高等级,提示
|
||||
if (level.value === 3) {
|
||||
// 已经是钻石代理,不需要升级
|
||||
return
|
||||
}
|
||||
|
||||
// 默认选中第一个可升级选项
|
||||
if (level.value === 1) {
|
||||
// 普通代理,默认选中黄金
|
||||
selectedToLevel.value = 2
|
||||
} else if (level.value === 2) {
|
||||
// 黄金代理,默认选中钻石
|
||||
selectedToLevel.value = 3
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.level-badge {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.level-badge.normal {
|
||||
background-color: #6b7280;
|
||||
}
|
||||
|
||||
.level-badge.gold {
|
||||
background-color: #f59e0b;
|
||||
}
|
||||
|
||||
.level-badge.diamond {
|
||||
background-color: #f59e0b;
|
||||
}
|
||||
</style>
|
||||
200
src/views/Authorization.vue
Normal file
200
src/views/Authorization.vue
Normal file
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<!-- 授权书滚动区域 -->
|
||||
<div class=" card flex-1 overflow-y-auto" ref="agreementBox" @scroll="handleScroll">
|
||||
<p class="my-2">{{ companyName }}:</p>
|
||||
<p class="indent-[2em]">
|
||||
<!-- <span class="font-bold"> {{ signature ? userData.name : "____________" }}</span> -->
|
||||
本人________拟向贵司申请大数据分析报告查询业务,贵司需要了解本人相关状况,用于查询大数据分析报告,因此本人同意向贵司提供本人的姓名和手机号等个人信息,并同意贵司向第三方传送上述信息。第三方将使用上述信息核实信息真实情况,查询信用记录,并生成报告。
|
||||
</p>
|
||||
<p class="mt-2 font-bold">授权内容如下:</p>
|
||||
<ol class="list-decimal pl-6">
|
||||
<li>
|
||||
贵司向依法成立的第三方服务商根据本人提交的信息进行核实,并有权通过前述第三方服务机构查询、使用本人的身份信息、设备信息、运营商信息等,查询本人信息(包括但不限于学历、婚姻、资产状况及对信息主体产生负面影响的不良信息),出具相关报告。
|
||||
</li>
|
||||
<li>
|
||||
依法成立的第三方服务商查询或核实、搜集、保存、处理、共享、使用(含合法业务应用)本人相关数据,且不再另行告知本人,但法律、法规、监管政策禁止的除外。
|
||||
</li>
|
||||
<!-- <li>本人授权本业务推广方( )可浏览本人大数据报告。</li> -->
|
||||
<li>
|
||||
本人授权有效期为自授权之日起
|
||||
1个月。本授权为不可撤销授权,但法律法规另有规定的除外。
|
||||
</li>
|
||||
</ol>
|
||||
<p class="mt-2 font-bold">用户声明与承诺:</p>
|
||||
<ul class="list-decimal pl-6">
|
||||
<li>
|
||||
本人在授权签署前,已通过实名认证及动态验证码验证(或其他身份验证手段),确认本授权行为为本人真实意思表示,平台已履行身份验证义务。
|
||||
</li>
|
||||
<li>
|
||||
本人在此声明已充分理解上述授权条款含义,知晓并自愿承担因授权数据使用可能带来的后果,包括但不限于影响个人信用评分、生活行为等。本人确认授权范围内的相关信息由本人提供并真实有效。
|
||||
</li>
|
||||
<li>
|
||||
若用户冒名签署或提供虚假信息,由用户自行承担全部法律责任,平台不承担任何后果。
|
||||
</li>
|
||||
</ul>
|
||||
<p class="mt-2 font-bold">特别提示:</p>
|
||||
<ul class="list-decimal pl-6">
|
||||
<li>
|
||||
本产品所有数据均来自第三方。可能部分数据未公开、数据更新延迟或信息受到限制,贵司不对数据的准确性、真实性、完整性做任何承诺。用户需根据实际情况,结合报告内容自行判断与决策。
|
||||
</li>
|
||||
<li>
|
||||
本产品仅供用户本人查询或被授权查询。除非用户取得合法授权,用户不得利用本产品查询他人信息。用户因未获得合法授权而擅自查询他人信息所产生的任何后果,由用户自行承担责任。
|
||||
</li>
|
||||
<li>
|
||||
本授权书涉及对本人敏感信息(包括但不限于婚姻状态、资产状况等)的查询与使用。本人已充分知晓相关信息的敏感性,并明确同意贵司及其合作方依据授权范围使用相关信息。
|
||||
</li>
|
||||
<li>
|
||||
平台声明:本授权书涉及的信息核实及查询结果由第三方服务商提供,平台不对数据的准确性、完整性、实时性承担责任;用户根据报告所作决策的风险由用户自行承担,平台对此不承担法律责任。
|
||||
</li>
|
||||
<li>
|
||||
本授权书中涉及的数据查询和报告生成由依法成立的第三方服务商提供。若因第三方行为导致数据错误或损失,用户应向第三方主张权利,平台不承担相关责任。
|
||||
</li>
|
||||
</ul>
|
||||
<p class="mt-2 font-bold">附加说明:</p>
|
||||
<ul class="list-decimal pl-6">
|
||||
<li>
|
||||
本人在授权的相关数据将依据法律法规及贵司内部数据管理规范妥善存储,存储期限为法律要求的最短必要时间。超过存储期限或在数据使用目的达成后,贵司将对相关数据进行销毁或匿名化处理。
|
||||
</li>
|
||||
<li>
|
||||
本人有权随时撤回本授权书中的授权,但撤回前的授权行为及其法律后果仍具有法律效力。若需撤回授权,本人可通过贵司官方渠道提交书面申请,贵司将在收到申请后依法停止对本人数据的使用。
|
||||
</li>
|
||||
<li>
|
||||
你通过“真爱查”,自愿支付相应费用,用于购买{{ companyName
|
||||
}}的大数据报告产品。如若对产品内容存在异议,可通过邮箱admin@iieeii.com或APP“联系客服”按钮进行反馈,贵司将在收到异议之日起20日内进行核查和处理,并将结果答复。
|
||||
</li>
|
||||
<li>
|
||||
你向{{ companyName }}的支付方式为:{{ companyName }}及其经官方授权的相关企业的支付宝账户。
|
||||
</li>
|
||||
</ul>
|
||||
<p class="mt-2 font-bold">争议解决机制:</p>
|
||||
<ul>
|
||||
<li>
|
||||
若因本授权书引发争议,双方应友好协商解决;协商不成的,双方同意将争议提交至授权书签署地(海南省)有管辖权的人民法院解决。
|
||||
</li>
|
||||
</ul>
|
||||
<p class="mt-2 font-bold">签署方式的法律效力声明:</p>
|
||||
<ul>
|
||||
<li>
|
||||
本授权书通过用户在线勾选、电子签名或其他网络签署方式完成,与手写签名具有同等法律效力。平台已通过技术手段保存签署过程的完整记录,作为用户真实意思表示的证据。
|
||||
</li>
|
||||
</ul>
|
||||
<p class="mt-2">本授权书于 {{ signTime }}生效。</p>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
const companyName = import.meta.env.VITE_COMPANY_NAME
|
||||
import { useFetch } from "@vueuse/core";
|
||||
import { useWebView } from "@/composables/useWebView";
|
||||
const { postMessage } = useWebView();
|
||||
const userData = ref({
|
||||
name: "",
|
||||
idCard: "",
|
||||
phone: "",
|
||||
});
|
||||
const signature = ref(false);
|
||||
const agreed = ref(false);
|
||||
const id = ref(null);
|
||||
const token = ref(null);
|
||||
const formatDate = (date) => {
|
||||
const options = { year: "numeric", month: "long", day: "numeric" };
|
||||
return new Intl.DateTimeFormat("zh-CN", options).format(date);
|
||||
};
|
||||
const signTime = ref(formatDate(new Date()));
|
||||
|
||||
const canAgree = ref(false); // 同意按钮状态
|
||||
const scrollMessage = ref("请滑动并阅读完整授权书以继续");
|
||||
|
||||
// 滚动事件处理
|
||||
let timeout = null;
|
||||
|
||||
const handleScroll = (event) => {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const element = event.target;
|
||||
if (
|
||||
Math.abs(
|
||||
element.scrollHeight - element.scrollTop - element.clientHeight
|
||||
) <= 50
|
||||
) {
|
||||
canAgree.value = true;
|
||||
scrollMessage.value = "您已阅读完整授权书,可以继续";
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
// 接收来自 App 的参数
|
||||
onMounted(() => {
|
||||
// 如果是通过 query 参数传递数据
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
id.value = urlParams.get("id") || "";
|
||||
token.value = urlParams.get("token") || "";
|
||||
if (id.value && token.value) {
|
||||
const { data, isFetching, error, onFetchResponse } = useFetch(
|
||||
"/api/v1/query/provisional_order/" + id.value,
|
||||
{
|
||||
async beforeFetch({ url, options, cancel }) {
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
Authorization: token.value,
|
||||
};
|
||||
|
||||
return {
|
||||
options,
|
||||
};
|
||||
},
|
||||
}
|
||||
)
|
||||
.get()
|
||||
.json();
|
||||
|
||||
onFetchResponse(() => {
|
||||
console.log("data", data.value);
|
||||
if (data.value.code === 200) {
|
||||
userData.value = {
|
||||
name: data.value.data.name,
|
||||
phone: data.value.data.mobile,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 用户同意
|
||||
const agree = () => {
|
||||
if (!signature.value) {
|
||||
signature.value = true;
|
||||
return;
|
||||
}
|
||||
postMessage({
|
||||
data: {
|
||||
action: "agreed",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 用户取消
|
||||
const cancel = () => {
|
||||
postMessage({
|
||||
data: {
|
||||
action: "cancelled",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener("UniAppJSBridgeReady", handleBridgeReady);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener("UniAppJSBridgeReady", handleBridgeReady);
|
||||
});
|
||||
const handleBridgeReady = () => {
|
||||
postMessage({
|
||||
data: {
|
||||
loaded: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
47
src/views/Complaint.vue
Normal file
47
src/views/Complaint.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div class="support-chat">
|
||||
<h1>投诉中心</h1>
|
||||
<p>如果您有任何问题,请联系我们。</p>
|
||||
<p>请点击右下角按钮联系相关人员</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
onMounted(() => {
|
||||
// 插入客服脚本
|
||||
(function (d, t) {
|
||||
var BASE_URL = "https://service.quannengcha.com";
|
||||
var g = d.createElement(t),
|
||||
s = d.getElementsByTagName(t)[0];
|
||||
g.src = BASE_URL + "/packs/js/sdk.js";
|
||||
g.defer = true;
|
||||
g.async = true;
|
||||
s.parentNode.insertBefore(g, s);
|
||||
g.onload = function () {
|
||||
window.chatwootSDK.run({
|
||||
websiteToken: "XJqwnEWKVNte8iJW8DLroEzd",
|
||||
baseUrl: BASE_URL,
|
||||
});
|
||||
};
|
||||
})(document, "script");
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.support-chat {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
padding-top: 100px;
|
||||
}
|
||||
|
||||
.support-chat h1 {
|
||||
font-size: 28px;
|
||||
color: #005a9e;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.support-chat p {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
83
src/views/Example.vue
Normal file
83
src/views/Example.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<BaseReport :feature="feature" :reportData="reportData" :reportParams="reportParams" :reportName="reportName"
|
||||
:reportDateTime="reportDateTime" :isEmpty="isEmpty" :isDone="isDone" isExample />
|
||||
<div class="w-20 h-20 z-[1000] fixed right-0 top-1/2 -translate-y-1/2">
|
||||
<img src="@/assets/images/example_sy.png" alt="example" class="w-full h-full object-contain" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { aesDecrypt } from "@/utils/crypto";
|
||||
|
||||
const AES_KEY = import.meta.env.VITE_INQUIRE_AES_KEY;
|
||||
|
||||
const feature = ref("");
|
||||
const reportData = ref([]);
|
||||
const reportParams = ref({});
|
||||
const reportName = ref("");
|
||||
const reportDateTime = ref(null);
|
||||
const isEmpty = ref(false);
|
||||
const isDone = ref(false);
|
||||
const active = ref(0);
|
||||
|
||||
|
||||
onBeforeMount(() => {
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
feature.value = query.get("feature");
|
||||
|
||||
if (!feature.value) return;
|
||||
getReport();
|
||||
});
|
||||
|
||||
const getReport = async () => {
|
||||
let queryUrl = `/query/example?feature=${feature.value}`;
|
||||
const { data, error } = await useApiFetch(queryUrl).get().json();
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
if (!AES_KEY) {
|
||||
console.error("缺少解密密钥");
|
||||
isEmpty.value = true;
|
||||
isDone.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
let decryptedData = data.value.data;
|
||||
|
||||
if (typeof decryptedData === "string") {
|
||||
try {
|
||||
const decryptedStr = aesDecrypt(decryptedData, AES_KEY);
|
||||
decryptedData = JSON.parse(decryptedStr);
|
||||
} catch (err) {
|
||||
console.error("报告数据解密失败", err);
|
||||
isEmpty.value = true;
|
||||
isDone.value = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!decryptedData) {
|
||||
isEmpty.value = true;
|
||||
isDone.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const sortedQueryData = Array.isArray(decryptedData.query_data)
|
||||
? [...decryptedData.query_data].sort((a, b) => {
|
||||
return a.feature.sort - b.feature.sort;
|
||||
})
|
||||
: [];
|
||||
reportData.value = sortedQueryData;
|
||||
console.log("reportData", reportData.value);
|
||||
reportParams.value = decryptedData.query_params || {};
|
||||
reportName.value = decryptedData.product_name || "";
|
||||
reportDateTime.value = decryptedData.create_time || null;
|
||||
} else if (data.value.code === 200003) {
|
||||
isEmpty.value = true;
|
||||
}
|
||||
isDone.value = true;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
113
src/views/Help.vue
Normal file
113
src/views/Help.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<div class="help-center">
|
||||
<van-tabs v-model:active="activeTab" sticky :offset-top="46">
|
||||
<van-tab v-for="(category, index) in categories" :key="index" :title="category.title" :name="category.name">
|
||||
<van-cell-group inset class="help-list" size="large">
|
||||
<van-cell v-for="item in category.items" :key="item.id" :title="item.title" is-link
|
||||
@click="goToDetail(item.id, item.type)" class="help-item">
|
||||
<template #label>
|
||||
<van-tag v-if="item.type === 'guide'" type="primary" size="small"
|
||||
class="guide-tag" style="background-color: var(--van-theme-primary); color: white;">引导指南</van-tag>
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const activeTab = ref('report')
|
||||
|
||||
const categories = [
|
||||
{
|
||||
title: '推广报告',
|
||||
name: 'report',
|
||||
items: [
|
||||
{ id: 'report_guide', title: '直推报告页面引导', type: 'guide' },
|
||||
{ id: 'invite_guide', title: '邀请下级页面引导', type: 'guide' },
|
||||
{ id: 'direct_earnings', title: '如何成为真爱查代理' },
|
||||
{ id: 'report_push', title: '如何推广报告' },
|
||||
{ id: 'report_calculation', title: '推广报告的收益是如何计算的?' },
|
||||
{ id: 'report_cost', title: '推广报告的成本是如何计算的?' },
|
||||
{ id: 'report_efficiency', title: '报告推广效率飙升指南' },
|
||||
{ id: 'report_secret', title: '报告推广秘籍大公开' },
|
||||
{ id: 'report_types', title: '真爱查有哪些大数据报告类型' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '邀请下级',
|
||||
name: 'invite',
|
||||
items: [
|
||||
{ id: 'invite_earnings', title: '邀请下级赚取收益' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '其他',
|
||||
name: 'other',
|
||||
items: []
|
||||
}
|
||||
]
|
||||
|
||||
const goToDetail = (id, type) => {
|
||||
if (type === 'guide') {
|
||||
router.push({
|
||||
path: '/help/guide',
|
||||
query: { id }
|
||||
})
|
||||
} else {
|
||||
router.push({
|
||||
path: '/help/detail',
|
||||
query: { id }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.help-center {
|
||||
min-height: 100vh;
|
||||
background-color: #f7f8fa;
|
||||
|
||||
.help-list {
|
||||
margin-top: 12px;
|
||||
|
||||
.guide-tag {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.help-item) {
|
||||
.van-cell__title {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.help-detail {
|
||||
padding: 20px;
|
||||
|
||||
&-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
white-space: pre-line;
|
||||
line-height: 1.6;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
78
src/views/HelpDetail.vue
Normal file
78
src/views/HelpDetail.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div class="help-detail">
|
||||
<h2>{{ currentHelp.title }}</h2>
|
||||
<template v-if="Array.isArray(currentHelp.images)">
|
||||
<img v-for="(image, index) in currentHelp.images" :key="index" :src="image" :alt="currentHelp.title"
|
||||
class="help-image">
|
||||
</template>
|
||||
<img v-else-if="currentHelp.image" :src="currentHelp.image" :alt="currentHelp.title" class="help-image">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const currentHelp = ref({
|
||||
title: '',
|
||||
image: '',
|
||||
images: null
|
||||
})
|
||||
|
||||
// 图片路径映射
|
||||
const imageMap = {
|
||||
report_calculation: '/image/help/report-calculation.jpg',
|
||||
report_efficiency: '/image/help/report-efficiency.jpg',
|
||||
report_cost: '/image/help/report-cost.jpg',
|
||||
report_types: '/image/help/report-types.jpg',
|
||||
report_push: '/image/help/report-push.jpg',
|
||||
report_secret: ['/image/help/report-secret-1.jpg', '/image/help/report-secret-2.jpg'],
|
||||
invite_earnings: '/image/help/invite-earnings.jpg',
|
||||
direct_earnings: '/image/help/direct-earnings.jpg'
|
||||
}
|
||||
|
||||
// 标题映射
|
||||
const titleMap = {
|
||||
report_calculation: '推广报告的收益是如何计算的?',
|
||||
report_efficiency: '报告推广效率飙升指南',
|
||||
report_cost: '推广报告的成本是如何计算的?',
|
||||
report_types: '真爱查有哪些大数据报告类型',
|
||||
report_push: '如何推广报告',
|
||||
report_secret: '报告推广秘籍大公开',
|
||||
invite_earnings: '如何邀请下级成为代理',
|
||||
direct_earnings: '如何成为真爱查代理'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const id = route.query.id
|
||||
if (id && titleMap[id]) {
|
||||
currentHelp.value = {
|
||||
title: titleMap[id],
|
||||
image: Array.isArray(imageMap[id]) ? null : imageMap[id],
|
||||
images: Array.isArray(imageMap[id]) ? imageMap[id] : null
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.help-detail {
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
|
||||
h2 {
|
||||
margin: 0 0 20px;
|
||||
font-size: 22px;
|
||||
color: #323233;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.help-image {
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
103
src/views/HelpGuide.vue
Normal file
103
src/views/HelpGuide.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div class="help-guide">
|
||||
<div class="guide-content" @click="handleImageClick">
|
||||
<img :src="currentStep.image" :alt="currentStep.title" class="guide-image">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const currentStepIndex = ref(0)
|
||||
|
||||
// 引导步骤数据
|
||||
const guideSteps = {
|
||||
report_guide: [
|
||||
{
|
||||
title: '第一步:进入直推报告页面',
|
||||
image: '/image/help/report-step1.jpg'
|
||||
},
|
||||
{
|
||||
title: '第二步:选择报告类型',
|
||||
image: '/image/help/report-step2.jpg'
|
||||
},
|
||||
{
|
||||
title: '第三步:填写推广信息',
|
||||
image: '/image/help/report-step3.jpg'
|
||||
},
|
||||
{
|
||||
title: '第四步:完成推广',
|
||||
image: '/image/help/report-step4.jpg'
|
||||
}
|
||||
],
|
||||
invite_guide: [
|
||||
{
|
||||
title: '第一步:进入邀请页面',
|
||||
image: '/image/help/invite-step1.jpg'
|
||||
},
|
||||
{
|
||||
title: '第二步:获取邀请码',
|
||||
image: '/image/help/invite-step2.jpg'
|
||||
},
|
||||
{
|
||||
title: '第三步:分享邀请链接',
|
||||
image: '/image/help/invite-step3.jpg'
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const currentGuide = computed(() => {
|
||||
const id = route.query.id
|
||||
return guideSteps[id] || []
|
||||
})
|
||||
|
||||
const totalSteps = computed(() => currentGuide.value.length)
|
||||
|
||||
const currentStep = computed(() => currentGuide.value[currentStepIndex.value])
|
||||
|
||||
const handleImageClick = () => {
|
||||
if (currentStepIndex.value < totalSteps.value - 1) {
|
||||
currentStepIndex.value++
|
||||
} else {
|
||||
// 最后一步,返回列表页
|
||||
router.back()
|
||||
}
|
||||
}
|
||||
|
||||
const onClickLeft = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const id = route.query.id
|
||||
if (!guideSteps[id]) {
|
||||
router.back()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.help-guide {
|
||||
background-color: #666666;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 46px);
|
||||
|
||||
.guide-content {
|
||||
flex: 1;
|
||||
height: calc(100vh - 46px);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
.guide-image {
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
183
src/views/HistoryQuery.vue
Normal file
183
src/views/HistoryQuery.vue
Normal file
@@ -0,0 +1,183 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useUserStore } from '@/stores/userStore'
|
||||
import { useAppStore } from '@/stores/appStore'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
import BindPhoneOnlyDialog from '@/components/BindPhoneOnlyDialog.vue'
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const appStore = useAppStore()
|
||||
const dialogStore = useDialogStore()
|
||||
const { isLoggedIn, mobile } = storeToRefs(userStore)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
const reportList = ref([])
|
||||
const num = ref(0)
|
||||
const max = ref(60)
|
||||
const loading = ref(false)
|
||||
const finished = ref(false)
|
||||
const showBindNotice = computed(() => isLoggedIn.value && !mobile.value && reportList.value.length > 0)
|
||||
const hasNoRecords = computed(() => reportList.value.length === 0)
|
||||
// 初始加载数据
|
||||
async function fetchData() {
|
||||
if (loading.value || finished.value) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const { data, error } = await useApiFetch(`query/list?page=${page.value}&page_size=${pageSize.value}`)
|
||||
.get()
|
||||
.json()
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
total.value = data.value.data.total
|
||||
if (data.value.data.list && data.value.data.list.length > 0) {
|
||||
reportList.value.push(...data.value.data.list)
|
||||
page.value += 1
|
||||
}
|
||||
if (reportList.value.length >= total.value) {
|
||||
finished.value = true
|
||||
}
|
||||
} else {
|
||||
// 接口返回错误,停止翻页
|
||||
finished.value = true
|
||||
console.error('获取查询列表失败:', data.value.msg || '未知错误')
|
||||
}
|
||||
} else {
|
||||
// 请求失败或返回错误,停止翻页
|
||||
finished.value = true
|
||||
console.error('获取查询列表失败:', error.value || '请求失败')
|
||||
}
|
||||
} catch (err) {
|
||||
// 捕获异常,停止翻页
|
||||
finished.value = true
|
||||
console.error('获取查询列表失败:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 初始加载
|
||||
onMounted(async () => {
|
||||
fetchData()
|
||||
})
|
||||
|
||||
// 下拉触底加载更多
|
||||
const onLoad = () => {
|
||||
if (!finished.value) {
|
||||
console.log("finished", finished.value)
|
||||
if (num.value >= max.value) {
|
||||
finished.value = true
|
||||
} else {
|
||||
fetchData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toDetail(item) {
|
||||
if (item.query_state != "success") return
|
||||
router.push({ path: '/report', query: { orderId: item.order_id } });
|
||||
}
|
||||
|
||||
function toLogin() {
|
||||
const redirect = encodeURIComponent('/historyQuery')
|
||||
router.push({ path: '/login', query: { redirect, from: 'promotionInquire' } })
|
||||
}
|
||||
|
||||
function handleBindSuccess() {
|
||||
reportList.value = []
|
||||
page.value = 1
|
||||
finished.value = false
|
||||
loading.value = false
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 状态文字映射
|
||||
function stateText(state) {
|
||||
switch (state) {
|
||||
case 'pending':
|
||||
return '查询中'
|
||||
case 'success':
|
||||
return '查询成功'
|
||||
case 'failed':
|
||||
return '查询失败'
|
||||
case 'refunded':
|
||||
return '已退款'
|
||||
default:
|
||||
return '未知状态'
|
||||
}
|
||||
}
|
||||
|
||||
// 状态颜色映射
|
||||
function statusClass(state) {
|
||||
switch (state) {
|
||||
case 'pending':
|
||||
return 'status-pending'
|
||||
case 'success':
|
||||
return 'status-success'
|
||||
case 'failed':
|
||||
return 'status-failed'
|
||||
case 'refunded':
|
||||
return 'status-refunded'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<BindPhoneOnlyDialog @bind-success="handleBindSuccess" />
|
||||
<div v-if="showBindNotice"
|
||||
class="bg-yellow-50 border border-yellow-200 text-yellow-700 rounded-lg p-3 flex items-center justify-between">
|
||||
<div class="text-sm">为防止报告丢失,请绑定手机号</div>
|
||||
<button class="px-3 py-1 text-sm font-medium bg-blue-500 text-white rounded"
|
||||
@click="dialogStore.openBindPhone">绑定手机号</button>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">为保障用户隐私及数据安全,报告保留{{ appStore.queryRetentionDays || 30 }}天,过期自动清理</div>
|
||||
<div v-if="hasNoRecords"
|
||||
class="bg-white rounded-lg shadow-sm p-6 flex flex-col items-center justify-center gap-3">
|
||||
<div class="text-gray-600 text-sm">暂无历史报告</div>
|
||||
<button v-if="!isLoggedIn || !mobile" class="px-4 py-2 bg-blue-500 text-white rounded"
|
||||
@click="toLogin">登录</button>
|
||||
</div>
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
|
||||
<div v-for="item in reportList" :key="item.id" @click="toDetail(item)"
|
||||
class="bg-white rounded-lg shadow-sm p-4 mb-4 relative cursor-pointer">
|
||||
<div class="flex flex-col">
|
||||
<div class="text-xl text-black mb-1">{{ item.product_name }}</div>
|
||||
<div class="text-sm text-[#999999]">{{ item.create_time }}</div>
|
||||
</div>
|
||||
<div class="absolute top-0 right-0 rounded-bl-lg rounded-tr-lg px-2 py-[1px] text-white text-sm font-medium"
|
||||
:class="[statusClass(item.query_state)]">
|
||||
{{ stateText(item.query_state) }}
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.status-pending {
|
||||
background-color: #1976d2;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-success {
|
||||
background-color: #1FBE5D;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-failed {
|
||||
background-color: #EB3C3C;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-refunded {
|
||||
background-color: #999999;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
107
src/views/Inquire.vue
Normal file
107
src/views/Inquire.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useAgentStore } from "@/stores/agentStore";
|
||||
import { useUserStore } from "@/stores/userStore";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { showToast } from "vant";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const agentStore = useAgentStore();
|
||||
const userStore = useUserStore();
|
||||
const { isAgent } = storeToRefs(agentStore);
|
||||
const { mobile, isLoggedIn } = storeToRefs(userStore);
|
||||
|
||||
const feature = ref(route.params.feature);
|
||||
|
||||
// 获取产品信息
|
||||
const featureData = ref({});
|
||||
|
||||
// 检查是否可以查询:已登录且已绑定手机号
|
||||
const canQuery = computed(() => {
|
||||
return isLoggedIn.value && mobile.value && mobile.value.trim() !== '';
|
||||
});
|
||||
|
||||
// 检查登录状态和手机号绑定
|
||||
onMounted(async () => {
|
||||
// 检查支付回调
|
||||
isFinishPayment();
|
||||
|
||||
// 检查是否已登录
|
||||
const token = localStorage.getItem("token");
|
||||
if (!token) {
|
||||
showToast({ message: "请先登录才能使用查询功能" });
|
||||
router.replace("/login");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取用户信息(包括手机号)
|
||||
try {
|
||||
await userStore.fetchUserInfo();
|
||||
} catch (error) {
|
||||
console.error("获取用户信息失败:", error);
|
||||
showToast({ message: "获取用户信息失败,请重新登录" });
|
||||
router.replace("/login");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否已绑定手机号
|
||||
if (!mobile.value || mobile.value.trim() === '') {
|
||||
showToast({ message: "请先绑定手机号才能使用查询功能" });
|
||||
router.replace("/me");
|
||||
return;
|
||||
}
|
||||
|
||||
// 已登录且已绑定手机号,可以查询
|
||||
await getProduct();
|
||||
});
|
||||
|
||||
function isFinishPayment() {
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
let orderNo = query.get("out_trade_no");
|
||||
if (orderNo) {
|
||||
router.push({ path: "/report", query: { orderNo } });
|
||||
}
|
||||
}
|
||||
|
||||
async function getProduct() {
|
||||
const { data, error } = await useApiFetch(`/product/en/${feature.value}`)
|
||||
.get()
|
||||
.json();
|
||||
|
||||
if (data.value) {
|
||||
featureData.value = data.value.data;
|
||||
// 确保 FLXG0V4B 排在首位
|
||||
if (
|
||||
featureData.value.features &&
|
||||
featureData.value.features.length > 0
|
||||
) {
|
||||
featureData.value.features.sort((a, b) => {
|
||||
if (a.api_id === "FLXG0V4B") return -1;
|
||||
if (b.api_id === "FLXG0V4B") return 1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 未登录或未绑定手机号,提示 -->
|
||||
<div v-if="!canQuery" class="min-h-screen flex items-center justify-center p-6">
|
||||
<div class="text-center">
|
||||
<div class="text-lg font-bold mb-4">无法使用查询功能</div>
|
||||
<div class="text-gray-600 mb-6">
|
||||
<span v-if="!isLoggedIn">请先登录</span>
|
||||
<span v-else>请先绑定手机号</span>
|
||||
</div>
|
||||
<button @click="router.push(isLoggedIn ? '/me' : '/login')"
|
||||
class="px-6 py-3 bg-primary text-white rounded-lg">
|
||||
{{ isLoggedIn ? '去绑定手机号' : '去登录' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 已登录且已绑定手机号,可以使用查询功能 -->
|
||||
<InquireForm v-else :type="'normal'" :feature="feature" :feature-data="featureData" />
|
||||
</template>
|
||||
398
src/views/Invitation.vue
Normal file
398
src/views/Invitation.vue
Normal file
@@ -0,0 +1,398 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gradient-to-b from-blue-50 to-white pb-20">
|
||||
<!-- 页面标题 -->
|
||||
<div class="px-4 pt-6 pb-4">
|
||||
<h1 class="text-2xl font-bold text-gray-800">邀请下级代理</h1>
|
||||
<p class="text-sm text-gray-500 mt-2">选择邀请码生成二维码或复制链接</p>
|
||||
</div>
|
||||
|
||||
<div class="px-4 space-y-4">
|
||||
<!-- 我的邀请码列表 -->
|
||||
<div class="bg-white rounded-xl shadow-lg p-5">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-800">我的邀请码</h3>
|
||||
<button @click="showGenerateCodeDialog = true"
|
||||
class="px-4 py-2 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-lg text-sm font-medium shadow-md active:from-blue-600 active:to-blue-700">
|
||||
生成邀请码
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="loadingCodeList" class="text-center py-8 text-gray-500">
|
||||
加载中...
|
||||
</div>
|
||||
<div v-else-if="codeList.length === 0" class="text-center py-8 text-gray-500">
|
||||
<p>暂无邀请码</p>
|
||||
<button @click="showGenerateCodeDialog = true"
|
||||
class="mt-4 px-4 py-2 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-lg text-sm font-medium shadow-md active:from-blue-600 active:to-blue-700">
|
||||
立即生成
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="space-y-3 max-h-[600px] overflow-y-auto">
|
||||
<div v-for="item in codeList" :key="item.id" :class="[
|
||||
'rounded-xl p-4 border-2 transition-all cursor-pointer shadow-sm',
|
||||
selectedCodeId === item.id
|
||||
? 'border-blue-500 bg-blue-50 shadow-md'
|
||||
: 'border-gray-200 bg-white hover:border-gray-300 hover:shadow'
|
||||
]" @click="selectCode(item)">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<div class="text-lg font-mono font-bold text-blue-600 break-all">{{ item.code }}
|
||||
</div>
|
||||
<span v-if="isExpired(item)"
|
||||
class="text-xs px-2.5 py-1 rounded-lg bg-red-100 text-red-700 font-medium whitespace-nowrap">
|
||||
已过期
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 space-y-0.5">
|
||||
<div v-if="item.remark">备注:{{ item.remark }}</div>
|
||||
<div>创建时间:{{ item.create_time }}</div>
|
||||
<div v-if="item.expire_time">过期时间:{{ item.expire_time }}</div>
|
||||
<div v-if="item.used_time">使用时间:{{ item.used_time }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div v-if="selectedCodeId === item.id" class="flex-shrink-0">
|
||||
<div
|
||||
class="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center shadow-sm">
|
||||
<span class="text-white text-xs font-bold">✓</span>
|
||||
</div>
|
||||
</div>
|
||||
<button v-if="selectedCodeId === item.id && item.status === 0"
|
||||
@click.stop="handleDeleteCode(item.id, item.code)"
|
||||
class="px-3 py-1.5 bg-red-500 text-white rounded-lg text-xs font-medium active:bg-red-600 whitespace-nowrap shadow-sm">
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="selectedCodeId === item.id && item.status === 0" class="mt-3 flex gap-2">
|
||||
<button @click.stop="copyInviteCode(item.code)"
|
||||
class="flex-1 px-4 py-2.5 bg-blue-500 text-white rounded-lg text-sm font-medium active:bg-blue-600 shadow-sm">
|
||||
复制邀请码
|
||||
</button>
|
||||
<button @click.stop="handleGenerateLink(item.code)"
|
||||
class="flex-1 px-4 py-2.5 bg-green-500 text-white rounded-lg text-sm font-medium active:bg-green-600 shadow-sm">
|
||||
生成链接
|
||||
</button>
|
||||
<button @click.stop="handleGenerateQRCode(item.code)"
|
||||
class="flex-1 px-4 py-2.5 bg-orange-500 text-white rounded-lg text-sm font-medium active:bg-orange-600 shadow-sm">
|
||||
生成二维码
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div v-if="codeListTotal > pageSize" class="mt-4 flex items-center justify-between pt-4 border-t">
|
||||
<button @click="loadCodeList(currentPage - 1)" :disabled="currentPage <= 1"
|
||||
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium active:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed">
|
||||
上一页
|
||||
</button>
|
||||
<span class="text-sm text-gray-600 font-medium">
|
||||
第 {{ currentPage }} / {{ Math.ceil(codeListTotal / pageSize) }} 页
|
||||
</span>
|
||||
<button @click="loadCodeList(currentPage + 1)"
|
||||
:disabled="currentPage >= Math.ceil(codeListTotal / pageSize)"
|
||||
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium active:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed">
|
||||
下一页
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 生成的邀请链接显示区域 -->
|
||||
<!-- <div v-if="showInviteLink && inviteLink" class="bg-white rounded-xl shadow-lg p-5">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-4">邀请链接</h3>
|
||||
<div class="bg-gray-50 rounded-lg p-3 border border-gray-200 mb-4">
|
||||
<div class="text-sm text-blue-600 break-all font-mono">{{ inviteLink }}</div>
|
||||
</div>
|
||||
<button @click="copyInviteLink"
|
||||
class="w-full py-3 bg-gradient-to-r from-orange-500 to-orange-600 text-white rounded-lg text-sm font-medium shadow-md active:from-orange-600 active:to-orange-700">
|
||||
复制链接
|
||||
</button>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<!-- 生成邀请码弹窗 -->
|
||||
<van-dialog v-model:show="showGenerateCodeDialog" title="生成邀请码" show-cancel-button @confirm="handleGenerateCode"
|
||||
@cancel="showGenerateCodeDialog = false">
|
||||
<div class="p-4 space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">生成数量</label>
|
||||
<van-stepper v-model="generateCount" :min="1" :max="100" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">过期天数(0表示不过期)</label>
|
||||
<van-stepper v-model="expireDays" :min="0" :max="365" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">备注(可选)</label>
|
||||
<van-field v-model="remark" placeholder="请输入备注" maxlength="50" />
|
||||
</div>
|
||||
</div>
|
||||
</van-dialog>
|
||||
|
||||
<!-- 二维码弹窗 -->
|
||||
<QRcode v-model:show="showQRcode" mode="invitation" :invite-link="inviteLink" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { watch, onMounted } from "vue";
|
||||
import { generateInviteCode, getInviteCodeList, deleteInviteCode, getInviteLink } from "@/api/agent";
|
||||
import { showToast, showConfirmDialog } from "vant";
|
||||
import QRcode from "@/components/QRcode.vue";
|
||||
|
||||
const showQRcode = ref(false);
|
||||
const inviteLink = ref("");
|
||||
const showInviteLink = ref(false); // 控制是否显示邀请链接区域
|
||||
const generatedCodes = ref([]);
|
||||
const showGenerateCodeDialog = ref(false);
|
||||
const generateCount = ref(1);
|
||||
const expireDays = ref(0);
|
||||
const remark = ref("");
|
||||
|
||||
// 邀请码列表相关
|
||||
const codeList = ref([]);
|
||||
const loadingCodeList = ref(false);
|
||||
const codeListTotal = ref(0);
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(20);
|
||||
const selectedCodeId = ref(null); // 当前选中的邀请码ID
|
||||
|
||||
// 页面加载时自动加载邀请码列表
|
||||
onMounted(() => {
|
||||
loadCodeList(1);
|
||||
});
|
||||
|
||||
// 选择邀请码
|
||||
const selectCode = (item) => {
|
||||
selectedCodeId.value = item.id;
|
||||
// 清除之前生成的链接和显示状态
|
||||
inviteLink.value = "";
|
||||
showInviteLink.value = false;
|
||||
};
|
||||
|
||||
// 生成邀请链接
|
||||
const handleGenerateLink = async (code) => {
|
||||
if (!code) {
|
||||
showToast({ message: "请先选择邀请码" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建目标路径:注册页面路径
|
||||
const targetPath = `/register?invite_code=${code}`;
|
||||
|
||||
// 调用后端API生成短链
|
||||
const { data, error } = await getInviteLink({
|
||||
invite_code: code,
|
||||
target_path: targetPath
|
||||
});
|
||||
|
||||
if (data.value && !error.value && data.value.code === 200) {
|
||||
inviteLink.value = data.value.data.invite_link || "";
|
||||
showInviteLink.value = true; // 显示邀请链接区域
|
||||
|
||||
// 自动复制到剪贴板
|
||||
copyToClipboard(inviteLink.value, "邀请链接已生成并复制到剪贴板");
|
||||
} else {
|
||||
showToast({ message: data.value?.msg || "生成邀请链接失败,请重试" });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("生成邀请链接失败:", err);
|
||||
showToast({ message: "生成邀请链接失败,请重试" });
|
||||
}
|
||||
};
|
||||
|
||||
// 生成二维码
|
||||
const handleGenerateQRCode = async (code) => {
|
||||
if (!code) {
|
||||
showToast({ message: "请先选择邀请码" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建目标路径:注册页面路径
|
||||
const targetPath = `/register?invite_code=${code}`;
|
||||
|
||||
// 调用后端API生成短链
|
||||
const { data, error } = await getInviteLink({
|
||||
invite_code: code,
|
||||
target_path: targetPath
|
||||
});
|
||||
|
||||
if (data.value && !error.value && data.value.code === 200) {
|
||||
inviteLink.value = data.value.data.invite_link || "";
|
||||
// 不设置 showInviteLink,这样不会显示邀请链接区域,只用于生成二维码
|
||||
|
||||
// 直接显示二维码弹窗
|
||||
showQRcode.value = true;
|
||||
} else {
|
||||
showToast({ message: data.value?.msg || "生成邀请链接失败,请重试" });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("生成邀请链接失败:", err);
|
||||
showToast({ message: "生成邀请链接失败,请重试" });
|
||||
}
|
||||
};
|
||||
|
||||
// 生成邀请码
|
||||
const handleGenerateCode = async () => {
|
||||
try {
|
||||
const { data, error } = await generateInviteCode({
|
||||
count: generateCount.value,
|
||||
expire_days: expireDays.value || 0,
|
||||
remark: remark.value || ""
|
||||
});
|
||||
|
||||
if (data.value && !error.value && data.value.code === 200) {
|
||||
generatedCodes.value = data.value.data.codes || [];
|
||||
showGenerateCodeDialog.value = false;
|
||||
showToast({ message: `成功生成 ${generatedCodes.value.length} 个邀请码` });
|
||||
// 重置表单
|
||||
generateCount.value = 1;
|
||||
expireDays.value = 0;
|
||||
remark.value = "";
|
||||
// 刷新邀请码列表(会自动选择第一个)
|
||||
await loadCodeList(1);
|
||||
} else {
|
||||
showToast({ message: data.value?.msg || "生成邀请码失败,请重试" });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to generate invite code", error);
|
||||
showToast({ message: "生成邀请码失败,请重试" });
|
||||
}
|
||||
};
|
||||
|
||||
// 判断邀请码是否过期
|
||||
const isExpired = (item) => {
|
||||
if (!item.expire_time) {
|
||||
// 没有过期时间,表示不过期
|
||||
return false;
|
||||
}
|
||||
// 将过期时间字符串转换为日期对象进行比较
|
||||
const expireDate = new Date(item.expire_time);
|
||||
const now = new Date();
|
||||
return expireDate < now;
|
||||
};
|
||||
|
||||
// 加载邀请码列表
|
||||
const loadCodeList = async (page = 1) => {
|
||||
loadingCodeList.value = true;
|
||||
try {
|
||||
const { data, error } = await getInviteCodeList({
|
||||
page: page,
|
||||
page_size: pageSize.value
|
||||
});
|
||||
|
||||
if (data.value && !error.value && data.value.code === 200) {
|
||||
codeList.value = data.value.data.list || [];
|
||||
codeListTotal.value = data.value.data.total || 0;
|
||||
currentPage.value = page;
|
||||
|
||||
// 如果有邀请码,默认选择第一个
|
||||
if (codeList.value.length > 0 && !selectedCodeId.value) {
|
||||
selectedCodeId.value = codeList.value[0].id;
|
||||
}
|
||||
} else {
|
||||
showToast({ message: data.value?.msg || "获取邀请码列表失败" });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load invite code list", error);
|
||||
showToast({ message: "获取邀请码列表失败" });
|
||||
} finally {
|
||||
loadingCodeList.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 删除邀请码
|
||||
const handleDeleteCode = async (id, code) => {
|
||||
try {
|
||||
await showConfirmDialog({
|
||||
title: "确认删除",
|
||||
message: `确定要删除邀请码 ${code} 吗?此操作不可恢复。`
|
||||
});
|
||||
|
||||
const { data, error } = await deleteInviteCode({ id });
|
||||
|
||||
if (data.value && !error.value && data.value.code === 200) {
|
||||
showToast({ message: "删除成功" });
|
||||
// 如果删除的是当前选中的,清除选中状态
|
||||
if (selectedCodeId.value === id) {
|
||||
selectedCodeId.value = null;
|
||||
inviteLink.value = "";
|
||||
}
|
||||
// 刷新列表
|
||||
await loadCodeList(currentPage.value);
|
||||
// 如果还有邀请码,自动选择第一个
|
||||
if (codeList.value.length > 0 && !selectedCodeId.value) {
|
||||
selectedCodeId.value = codeList.value[0].id;
|
||||
}
|
||||
} else {
|
||||
showToast({ message: data.value?.msg || "删除失败,请重试" });
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== "cancel") {
|
||||
console.error("Failed to delete invite code", error);
|
||||
showToast({ message: "删除失败,请重试" });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 复制邀请码
|
||||
const copyInviteCode = (code) => {
|
||||
if (!code) {
|
||||
showToast({ message: "邀请码不能为空" });
|
||||
return;
|
||||
}
|
||||
copyToClipboard(code);
|
||||
};
|
||||
|
||||
// 复制邀请链接
|
||||
const copyInviteLink = () => {
|
||||
if (!inviteLink.value) {
|
||||
showToast({ message: "请先生成邀请链接" });
|
||||
return;
|
||||
}
|
||||
copyToClipboard(inviteLink.value);
|
||||
};
|
||||
|
||||
// 复制到剪贴板
|
||||
const copyToClipboard = (text, customMessage = "已复制到剪贴板") => {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard
|
||||
.writeText(text)
|
||||
.then(() => {
|
||||
showToast({ message: customMessage });
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("复制失败:", err);
|
||||
fallbackCopy(text, customMessage);
|
||||
});
|
||||
} else {
|
||||
fallbackCopy(text, customMessage);
|
||||
}
|
||||
};
|
||||
|
||||
// 降级复制方法
|
||||
const fallbackCopy = (text, customMessage = "已复制到剪贴板") => {
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = text;
|
||||
textArea.style.position = "fixed";
|
||||
textArea.style.opacity = "0";
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
try {
|
||||
document.execCommand("copy");
|
||||
showToast({ message: customMessage });
|
||||
} catch (err) {
|
||||
console.error("复制失败:", err);
|
||||
showToast({ message: "复制失败,请手动复制" });
|
||||
} finally {
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 可以添加一些自定义样式</style>
|
||||
127
src/views/InvitationAgentApply.vue
Normal file
127
src/views/InvitationAgentApply.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-[#DBE0FF]">
|
||||
<img src="@/assets/images/invitation_agent_apply.png" alt="邀请代理申请" />
|
||||
<!-- 统一状态处理容器 -->
|
||||
<div class="flex flex-col items-center justify-centerx">
|
||||
<!-- 已是代理状态 -->
|
||||
<div v-if="isAgent" class="text-center">
|
||||
<span class="text-xs text-gray-500">您已是代理,欢迎回来!</span>
|
||||
<div class="bg-green-100 p-1 rounded-3xl shadow-xl mt-1" @click="goToHome">
|
||||
<div
|
||||
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">
|
||||
进入应用首页
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 未成为代理状态(包含邀请状态) -->
|
||||
<div v-else class="text-center">
|
||||
<span class="text-xs text-gray-500">{{
|
||||
isSelf ? "立即申请成为代理人" : "邀您注册代理人"
|
||||
}}</span>
|
||||
<div class="bg-gray-100 p-1 rounded-3xl shadow-xl mt-1" @click="agentApply">
|
||||
<div
|
||||
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">
|
||||
立即成为代理方
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AgentApplicationForm v-model:show="showApplyPopup" @submit="submitApplication" @close="showApplyPopup = false" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { aesDecrypt } from "@/utils/crypto";
|
||||
const showApplyPopup = ref(false);
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
import { storeToRefs } from "pinia";
|
||||
import { ref } from "vue";
|
||||
const store = useAgentStore();
|
||||
const userStore = useUserStore();
|
||||
const { userName } = storeToRefs(userStore);
|
||||
const { isAgent } = storeToRefs(store);
|
||||
const ancestor = ref("");
|
||||
const isSelf = ref(false);
|
||||
const agentApply = () => {
|
||||
showApplyPopup.value = true;
|
||||
};
|
||||
|
||||
// 跳转到首页
|
||||
const goToHome = () => {
|
||||
router.replace("/promote");
|
||||
};
|
||||
onBeforeMount(async () => {
|
||||
// 如果是通过邀请链接访问(旧的linkIdentifier格式),提取信息
|
||||
if (route.params.linkIdentifier && route.name !== "invitationAgentApplySelf") {
|
||||
try {
|
||||
const linkIdentifier = route.params.linkIdentifier;
|
||||
const decryptDataStr = aesDecrypt(
|
||||
decodeURIComponent(linkIdentifier),
|
||||
"8e3e7a2f60edb49221e953b9c029ed10"
|
||||
);
|
||||
const decryptData = JSON.parse(decryptDataStr);
|
||||
// 旧格式可能包含agentID和mobile,但新系统使用邀请码
|
||||
// 这里可以保留兼容,但主要功能是通过邀请码
|
||||
} catch (error) {
|
||||
console.error("解析链接标识符失败", error);
|
||||
}
|
||||
} else {
|
||||
isSelf.value = true;
|
||||
}
|
||||
|
||||
// 检查是否已登录并获取代理状态
|
||||
const token = localStorage.getItem("token");
|
||||
if (token) {
|
||||
await store.fetchAgentStatus();
|
||||
}
|
||||
});
|
||||
import { applyForAgent, registerByInviteCode } from "@/api/agent";
|
||||
|
||||
const submitApplication = async (formData) => {
|
||||
const { region, mobile, wechat_id, code, referrer } = formData;
|
||||
|
||||
// 根据是否已登录选择不同的API
|
||||
const isLoggedIn = !!localStorage.getItem("token");
|
||||
const apiCall = isLoggedIn ? applyForAgent : registerByInviteCode;
|
||||
|
||||
let postData = {
|
||||
region,
|
||||
mobile,
|
||||
wechat_id,
|
||||
code,
|
||||
referrer,
|
||||
};
|
||||
|
||||
const { data, error } = await apiCall(postData);
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
showApplyPopup.value = false;
|
||||
showToast({ message: "注册成功,您已成为代理!" });
|
||||
// 更新token和状态
|
||||
if (data.value.data.accessToken) {
|
||||
localStorage.setItem("token", data.value.data.accessToken);
|
||||
localStorage.setItem(
|
||||
"refreshAfter",
|
||||
data.value.data.refreshAfter
|
||||
);
|
||||
localStorage.setItem(
|
||||
"accessExpire",
|
||||
data.value.data.accessExpire
|
||||
);
|
||||
// 重新获取代理状态
|
||||
await store.fetchAgentStatus();
|
||||
await userStore.fetchUserInfo();
|
||||
// 跳转到代理主页
|
||||
router.replace("/agent");
|
||||
}
|
||||
} else {
|
||||
console.log("申请失败", data.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
82
src/views/InvitationPage.vue
Normal file
82
src/views/InvitationPage.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-sm" style="color: var(--van-text-color-2);">我的邀请码</div>
|
||||
<div class="text-2xl font-bold" style="color: var(--van-theme-primary);">{{ agentCode }}</div>
|
||||
</div>
|
||||
<button @click="copyInviteCode"
|
||||
class="px-4 py-2 bg-blue-500 text-white rounded-lg text-sm font-medium active:bg-blue-600 shadow-sm">复制邀请码</button>
|
||||
</div>
|
||||
<div class="p-3">
|
||||
<QRcode :inviteLink="inviteLink" mode="invitation" :asPage="true" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { showToast } from "vant";
|
||||
import { storeToRefs } from "pinia";
|
||||
import QRcode from "@/components/QRcode.vue";
|
||||
import { useAgentStore } from "@/stores/agentStore";
|
||||
import { getInviteLink } from "@/api/agent";
|
||||
|
||||
const inviteLink = ref("");
|
||||
const agentStore = useAgentStore();
|
||||
const { agentCode } = storeToRefs(agentStore);
|
||||
|
||||
const copyText = async (text, tip) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
showToast({ message: tip || "已复制" });
|
||||
} catch (e) {
|
||||
const ta = document.createElement("textarea");
|
||||
ta.value = text;
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
try {
|
||||
document.execCommand("copy");
|
||||
showToast({ message: tip || "已复制" });
|
||||
} finally {
|
||||
document.body.removeChild(ta);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const copyInviteCode = () => {
|
||||
if (!agentCode.value) {
|
||||
showToast({ message: "未获取到邀请码" });
|
||||
return;
|
||||
}
|
||||
copyText(String(agentCode.value), "邀请码已复制");
|
||||
};
|
||||
|
||||
const copyInviteLink = () => {
|
||||
if (!inviteLink.value) {
|
||||
showToast({ message: "暂无邀请链接" });
|
||||
return;
|
||||
}
|
||||
copyText(inviteLink.value, "邀请链接已复制");
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
if (!agentStore.isLoaded) {
|
||||
await agentStore.fetchAgentStatus();
|
||||
}
|
||||
if (!agentStore.isAgent || !agentCode.value) {
|
||||
showToast({ message: "请注册成为代理后再邀请" });
|
||||
return;
|
||||
}
|
||||
|
||||
const targetPath = `/register?invite_code=${agentCode.value}`;
|
||||
const { data, error } = await getInviteLink({ invite_code: agentCode.value, target_path: targetPath });
|
||||
if (data.value && !error.value && data.value.code === 200) {
|
||||
inviteLink.value = data.value.data.invite_link || "";
|
||||
} else {
|
||||
showToast({ message: data.value?.msg || "获取邀请链接失败" });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
430
src/views/Login.vue
Normal file
430
src/views/Login.vue
Normal file
@@ -0,0 +1,430 @@
|
||||
<script setup>
|
||||
import { ref, computed, onUnmounted, nextTick } from 'vue'
|
||||
import { showToast } from 'vant'
|
||||
import { useAgentStore } from '@/stores/agentStore'
|
||||
import { useUserStore } from '@/stores/userStore'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { mobileCodeLogin } from '@/api/user'
|
||||
import useApiFetch from '@/composables/useApiFetch'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const agentStore = useAgentStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const phoneNumber = ref('')
|
||||
const verificationCode = ref('')
|
||||
const isCountingDown = ref(false)
|
||||
const countdown = ref(60)
|
||||
const isAgreed = ref(false)
|
||||
let timer = null
|
||||
|
||||
const isPhoneNumberValid = computed(() => {
|
||||
return /^1[3-9]\d{9}$/.test(phoneNumber.value)
|
||||
})
|
||||
|
||||
const canLogin = computed(() => {
|
||||
return isPhoneNumberValid.value && verificationCode.value.length === 6 && isAgreed.value
|
||||
})
|
||||
const hideRegister = computed(() => route.query.from === 'promotionInquire')
|
||||
|
||||
async function sendVerificationCode() {
|
||||
if (isCountingDown.value || !isPhoneNumberValid.value) return
|
||||
if (!isPhoneNumberValid.value) {
|
||||
showToast({ message: "请输入有效的手机号" });
|
||||
return
|
||||
}
|
||||
|
||||
const { data, error } = await useApiFetch('auth/sendSms')
|
||||
.post({ mobile: phoneNumber.value, actionType: 'login' })
|
||||
.json()
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
showToast({ message: "获取成功" });
|
||||
startCountdown()
|
||||
// 聚焦到验证码输入框
|
||||
nextTick(() => {
|
||||
const verificationCodeInput = document.getElementById('verificationCode');
|
||||
if (verificationCodeInput) {
|
||||
verificationCodeInput.focus();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
showToast(data.value.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function startCountdown() {
|
||||
isCountingDown.value = true
|
||||
countdown.value = 60
|
||||
timer = setInterval(() => {
|
||||
if (countdown.value > 0) {
|
||||
countdown.value--
|
||||
} else {
|
||||
clearInterval(timer)
|
||||
isCountingDown.value = false
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
async function handleLogin() {
|
||||
if (!isPhoneNumberValid.value) {
|
||||
showToast({ message: "请输入有效的手机号" });
|
||||
return
|
||||
}
|
||||
if (verificationCode.value.length !== 6) {
|
||||
showToast({ message: "请输入有效的验证码" });
|
||||
return
|
||||
}
|
||||
if (!isAgreed.value) {
|
||||
showToast({ message: "请先同意用户协议和隐私政策" });
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const { data, error } = await mobileCodeLogin({
|
||||
mobile: phoneNumber.value,
|
||||
code: verificationCode.value
|
||||
})
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
// 保存token
|
||||
localStorage.setItem('token', data.value.data.accessToken)
|
||||
localStorage.setItem('refreshAfter', data.value.data.refreshAfter)
|
||||
localStorage.setItem('accessExpire', data.value.data.accessExpire)
|
||||
|
||||
// 获取用户信息和代理信息
|
||||
await Promise.all([
|
||||
userStore.fetchUserInfo(),
|
||||
agentStore.fetchAgentStatus()
|
||||
])
|
||||
|
||||
showToast({ message: "登录成功!" });
|
||||
setTimeout(() => {
|
||||
const redirect = route.query.redirect ? decodeURIComponent(route.query.redirect) : '/'
|
||||
router.replace(redirect)
|
||||
}, 500)
|
||||
} else {
|
||||
showToast(data.value.msg || "登录失败,请重试")
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('登录失败:', err)
|
||||
showToast({ message: "登录失败,请重试" });
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
}
|
||||
})
|
||||
|
||||
const onClickLeft = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
const goToRegister = () => {
|
||||
router.push('/register')
|
||||
}
|
||||
|
||||
function toUserAgreement() {
|
||||
router.push(`/userAgreement`)
|
||||
}
|
||||
|
||||
function toPrivacyPolicy() {
|
||||
router.push(`/privacyPolicy`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-layout ">
|
||||
<van-nav-bar fixed placeholder title="登录" left-text="" left-arrow @click-left="onClickLeft" />
|
||||
<div class="login px-4 relative z-10">
|
||||
<div class="mb-8 pt-20 text-left">
|
||||
<div class="flex flex-col items-center">
|
||||
<img class="h-16 w-16 rounded-full shadow" src="@/assets/images/logo.jpg" alt="Logo" />
|
||||
<div class="report-title-wrapper">
|
||||
<h1 class="report-title">大数据风险报告</h1>
|
||||
<div class="report-title-decoration"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 登录表单 -->
|
||||
<div class="login-form">
|
||||
<!-- 手机号输入 -->
|
||||
<div class="form-item">
|
||||
<div class="form-label">手机号</div>
|
||||
<input v-model="phoneNumber" class="form-input" type="tel" placeholder="请输入手机号" maxlength="11" />
|
||||
</div>
|
||||
|
||||
<!-- 验证码输入 -->
|
||||
<div class="form-item">
|
||||
<div class="form-label">验证码</div>
|
||||
<div class="verification-input-wrapper">
|
||||
<input v-model="verificationCode" id="verificationCode" class="form-input verification-input"
|
||||
placeholder="请输入验证码" maxlength="6" />
|
||||
<button class="get-code-btn" :class="{ 'disabled': isCountingDown || !isPhoneNumberValid }"
|
||||
@click="sendVerificationCode" :disabled="isCountingDown || !isPhoneNumberValid">
|
||||
{{ isCountingDown ? `${countdown}s` : '获取验证码' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 协议同意框 -->
|
||||
<div class="agreement-wrapper">
|
||||
<input type="checkbox" v-model="isAgreed" class="agreement-checkbox accent-primary"
|
||||
id="agreement" />
|
||||
<label for="agreement" class="agreement-text">
|
||||
我已阅读并同意
|
||||
<a class="agreement-link" @click="toUserAgreement">《用户协议》</a>
|
||||
和
|
||||
<a class="agreement-link" @click="toPrivacyPolicy">《隐私政策》</a>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- 提示文字 -->
|
||||
<div class="notice-text">
|
||||
未注册手机号登录后将自动注册账号,并且代表您已阅读并同意
|
||||
</div>
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<button class="login-btn" :class="{ 'disabled': !canLogin }" @click="handleLogin" :disabled="!canLogin">
|
||||
登录
|
||||
</button>
|
||||
|
||||
<!-- 注册按钮(推广链接来源的登录不展示) -->
|
||||
<button v-if="!hideRegister" class="register-btn" @click="goToRegister">
|
||||
注册成为代理
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.login-layout {
|
||||
background-image: url('@/assets/images/login_bg.png');
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 登录表单 */
|
||||
.login-form {
|
||||
background-color: var(--color-bg-primary);
|
||||
padding: 2rem;
|
||||
margin-top: 0.5rem;
|
||||
box-shadow: 0px 0px 24px 0px #3F3F3F0F;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* 表单项 */
|
||||
.form-item {
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 0.9375rem;
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: 0;
|
||||
margin-right: 1rem;
|
||||
font-weight: 500;
|
||||
min-width: 4rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 0.875rem 0;
|
||||
|
||||
font-size: 0.9375rem;
|
||||
color: var(--color-text-primary);
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.form-input::placeholder {
|
||||
color: var(--color-text-tertiary);
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
border-bottom-color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
/* 验证码输入 */
|
||||
.verification-input-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.verification-input {
|
||||
flex: 1;
|
||||
padding-right: 6rem;
|
||||
}
|
||||
|
||||
.get-code-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-primary);
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.get-code-btn.disabled {
|
||||
color: var(--color-gray-400);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 注册按钮 */
|
||||
.register-btn {
|
||||
width: 100%;
|
||||
padding: 0.875rem;
|
||||
background-color: transparent;
|
||||
color: var(--color-primary);
|
||||
border: 1px solid var(--color-primary);
|
||||
border-radius: 1.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
letter-spacing: 0.25rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.register-btn:hover {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-text-white);
|
||||
}
|
||||
|
||||
/* 登录按钮 */
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
padding: 0.875rem;
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-text-white);
|
||||
border: none;
|
||||
border-radius: 1.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.3s;
|
||||
letter-spacing: 0.25rem;
|
||||
}
|
||||
|
||||
.login-btn:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.login-btn.disabled {
|
||||
background-color: var(--color-gray-300);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 协议同意 */
|
||||
.agreement-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.agreement-checkbox {
|
||||
flex-shrink: 0;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.agreement-text {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.agreement-link {
|
||||
color: var(--color-primary);
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* 提示文字 */
|
||||
.notice-text {
|
||||
font-size: 0.6875rem;
|
||||
color: var(--color-text-tertiary);
|
||||
line-height: 1.5;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* 大数据风险报告标题样式 */
|
||||
.report-title-wrapper {
|
||||
position: relative;
|
||||
margin-top: 1.5rem;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.report-title {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 25%, #f093fb 50%, #4facfe 75%, #00f2fe 100%);
|
||||
background-size: 200% 200%;
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
text-align: center;
|
||||
letter-spacing: 0.1em;
|
||||
margin: 0;
|
||||
padding: 0.5rem 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
animation: gradientShift 3s ease infinite;
|
||||
text-shadow: 0 2px 10px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.report-title-decoration {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 60px;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, transparent, #667eea, #764ba2, #667eea, transparent);
|
||||
border-radius: 2px;
|
||||
animation: decorationPulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes gradientShift {
|
||||
0%, 100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes decorationPulse {
|
||||
0%, 100% {
|
||||
opacity: 0.6;
|
||||
transform: translateX(-50%) scaleX(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) scaleX(1.2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
505
src/views/Me.vue
Normal file
505
src/views/Me.vue
Normal file
@@ -0,0 +1,505 @@
|
||||
<template>
|
||||
<div class="box-border min-h-screen">
|
||||
<div class="flex flex-col p-4 space-y-6">
|
||||
<!-- 用户信息和资产卡片(合并) -->
|
||||
<div class="profile-section group relative rounded-xl p-0.5 transition-all hover:shadow-xl"
|
||||
:class="isAgent ? levelGradient.cardBorder : 'bg-gray-200'"
|
||||
@click="!isLoggedIn ? redirectToLogin() : null">
|
||||
<div class="rounded-xl bg-white p-6">
|
||||
<!-- 上半部分:用户信息 -->
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<div class="relative">
|
||||
<!-- 头像容器添加overflow-hidden解决边框问题 -->
|
||||
<div class="overflow-hidden rounded-full p-0.5" :class="levelGradient.border">
|
||||
<img :src="userAvatar || getDefaultAvatar()" alt="User Avatar"
|
||||
class="h-20 w-20 rounded-full border-4 border-white" />
|
||||
</div>
|
||||
|
||||
<!-- 代理标识 -->
|
||||
<div v-if="isAgent" class="absolute -bottom-2 -right-2">
|
||||
<div class="flex items-center justify-center rounded-full px-3 py-1 text-xs font-bold text-white shadow-sm"
|
||||
:class="levelGradient.badge">
|
||||
{{ levelNamesMap[level] || levelNamesMap[1] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 space-y-1">
|
||||
<h2 class="text-2xl font-bold" style="color: var(--van-text-color);">
|
||||
{{
|
||||
!isLoggedIn
|
||||
? "点击登录"
|
||||
: mobile
|
||||
? mobile
|
||||
: isWeChat
|
||||
? "微信用户"
|
||||
: "未绑定手机号"
|
||||
}}
|
||||
</h2>
|
||||
<!-- 手机号绑定提示 -->
|
||||
<template v-if="isLoggedIn && !mobile">
|
||||
<p @click.stop="showRegisterAgentDialog" class="text-sm cursor-pointer hover:underline"
|
||||
style="color: var(--van-theme-primary);">
|
||||
点击注册成为代理
|
||||
</p>
|
||||
</template>
|
||||
<!-- 普通用户申请成为代理提示 -->
|
||||
<template v-else-if="isLoggedIn && mobile && !isAgent">
|
||||
<p @click.stop="toRegister" class="text-sm cursor-pointer hover:underline"
|
||||
style="color: var(--van-theme-primary);">
|
||||
点击申请成为代理
|
||||
</p>
|
||||
</template>
|
||||
<p v-if="isAgent" class="font-bold" :class="levelGradient.text">
|
||||
ID: {{ agentCode }}
|
||||
</p>
|
||||
</div>
|
||||
<!-- 右侧资产信息(仅代理显示) -->
|
||||
<div v-if="isAgent" class="text-right">
|
||||
<div class="text-sm mb-1" style="color: var(--van-text-color-2);">余额</div>
|
||||
<div class="text-2xl font-bold" style="color: var(--van-theme-primary);">
|
||||
¥ {{ (revenueData?.balance || 0).toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 下半部分:资产详情和操作按钮(仅代理显示) -->
|
||||
<template v-if="isAgent">
|
||||
<div class="grid grid-cols-3 gap-3 mb-4 pt-4 border-t"
|
||||
style="border-color: var(--van-border-color);">
|
||||
<div class="text-center">
|
||||
<div class="text-sm mb-1" style="color: var(--van-text-color-2);">累计收益</div>
|
||||
<div class="text-base font-semibold" style="color: var(--van-text-color);">
|
||||
¥ {{ (revenueData?.total_earnings || 0).toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-sm mb-1" style="color: var(--van-text-color-2);">风险保障金</div>
|
||||
<div class="text-base font-semibold" style="color: var(--van-text-color);">
|
||||
¥ {{ (revenueData?.frozen_balance || 0).toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-sm mb-1" style="color: var(--van-text-color-2);">累计提现</div>
|
||||
<div class="text-base font-semibold" style="color: var(--van-text-color);">
|
||||
¥ {{ (revenueData?.withdrawn_amount || 0).toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<button @click.stop="toWithdraw"
|
||||
class="text-white rounded-full py-1.5 px-4 shadow-md flex items-center justify-center text-base"
|
||||
style="background: linear-gradient(135deg, var(--van-theme-primary), var(--van-theme-primary-dark)); min-height: 36px;">
|
||||
<van-icon name="gold-coin" class="mr-1" />
|
||||
提现
|
||||
</button>
|
||||
<button @click.stop="toWithdrawDetails"
|
||||
class="bg-white/90 border rounded-full py-1.5 px-4 shadow-sm flex items-center justify-center text-base"
|
||||
style="color: var(--van-text-color-2); border-color: var(--van-border-color); min-height: 36px;">
|
||||
<van-icon name="notes" class="mr-1" />
|
||||
提现记录
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 佣金和下级推广收益(合并) -->
|
||||
<div v-if="isAgent" class="rounded-xl shadow-lg bg-white p-5">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<!-- 我的推广收益部分 -->
|
||||
<div class="pr-4 border-r" style="border-color: var(--van-border-color);">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="flex items-center">
|
||||
<van-icon name="balance-list" class="text-lg mr-2"
|
||||
style="color: var(--color-warning);" />
|
||||
<span class="text-base font-bold" style="color: var(--van-text-color);">我的推广收益</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mb-3">
|
||||
<div class="text-xl font-bold" style="color: var(--color-warning);">
|
||||
¥ {{ (revenueData?.commission_total || 0).toFixed(2) }}
|
||||
</div>
|
||||
<div class="text-sm mt-0.5" style="color: var(--van-text-color-2);">累计总收益</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2 mb-3">
|
||||
<div class="text-center">
|
||||
<div class="text-sm mb-1" style="color: var(--van-text-color-2);">今日</div>
|
||||
<div class="text-base font-semibold" style="color: var(--color-warning);">
|
||||
¥ {{ (revenueData?.commission_today || 0).toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-sm mb-1" style="color: var(--van-text-color-2);">本月</div>
|
||||
<div class="text-base font-semibold" style="color: var(--color-warning);">
|
||||
¥ {{ (revenueData?.commission_month || 0).toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-center text-sm font-semibold cursor-pointer"
|
||||
style="color: var(--color-warning);" @click="goToPromoteDetail">
|
||||
<span>查看明细</span>
|
||||
<span class="ml-1 text-base">→</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 下级推广收益部分 -->
|
||||
<div class="pl-4">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="flex items-center">
|
||||
<van-icon name="gift-o" class="text-lg mr-2" style="color: var(--color-success);" />
|
||||
<span class="text-base font-bold" style="color: var(--van-text-color);">下级推广收益</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mb-3">
|
||||
<div class="text-xl font-bold" style="color: var(--color-success);">
|
||||
¥ {{ (revenueData?.rebate_total || 0).toFixed(2) }}
|
||||
</div>
|
||||
<div class="text-sm mt-0.5" style="color: var(--van-text-color-2);">累计总收益</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2 mb-3">
|
||||
<div class="text-center">
|
||||
<div class="text-sm mb-1" style="color: var(--van-text-color-2);">今日</div>
|
||||
<div class="text-base font-semibold" style="color: var(--color-success);">
|
||||
¥ {{ (revenueData?.rebate_today || 0).toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-sm mb-1" style="color: var(--van-text-color-2);">本月</div>
|
||||
<div class="text-base font-semibold" style="color: var(--color-success);">
|
||||
¥ {{ (revenueData?.rebate_month || 0).toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-center text-sm font-semibold cursor-pointer"
|
||||
style="color: var(--color-success);" @click="goToRebateDetail">
|
||||
<span>查看明细</span>
|
||||
<span class="ml-1 text-base">→</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 功能菜单 -->
|
||||
<div class="">
|
||||
<div class="bg-white rounded-xl shadow-sm p-4">
|
||||
<div class="grid grid-cols-4 gap-4">
|
||||
<!-- 升级功能入口(如果不是钻石代理) -->
|
||||
<button v-if="isAgent && level !== 3"
|
||||
class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg hover:bg-purple-50 transition-colors"
|
||||
@click="toUpgrade">
|
||||
<img src="@/assets/images/me/sjdl.svg" class="w-8 h-8 object-contain" alt="升级代理" />
|
||||
<span class="text-xs text-gray-700 font-medium text-center">升级代理</span>
|
||||
</button>
|
||||
<!-- 升级下级入口(仅钻石代理) -->
|
||||
<button v-if="isAgent && level === 3"
|
||||
class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg hover:bg-purple-50 transition-colors"
|
||||
@click="toUpgradeSubordinate">
|
||||
<img src="@/assets/images/me/sjxj.svg" class="w-8 h-8 object-contain" alt="调整下级级别" />
|
||||
<span class="text-xs text-gray-700 font-medium text-center">调整级别</span>
|
||||
</button>
|
||||
<!-- 邀请码管理入口 -->
|
||||
<button v-if="isAgent"
|
||||
class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg hover:bg-blue-50 transition-colors"
|
||||
@click="toInviteCodeManage">
|
||||
<img src="@/assets/images/me/yqmgl.svg" class="w-8 h-8 object-contain" alt="邀请码管理" />
|
||||
<span class="text-xs text-gray-700 font-medium text-center">邀请码管理</span>
|
||||
</button>
|
||||
<!-- 实名认证入口(所有代理) -->
|
||||
<button v-if="isAgent"
|
||||
class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg hover:bg-green-50 transition-colors"
|
||||
@click="toRealNameAuth">
|
||||
<img src="@/assets/images/me/smrz.svg" class="w-8 h-8 object-contain" alt="提现" />
|
||||
<span class="text-xs text-gray-700 font-medium text-center">实名认证</span>
|
||||
</button>
|
||||
<!-- 提现入口(所有代理) -->
|
||||
<!-- <button v-if="isAgent"
|
||||
class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg hover:bg-blue-50 transition-colors"
|
||||
@click="toWithdraw">
|
||||
<img src="@/assets/images/me/tx.svg" class="w-8 h-8 object-contain" alt="提现" />
|
||||
<span class="text-xs text-gray-700 font-medium text-center">提现</span>
|
||||
</button> -->
|
||||
<!-- 推广查询记录 -->
|
||||
<button v-if="isAgent"
|
||||
class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg hover:bg-blue-50 transition-colors"
|
||||
@click="toAgentReport">
|
||||
<img src="@/assets/images/me/tgcxjl.svg" class="w-8 h-8 object-contain" alt="推广查询记录" />
|
||||
<span class="text-xs text-gray-700 font-medium text-center">推广查询记录</span>
|
||||
</button>
|
||||
<button
|
||||
class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg hover:bg-blue-50 transition-colors"
|
||||
@click="toUserAgreement">
|
||||
<img src="@/assets/images/me/yhxy.svg" class="w-8 h-8 object-contain" alt="用户协议" />
|
||||
<span class="text-xs text-gray-700 font-medium text-center">用户协议</span>
|
||||
</button>
|
||||
<button
|
||||
class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg hover:bg-blue-50 transition-colors"
|
||||
@click="toPrivacyPolicy">
|
||||
<img src="@/assets/images/me/yszc.svg" class="w-8 h-8 object-contain" alt="隐私政策" />
|
||||
<span class="text-xs text-gray-700 font-medium text-center">隐私政策</span>
|
||||
</button>
|
||||
<button
|
||||
class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg hover:bg-blue-50 transition-colors"
|
||||
@click="toService">
|
||||
<img src="@/assets/images/me/lxkf.svg" class="w-8 h-8 object-contain" alt="联系客服" />
|
||||
<span class="text-xs text-gray-700 font-medium text-center">联系客服</span>
|
||||
</button>
|
||||
<button
|
||||
class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg hover:bg-purple-50 transition-colors"
|
||||
@click="toAgentSystemGuide">
|
||||
<img src="@/assets/images/me/yhxy.svg" class="w-8 h-8 object-contain" alt="代理系统指南" />
|
||||
<span class="text-xs text-gray-700 font-medium text-center">代理系统指南</span>
|
||||
</button>
|
||||
<button v-if="isLoggedIn && !isWeChat"
|
||||
class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg hover:bg-red-50 transition-colors"
|
||||
@click="handleLogout">
|
||||
<img src="@/assets/images/me/tcdl.png" class="w-8 h-8 object-contain" alt="退出登录" />
|
||||
<span class="text-xs text-gray-700 font-medium text-center">退出登录</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BindPhoneDialog />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { storeToRefs } from "pinia";
|
||||
import { ref, computed, onBeforeMount, onMounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import headShot from "@/assets/images/head_shot.webp";
|
||||
import { useAgentStore } from "@/stores/agentStore";
|
||||
import { useUserStore } from "@/stores/userStore";
|
||||
import { useEnv } from "@/composables/useEnv";
|
||||
import { useDialogStore } from "@/stores/dialogStore";
|
||||
import useApiFetch from "@/composables/useApiFetch";
|
||||
import { getRevenueInfo } from '@/api/agent';
|
||||
import BindPhoneDialog from "@/components/BindPhoneDialog.vue";
|
||||
|
||||
const router = useRouter();
|
||||
const agentStore = useAgentStore();
|
||||
const userStore = useUserStore();
|
||||
const dialogStore = useDialogStore();
|
||||
const { isAgent, level, levelName, agentCode } = storeToRefs(agentStore);
|
||||
const { userName, userAvatar, isLoggedIn, mobile } = storeToRefs(userStore);
|
||||
const { isWeChat } = useEnv();
|
||||
const revenueData = ref(null);
|
||||
|
||||
// 等级名称映射(数字等级)
|
||||
const levelNamesMap = {
|
||||
1: "普通代理",
|
||||
2: "黄金代理",
|
||||
3: "钻石代理",
|
||||
};
|
||||
|
||||
const levelTextMap = {
|
||||
1: "基础代理特权",
|
||||
2: "高级代理特权",
|
||||
3: "尊享代理特权",
|
||||
};
|
||||
|
||||
const levelGradient = computed(() => {
|
||||
const currentLevel = level.value || 1;
|
||||
const gradients = {
|
||||
1: {
|
||||
border: "bg-gradient-to-r from-gray-300 to-gray-400",
|
||||
badge: "bg-gradient-to-r from-gray-500 to-gray-600",
|
||||
text: "text-gray-600",
|
||||
cardBorder: "bg-gradient-to-r from-gray-300 to-gray-400",
|
||||
},
|
||||
2: {
|
||||
border: "bg-gradient-to-r from-yellow-400 to-amber-500",
|
||||
badge: "bg-gradient-to-r from-yellow-500 to-amber-600",
|
||||
text: "text-amber-600",
|
||||
cardBorder: "bg-gradient-to-r from-yellow-400 to-amber-500",
|
||||
},
|
||||
3: {
|
||||
border: "bg-gradient-to-r from-purple-400 to-pink-400 shadow-[0_0_15px_rgba(163,51,200,0.2)]",
|
||||
badge: "bg-gradient-to-r from-purple-500 to-pink-500",
|
||||
text: "text-purple-600",
|
||||
cardBorder: "bg-gradient-to-r from-purple-400 to-pink-400 shadow-[0_0_20px_rgba(163,51,200,0.3)]",
|
||||
},
|
||||
};
|
||||
return gradients[currentLevel] || gradients[1];
|
||||
});
|
||||
|
||||
const maskName = (name) => {
|
||||
if (!name || name.length < 11) return name;
|
||||
return name.substring(0, 3) + "****" + name.substring(7);
|
||||
};
|
||||
|
||||
function toUserAgreement() {
|
||||
router.push(`/userAgreement`);
|
||||
}
|
||||
|
||||
function toPrivacyPolicy() {
|
||||
router.push(`/privacyPolicy`); // 新增隐私政策路由
|
||||
}
|
||||
|
||||
|
||||
function redirectToLogin() {
|
||||
router.push(`/login`);
|
||||
}
|
||||
|
||||
function handleLogout() {
|
||||
// 改进的存储管理
|
||||
localStorage.removeItem("token");
|
||||
localStorage.removeItem("refreshAfter");
|
||||
localStorage.removeItem("accessExpire");
|
||||
localStorage.removeItem("userInfo");
|
||||
localStorage.removeItem("agentInfo");
|
||||
|
||||
// 重置状态
|
||||
userStore.resetUser();
|
||||
agentStore.resetAgent();
|
||||
|
||||
location.reload();
|
||||
}
|
||||
const toBigData = () => {
|
||||
window.location.href = "https://www.tybigdata.com/";
|
||||
};
|
||||
function handleCancelAccount() {
|
||||
// 账号注销功能
|
||||
if (confirm("注销账号后,您的所有数据将被清除且无法恢复,确定要继续吗?")) {
|
||||
useApiFetch("/user/cancelOut")
|
||||
.post()
|
||||
.json()
|
||||
.then(({ data, error }) => {
|
||||
if (!error && data.value && data.value.code === 200) {
|
||||
alert("账号已注销");
|
||||
handleLogout();
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("注销账号失败:", error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const serviceUrl = import.meta.env.VITE_SERVICE_URL || '';
|
||||
function toService() {
|
||||
if (serviceUrl) {
|
||||
window.location.href = serviceUrl; // 跳转到客服页面
|
||||
}
|
||||
}
|
||||
|
||||
const toAgentSystemGuide = () => {
|
||||
router.push({ name: "agentSystemGuide" });
|
||||
};
|
||||
|
||||
const toUpgrade = () => {
|
||||
router.push({ name: "agentUpgrade" });
|
||||
};
|
||||
|
||||
const toUpgradeSubordinate = () => {
|
||||
router.push({ name: "upgradeSubordinate" });
|
||||
};
|
||||
|
||||
const toInviteCodeManage = () => {
|
||||
router.push({ name: "invitation" });
|
||||
};
|
||||
|
||||
const toRealNameAuth = () => {
|
||||
dialogStore.openRealNameAuth();
|
||||
};
|
||||
const toAgentReport = () => {
|
||||
router.push({ name: "agentPromotionQueryList" })
|
||||
}
|
||||
const toWithdraw = () => {
|
||||
router.push({ name: "withdraw" });
|
||||
};
|
||||
|
||||
const toWithdrawDetails = () => {
|
||||
router.push({ name: "withdrawDetails" });
|
||||
};
|
||||
|
||||
const goToPromoteDetail = () => {
|
||||
router.push({ name: "promoteDetails" });
|
||||
};
|
||||
|
||||
const goToRebateDetail = () => {
|
||||
router.push({ name: "rewardsDetails" });
|
||||
};
|
||||
|
||||
const getRevenueData = async () => {
|
||||
if (!isAgent.value) return;
|
||||
try {
|
||||
const { data: revenueResponse, error: revenueError } = await getRevenueInfo();
|
||||
if (revenueResponse.value?.code === 200 && !revenueError.value) {
|
||||
revenueData.value = revenueResponse.value.data;
|
||||
} else if (revenueResponse.value?.code !== 200) {
|
||||
// 如果不是代理,静默处理,不显示错误提示
|
||||
console.log("获取收益信息失败:", revenueResponse.value?.msg);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("获取收益信息异常:", err);
|
||||
// 静默处理错误,不中断用户操作
|
||||
}
|
||||
};
|
||||
|
||||
const getDefaultAvatar = () => {
|
||||
if (!isAgent.value) return headShot;
|
||||
|
||||
const currentLevel = level.value || 1;
|
||||
switch (currentLevel) {
|
||||
case 1:
|
||||
return "/image/shot_nonal.png";
|
||||
case 2:
|
||||
return "/image/shot_vip.png";
|
||||
case 3:
|
||||
return "/image/shot_svip.png";
|
||||
default:
|
||||
return headShot;
|
||||
}
|
||||
};
|
||||
|
||||
const showRegisterAgentDialog = () => {
|
||||
dialogStore.openRegisterAgent();
|
||||
};
|
||||
|
||||
const toRegister = () => {
|
||||
router.push("/register");
|
||||
};
|
||||
|
||||
onBeforeMount(async () => {
|
||||
// 获取存储的用户和代理信息
|
||||
const userInfo = localStorage.getItem("userInfo");
|
||||
if (userInfo) {
|
||||
try {
|
||||
const parsedUserInfo = JSON.parse(userInfo);
|
||||
userStore.updateUserInfo(parsedUserInfo);
|
||||
} catch (e) {
|
||||
console.error("解析用户信息失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
const agentInfo = localStorage.getItem("agentInfo");
|
||||
if (agentInfo) {
|
||||
try {
|
||||
const parsedAgentInfo = JSON.parse(agentInfo);
|
||||
agentStore.updateAgentInfo(parsedAgentInfo);
|
||||
} catch (e) {
|
||||
console.error("解析代理信息失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果已登录,刷新代理状态(确保状态是最新的)
|
||||
const token = localStorage.getItem("token");
|
||||
if (token && isLoggedIn.value) {
|
||||
try {
|
||||
await agentStore.fetchAgentStatus();
|
||||
} catch (err) {
|
||||
console.error("刷新代理状态失败:", err);
|
||||
// 不中断流程,只是记录错误
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 只有在确认是代理后才获取收益数据
|
||||
if (isAgent.value) {
|
||||
getRevenueData();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
186
src/views/NotFound.vue
Normal file
186
src/views/NotFound.vue
Normal file
@@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<div class="not-found">
|
||||
<div class="not-found-content">
|
||||
<h1>404</h1>
|
||||
<h2>页面未找到</h2>
|
||||
<p>抱歉,您访问的页面不存在或已被移除。</p>
|
||||
<div class="suggestions">
|
||||
<h3>您可以尝试:</h3>
|
||||
<ul>
|
||||
<li><router-link to="/">返回首页</router-link></li>
|
||||
<li><router-link to="/help">查看帮助中心</router-link></li>
|
||||
<li><router-link to="/service">联系客服</router-link></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<router-link to="/" class="home-link">返回首页</router-link>
|
||||
<router-link to="/help" class="help-link">帮助中心</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
import { useSEO } from '@/composables/useSEO'
|
||||
|
||||
// SEO优化
|
||||
const { updateSEO } = useSEO()
|
||||
|
||||
onMounted(() => {
|
||||
updateSEO({
|
||||
title: '404 - 页面未找到 | 真爱查',
|
||||
description: '抱歉,您访问的页面不存在。真爱查专业大数据风险管控平台,提供大数据风险报告查询、婚姻状况查询、个人信用评估等服务。',
|
||||
keywords: '404, 页面未找到, 真爱查, 大数据风险管控',
|
||||
url: 'https://www.zhinengcha.cn/404'
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.not-found {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.not-found-content {
|
||||
background: white;
|
||||
padding: 60px 40px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.not-found h1 {
|
||||
font-size: 120px;
|
||||
color: #667eea;
|
||||
margin: 0 0 20px 0;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.not-found h2 {
|
||||
font-size: 32px;
|
||||
color: #333;
|
||||
margin: 0 0 20px 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.not-found p {
|
||||
font-size: 18px;
|
||||
color: #666;
|
||||
margin-bottom: 30px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.suggestions {
|
||||
margin: 30px 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.suggestions h3 {
|
||||
font-size: 20px;
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.suggestions ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.suggestions li {
|
||||
margin: 10px 0;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.suggestions li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.suggestions a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.suggestions a:hover {
|
||||
color: #5a6fd8;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 40px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.home-link, .help-link {
|
||||
display: inline-block;
|
||||
padding: 15px 30px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
border-radius: 50px;
|
||||
transition: all 0.3s ease;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.home-link {
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
.home-link:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.help-link {
|
||||
color: #667eea;
|
||||
background: white;
|
||||
border: 2px solid #667eea;
|
||||
}
|
||||
|
||||
.help-link:hover {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.not-found-content {
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.not-found h1 {
|
||||
font-size: 80px;
|
||||
}
|
||||
|
||||
.not-found h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.home-link, .help-link {
|
||||
width: 100%;
|
||||
max-width: 200px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
532
src/views/PaymentResult.vue
Normal file
532
src/views/PaymentResult.vue
Normal file
@@ -0,0 +1,532 @@
|
||||
<template>
|
||||
<div class="payment-result-container flex flex-col items-center p-6">
|
||||
<!-- 加载动画,验证支付结果时显示 -->
|
||||
<div v-if="isLoading" class="w-full">
|
||||
<div class="flex flex-col items-center justify-center py-10">
|
||||
<van-loading size="48px" color="#1989fa" />
|
||||
<p class="mt-4 text-gray-600 text-lg">正在处理支付结果...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 支付结果展示 -->
|
||||
<div v-else class="w-full">
|
||||
<!-- 支付成功 -->
|
||||
<div v-if="paymentStatus === 'paid'" class="success-result">
|
||||
<div class="success-animation mb-6">
|
||||
<van-icon name="checked" size="64" color="#07c160" />
|
||||
<div class="success-ring"></div>
|
||||
</div>
|
||||
|
||||
<h1 class="text-2xl font-bold text-center text-gray-800 mb-4">
|
||||
支付成功
|
||||
</h1>
|
||||
|
||||
<div class="payment-info bg-white rounded-lg shadow-md p-6 mb-6 w-full">
|
||||
<div class="flex justify-between mb-4">
|
||||
<span class="text-gray-600">订单编号</span>
|
||||
<span class="text-gray-800">{{ orderNo }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">支付类型</span>
|
||||
<span class="text-gray-800">{{
|
||||
paymentType === "agent_upgrade"
|
||||
? "代理升级"
|
||||
: "查询服务"
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="paymentType === 'agent_upgrade'" class="text-center text-gray-600 mb-4">恭喜你升级代理等级成功,享受更多权益
|
||||
</div>
|
||||
|
||||
<div class="action-buttons grid grid-cols-1 gap-4">
|
||||
<van-button block type="primary" class="rounded-lg" @click="handleNavigation">
|
||||
{{
|
||||
paymentType === "agent_upgrade"
|
||||
? "查看代理信息"
|
||||
: "查看查询结果"
|
||||
}}
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 退款状态 -->
|
||||
<div v-else-if="paymentStatus === 'refunded'" class="refund-result">
|
||||
<div v-if="paymentType === 'query'" class="success-animation mb-6">
|
||||
<van-icon name="checked" size="64" color="#07c160" />
|
||||
<div class="success-ring"></div>
|
||||
</div>
|
||||
<div v-else class="info-animation mb-6">
|
||||
<van-icon name="info-o" size="64" color="#1989fa" />
|
||||
<div class="info-ring"></div>
|
||||
</div>
|
||||
|
||||
<h1 class="text-2xl font-bold text-center text-gray-800 mb-4">
|
||||
{{ paymentType === "query" ? "已处理" : "订单已退款" }}
|
||||
</h1>
|
||||
|
||||
<div class="payment-info bg-white rounded-lg shadow-md p-6 mb-6 w-full">
|
||||
<div class="flex justify-between mb-4">
|
||||
<span class="text-gray-600">订单编号</span>
|
||||
<span class="text-gray-800">{{ orderNo }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between mb-4">
|
||||
<span class="text-gray-600">支付类型</span>
|
||||
<span class="text-gray-800">{{
|
||||
paymentType === "agent_upgrade"
|
||||
? "代理升级"
|
||||
: "查询服务"
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">订单状态</span>
|
||||
<span class="text-blue-600">已退款</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="paymentType === 'query'" class="action-buttons grid grid-cols-1 gap-4">
|
||||
<van-button block type="primary" class="rounded-lg" @click="handleNavigation">
|
||||
查看查询结果
|
||||
</van-button>
|
||||
</div>
|
||||
|
||||
<div v-else class="message-box p-4 bg-blue-50 rounded-lg mb-6">
|
||||
<p class="text-center text-blue-800">
|
||||
您的代理会员费用已退款,如有疑问请联系客服。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="paymentType === 'agent_upgrade'" class="action-buttons grid grid-cols-1 gap-4">
|
||||
<van-button block type="primary" class="rounded-lg" @click="contactService">
|
||||
联系客服
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 其他状态(待支付、失败、关闭) -->
|
||||
<div v-else class="other-result">
|
||||
<div class="info-animation mb-6">
|
||||
<van-icon :name="getStatusIcon" size="64" :color="getStatusColor" />
|
||||
<div class="info-ring" :class="getRingClass"></div>
|
||||
</div>
|
||||
|
||||
<h1 class="text-2xl font-bold text-center text-gray-800 mb-4">
|
||||
{{ statusText }}
|
||||
</h1>
|
||||
|
||||
<!-- 添加轮询状态提示 -->
|
||||
<div v-if="paymentStatus === 'pending'" class="text-center text-gray-500 mb-4">
|
||||
<p>正在等待支付结果,请稍候...</p>
|
||||
<p class="text-sm mt-1">
|
||||
已等待
|
||||
{{
|
||||
Math.floor(
|
||||
(pollingCount * getPollingInterval) / 1000
|
||||
)
|
||||
}}
|
||||
秒
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="payment-info bg-white rounded-lg shadow-md p-6 mb-6 w-full">
|
||||
<div class="flex justify-between mb-4">
|
||||
<span class="text-gray-600">订单编号</span>
|
||||
<span class="text-gray-800">{{ orderNo }}</span>
|
||||
</div>
|
||||
<div v-if="!isApiError" class="flex justify-between mb-4">
|
||||
<span class="text-gray-600">支付类型</span>
|
||||
<span class="text-gray-800">{{
|
||||
paymentType === "agent_upgrade"
|
||||
? "代理升级"
|
||||
: "查询服务"
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">订单状态</span>
|
||||
<span :class="getStatusTextClass">{{
|
||||
statusText
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="message-box p-4 bg-blue-50 rounded-lg mb-6">
|
||||
<p class="text-center" :class="getMessageClass">
|
||||
{{ statusMessage }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="action-buttons grid grid-cols-2 gap-4">
|
||||
<van-button block type="default" class="rounded-lg" @click="goHome">
|
||||
返回首页
|
||||
</van-button>
|
||||
<van-button block type="primary" class="rounded-lg" @click="contactService">
|
||||
联系客服
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
onBeforeMount,
|
||||
defineExpose,
|
||||
onBeforeUnmount,
|
||||
} from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useAgentStore } from "@/stores/agentStore";
|
||||
import { useUserStore } from "@/stores/userStore";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const orderNo = ref("");
|
||||
const agentStore = useAgentStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 状态变量
|
||||
const isLoading = ref(true);
|
||||
const paymentResult = ref(null);
|
||||
const paymentType = ref("");
|
||||
const paymentStatus = ref("");
|
||||
const isApiError = ref(false);
|
||||
const pollingInterval = ref(null);
|
||||
const pollingCount = ref(0);
|
||||
const maxPollingCount = 30; // 最大轮询次数
|
||||
const baseInterval = 2000; // 基础轮询间隔(2秒)
|
||||
|
||||
// 计算属性
|
||||
const statusText = computed(() => {
|
||||
if (isApiError.value) {
|
||||
return "系统繁忙";
|
||||
}
|
||||
|
||||
switch (paymentStatus.value) {
|
||||
case "pending":
|
||||
return "正在支付";
|
||||
case "failed":
|
||||
return "支付失败";
|
||||
case "closed":
|
||||
return "订单已关闭";
|
||||
default:
|
||||
return "处理中";
|
||||
}
|
||||
});
|
||||
|
||||
const statusMessage = computed(() => {
|
||||
if (isApiError.value) {
|
||||
return "系统正在维护或网络繁忙,请稍后再试,或联系客服确认订单状态。";
|
||||
}
|
||||
|
||||
switch (paymentStatus.value) {
|
||||
case "pending":
|
||||
return "您的订单正在支付,请稍后";
|
||||
case "failed":
|
||||
return "支付未成功,您可以返回重新支付,或联系客服确认详情。";
|
||||
case "closed":
|
||||
return "订单已关闭,如有疑问请联系客服。";
|
||||
default:
|
||||
return "系统正在处理您的订单,如有疑问请联系客服。";
|
||||
}
|
||||
});
|
||||
|
||||
// 状态图标
|
||||
const getStatusIcon = computed(() => {
|
||||
if (isApiError.value) {
|
||||
return "warning-o";
|
||||
}
|
||||
return paymentStatus.value === "pending" ? "clock-o" : "close";
|
||||
});
|
||||
|
||||
// 状态颜色
|
||||
const getStatusColor = computed(() => {
|
||||
if (isApiError.value) {
|
||||
return "#ff9800"; // 橙色警告
|
||||
}
|
||||
return paymentStatus.value === "pending" ? "#ff976a" : "#ee0a24";
|
||||
});
|
||||
|
||||
// 环形样式类
|
||||
const getRingClass = computed(() => {
|
||||
if (isApiError.value) {
|
||||
return "api-error-ring";
|
||||
}
|
||||
return {
|
||||
"pending-ring": paymentStatus.value === "pending",
|
||||
"failed-ring": paymentStatus.value === "failed",
|
||||
"closed-ring": paymentStatus.value === "closed",
|
||||
};
|
||||
});
|
||||
|
||||
// 状态文本样式
|
||||
const getStatusTextClass = computed(() => {
|
||||
if (isApiError.value) {
|
||||
return "text-amber-600";
|
||||
}
|
||||
return {
|
||||
"text-orange-500": paymentStatus.value === "pending",
|
||||
"text-red-500":
|
||||
paymentStatus.value === "failed" ||
|
||||
paymentStatus.value === "closed",
|
||||
};
|
||||
});
|
||||
|
||||
// 消息文本样式
|
||||
const getMessageClass = computed(() => {
|
||||
if (isApiError.value) {
|
||||
return "text-amber-800";
|
||||
}
|
||||
return {
|
||||
"text-orange-700": paymentStatus.value === "pending",
|
||||
"text-red-700":
|
||||
paymentStatus.value === "failed" ||
|
||||
paymentStatus.value === "closed",
|
||||
};
|
||||
});
|
||||
|
||||
// 计算轮询间隔时间(渐进式增加)
|
||||
const getPollingInterval = computed(() => {
|
||||
// 每5次轮询增加1秒,最大间隔10秒
|
||||
const increment = Math.floor(pollingCount.value / 5);
|
||||
return Math.min(baseInterval + increment * 1000, 10000);
|
||||
});
|
||||
|
||||
// 检查支付状态
|
||||
const checkPaymentStatus = async () => {
|
||||
if (pollingCount.value >= maxPollingCount) {
|
||||
// 超过最大轮询次数,停止轮询
|
||||
stopPolling();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { data, error } = await useApiFetch(`/pay/check`)
|
||||
.post({
|
||||
order_no: orderNo.value,
|
||||
})
|
||||
.json();
|
||||
|
||||
if (data.value && !error.value) {
|
||||
paymentResult.value = data.value.data;
|
||||
paymentType.value = data.value.data.type || "";
|
||||
const newStatus = data.value.data.status || "";
|
||||
|
||||
// 状态发生变化时更新
|
||||
if (paymentStatus.value !== newStatus) {
|
||||
paymentStatus.value = newStatus;
|
||||
|
||||
// 对于查询类型,如果状态是已支付或已退款,直接跳转
|
||||
if (
|
||||
paymentType.value === "query" &&
|
||||
(newStatus === "paid" || newStatus === "refunded")
|
||||
) {
|
||||
stopPolling();
|
||||
router.replace({
|
||||
path: "/report",
|
||||
query: { orderNo: orderNo.value },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果状态不是 pending,停止轮询
|
||||
if (newStatus !== "pending") {
|
||||
stopPolling();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error("API调用失败:", error.value);
|
||||
// 不要立即停止轮询,继续尝试
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("验证支付状态失败:", err);
|
||||
// 不要立即停止轮询,继续尝试
|
||||
} finally {
|
||||
pollingCount.value++;
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 开始轮询
|
||||
const startPolling = () => {
|
||||
if (pollingInterval.value) return;
|
||||
|
||||
pollingCount.value = 0;
|
||||
const poll = () => {
|
||||
checkPaymentStatus();
|
||||
if (
|
||||
paymentStatus.value === "pending" &&
|
||||
pollingCount.value < maxPollingCount
|
||||
) {
|
||||
pollingInterval.value = setTimeout(poll, getPollingInterval.value);
|
||||
}
|
||||
};
|
||||
poll();
|
||||
};
|
||||
|
||||
// 停止轮询
|
||||
const stopPolling = () => {
|
||||
if (pollingInterval.value) {
|
||||
clearTimeout(pollingInterval.value);
|
||||
pollingInterval.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
// 在组件挂载前验证支付结果
|
||||
onBeforeMount(async () => {
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
orderNo.value = query.get("out_trade_no");
|
||||
if (!orderNo.value) {
|
||||
orderNo.value = route.query.orderNo;
|
||||
}
|
||||
if (!orderNo.value) {
|
||||
router.push("/");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检测是否为 iOS 浏览器且没有上级页面
|
||||
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
|
||||
const hasHistory = window.history.length > 1;
|
||||
|
||||
if (isIOS && !hasHistory) {
|
||||
// iOS 浏览器且没有上级页面,关闭当前标签页
|
||||
window.close();
|
||||
return;
|
||||
}
|
||||
// 首次检查支付状态
|
||||
await checkPaymentStatus();
|
||||
|
||||
// 如果状态是 pending,开始轮询
|
||||
if (paymentStatus.value === "pending") {
|
||||
startPolling();
|
||||
}
|
||||
});
|
||||
|
||||
// 组件卸载前清理轮询
|
||||
onBeforeUnmount(() => {
|
||||
stopPolling();
|
||||
});
|
||||
|
||||
// 处理导航逻辑
|
||||
function handleNavigation() {
|
||||
if (paymentType.value === "agent_upgrade") {
|
||||
// 跳转到代理主页
|
||||
router.replace("/agent");
|
||||
agentStore.fetchAgentStatus();
|
||||
userStore.fetchUserInfo();
|
||||
} else {
|
||||
// 跳转到查询结果页面
|
||||
router.replace({
|
||||
path: "/report",
|
||||
query: { orderNo: orderNo.value },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 返回首页
|
||||
function goHome() {
|
||||
router.replace("/");
|
||||
}
|
||||
|
||||
// 联系客服
|
||||
const serviceUrl = import.meta.env.VITE_SERVICE_URL || '';
|
||||
function contactService() {
|
||||
// 可以替换为实际的客服联系逻辑,如打开聊天窗口或跳转到客服页面
|
||||
if (serviceUrl) {
|
||||
window.location.href = serviceUrl; // 跳转到客服页面
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露方法和数据供父组件或路由调用
|
||||
defineExpose({
|
||||
paymentResult,
|
||||
paymentType,
|
||||
paymentStatus,
|
||||
handleNavigation,
|
||||
stopPolling, // 暴露停止轮询方法
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.payment-result-container {
|
||||
min-height: 80vh;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.success-animation,
|
||||
.info-animation {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 2rem auto;
|
||||
}
|
||||
|
||||
.success-ring,
|
||||
.info-ring,
|
||||
.pending-ring,
|
||||
.failed-ring,
|
||||
.closed-ring,
|
||||
.api-error-ring {
|
||||
position: absolute;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
.success-ring {
|
||||
border: 2px solid #07c160;
|
||||
}
|
||||
|
||||
.info-ring {
|
||||
border: 2px solid #1989fa;
|
||||
}
|
||||
|
||||
.pending-ring {
|
||||
border: 2px solid #ff976a;
|
||||
}
|
||||
|
||||
.failed-ring,
|
||||
.closed-ring {
|
||||
border: 2px solid #ee0a24;
|
||||
}
|
||||
|
||||
.api-error-ring {
|
||||
border: 2px solid #ff9800;
|
||||
/* 橙色警告 */
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(0.95);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: scale(1.1);
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(0.95);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.success-result,
|
||||
.refund-result,
|
||||
.other-result {
|
||||
animation: fadeIn 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
462
src/views/PrivacyPolicy.vue
Normal file
462
src/views/PrivacyPolicy.vue
Normal file
@@ -0,0 +1,462 @@
|
||||
<script setup>
|
||||
// 从环境变量获取配置
|
||||
const companyName = import.meta.env.VITE_COMPANY_NAME || '';
|
||||
const appName = import.meta.env.VITE_APP_NAME || '{{ appName }}';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<!-- 页面标题 -->
|
||||
<div class="mb-4 text-center text-lg font-bold">隐私政策</div>
|
||||
|
||||
<!-- 内容主体 -->
|
||||
<div class="indent-[2em]">
|
||||
<div class="mb-4">
|
||||
<!-- 开篇说明 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">
|
||||
您的信任对我们非常重要
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
我们深知个人信息对您的重要性,我们将按法律法规要求,采取相应安全保护措施,尽力保护您的个人信息安全可控。
|
||||
有鉴于此,{{ companyName }}(以下简称"我们"或"{{ appName }}")作为{{ appName }}产品及服务的提供者制定本《隐私政策》(下称"本政策")并提醒您:
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
本政策适用于全部{{ appName }}产品及服务,如我们关联公司的产品或服务中使用了{{ appName }}提供的产品或服务但未设独立的隐私政策的,
|
||||
该部分{{ appName }}提供的产品或服务同样适用于本政策。
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
需要特别说明的是,本政策不适用于其他第三方通过网页或{{ appName }}客户端直接向您提供的服务(统称"第三方服务"),
|
||||
您向该第三方服务提供者提供的信息不适用于本政策,您在选择使用第三方服务前应充分了解第三方服务的产品功能及隐私保护政策,再选择是否开通功能。
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
在使用{{ appName }}产品或服务前,请您务必仔细阅读并透彻理解本政策,在确认充分理解使用相关产品或服务。
|
||||
一旦您开始使用{{ appName }}产品或服务,即表示您已充分理解并同意本政策。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-2 font-bold leading-relaxed">第一部分 定义</div>
|
||||
<div class="mb-4">
|
||||
<!-- 第一部分 -->
|
||||
<div class="leading-relaxed">
|
||||
<div>
|
||||
1、{{ appName }}服务提供者:是指研发并提供{{ appName }}产品和服务法律主体,{{ companyName }}(下称"我们"或"{{ appName }}")
|
||||
</div>
|
||||
<div>
|
||||
2、{{ appName }}用户:是指注册{{ appName }}账户的用户,以下称“您”。
|
||||
</div>
|
||||
<div>
|
||||
3、个人信息:指以电子或者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息。
|
||||
</div>
|
||||
<div>
|
||||
4、个人信息删除:指在实现日常业务功能所涉及的系统中去除个人信息的行为,使其保持不可被检索、访问的状态,具体指产品内的账号注销功能。
|
||||
</div>
|
||||
<div>
|
||||
5、个人信息匿名化:通过对个人信息的加密技术处理,使得个人信息主体无法被识别,且处理后的信息不能被复原的过程。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2 font-bold leading-relaxed">第二部分 隐私政策</div>
|
||||
<div class="mb-4">
|
||||
<!-- 第一部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">
|
||||
一、我们如何收集您的个人信息
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
为了向您及{{ appName }}企业用户提供{{ appName }}服务,维护{{ appName }}服务的正常运行,改进及优化我们的服务体验并保障您的账号安全,
|
||||
我们会出于本政策下述目的及方式收集您在注册、使用{{ appName }}服务时主动提供、授权提供或基于您使用{{ appName }}服务时产生的信息:
|
||||
</div>
|
||||
|
||||
<!-- 注册{{ appName }}用户信息 -->
|
||||
<div class="mb-4">
|
||||
<div class="mb-2 font-bold leading-relaxed">
|
||||
(一)注册{{ appName }}用户信息
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
为注册成为{{ appName }}用户,以便我们为您提供{{ appName }}服务,诸如数据查询、视频查看等功能,
|
||||
您需要提供您的手机号码及短信验证码以注册并创建{{ appName }}账号,否则您将不能使用{{ appName }}服务。
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
如果您仅需使用浏览、搜索{{ appName }}网页展示的产品、功能及服务介绍,您不需要注册成为{{ appName }}用户并提供上述信息。
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
如您的账号是注册在企业下的关联账号,当您所在企业用户注销{{ appName }}账户时,我们将会匿名化处理或删除您在该组织的相关个人信息,
|
||||
但您作为{{ appName }}个人用户的个人信息仍将保留,除非您主动注销{{ appName }}账户。
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
在经过用户授权同意的情况下,我司需要获取用户的手机号码以便开展相应业务。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 使用{{ appName }}服务过程中收集信息 -->
|
||||
<div class="mb-4">
|
||||
<div class="mb-2 font-bold leading-relaxed">
|
||||
(二)使用{{ appName }}服务过程中收集信息
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
当您在使用{{ appName }}服务过程中,为向您提供您需求的{{ appName }}软件服务、交互展示、搜索结果、识别账号异常状态,维护{{ appName }}服务的正常运行,改进及优化您对{{ appName }}服务的体验并保障您的账号安全,包括您使用{{ appName }}服务以及使用方式的信息,并将这些信息进行关联:
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
<div>1、日志信息:</div>
|
||||
<div>
|
||||
当您使用我们的网站或客户端提供的产品或服务时,我们会自动收集您对我们服务的详细使用情况,作为有关网络日志保存。例如您的搜索查询内容、IP地址、使用的语言、访问日期和时间、您访问的网页记录、日志信息。
|
||||
</div>
|
||||
<div>
|
||||
请注意,单独的设备信息、日志信息是无法识别特定自然人身份的信息。如果我们将这类非个人信息与其他信息结合用于识别特定自然人身份,或者将其与个人信息结合使用,则在结合使用期间,这类非个人信息将有可能被视为个人信息,除取得您授权或法律法规另有规定外,我们会将该类个人信息做匿名化、去标识化处理。
|
||||
</div>
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
<div>2、您向我们提供的信息:</div>
|
||||
<div>
|
||||
在服务使用过程中,特别是在申请提现、实名认证或佣金结算时,您需要提供包括但不限于姓名、身份证号、银行卡号、手机号、税务身份信息等个人资料。
|
||||
您同意我们为履行合同义务、税务申报、身份核验、财务结算等必要目的,收集、使用、存储并在必要范围内共享该等信息。
|
||||
在进行税务代扣代缴、结算服务时,我们有权将必要信息提供给依法合作的第三方税务服务商、结算服务商,前提是该第三方承担同等信息保护义务。
|
||||
</div>
|
||||
<div>
|
||||
您可以对{{ appName }}产品及服务的体验问题反馈,帮助我们更好地了解您使用我们产品或服务的体验和需求,改善我们产品或服务,为此我们会记录您的联系信息、反馈的问题或建议,以便我们进一步联系您反馈您我们的处理意见。
|
||||
为向您提供更好的服务,例如在不同的服务端或设备上提供体验一致的服务和您需求的客服接待,了解产品适配性,识别账号异常状态。
|
||||
</div>
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
<div>3、为您提供安全保障收集信息:</div>
|
||||
<div>
|
||||
为预防、发现、调查欺诈、侵权、危害安全、非法或违反与我们或与我们关联公司的协议、政策或规则的行为,我们可能收集或整合您的用户个人信息、服务使用信息、设备信息、日志信息以及我们关联公司、合作伙伴取得您授权或依据法律共享的信息。
|
||||
您理解并同意,我们向您提供的功能和服务场景是不断迭代升级的,如我们未在上述场景中明示您需要收集的个人信息,我们将会通过页面提示、交互设计等方式另行向您明示信息收集的内容、范围和目的并征得您同意。
|
||||
</div>
|
||||
<div>
|
||||
如我们停止运营{{ appName }}产品或服务,我们将及时停止继续收集您个人信息的活动,将停止运营的通知以公告或短信的形式通知您,并依照所适用的法律对所持有的您的个人信息进行删除或匿名化处理。
|
||||
</div>
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
<div>4、手机号码收集及其用途:</div>
|
||||
<div>
|
||||
在您使用{{ appName }}服务的过程中,我们可能会要求您提供手机号码。我们收集您的手机号码,主要是为了向您发送重要的通知、服务更新、账户安全信息、促销活动、服务相关的短信等。为了确保您能及时获得关于您账号安全、产品更新和优化、系统维护等信息,我们可能会向您发送有关服务变更、功能更新、版本升级等通知,确保您能够持续享受我们的产品和服务。
|
||||
</div>
|
||||
<div>
|
||||
此外,您的手机号码还可能用于为您提供个性化的短信推广内容,帮助您了解我们新推出的服务、产品或活动优惠。我们承诺,不会在未经您明确同意的情况下,将您的手机号码用于任何与服务相关以外的用途,且不会将您的信息出售或租赁给第三方。为了保障您的权益,您可以随时通过设置页面或联系客户服务停止接收短信通知或推广信息。如果您选择取消订阅短信通知或推广,您仍将继续收到与账户安全、系统通知等相关的重要信息。
|
||||
</div>
|
||||
<div>
|
||||
我们会采取严格的措施保护您的手机号码不被滥用,包括采用加密存储、定期审查访问权限等技术和管理手段,以确保您的个人信息安全。同时,我们也会根据适用的法律法规,在您停止使用我们的服务或终止您的账户时,删除或匿名化处理您的手机号码及其他相关信息。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<!-- 第二部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">
|
||||
二、我们如何使用信息
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
收集您的信息是为了向您提供服务及提升服务质量,为了实现这一目的,我们会把您的信息用于下列用途:
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
<div>
|
||||
(1)向您提供您使用的{{ appName }}产品或服务,并维护、改进、优化这些服务及服务体验;
|
||||
</div>
|
||||
<div>
|
||||
(2)为预防、发现、调查欺诈、侵权、危害安全、非法或违反与我们或与我们关联公司的协议、政策或规则的行为,保护您、其他用户或公众以及我们或我们关联公司的合法权益,我们会使用或整合您的个人信息、服务使用信息、设备信息、日志信息以及我们关联公司、合作伙伴取得您授权或依据法律共享的信息,来综合判断您的操作风险、检测及防范安全事件,并依法采取必要的记录、审计、分析、处置措施;
|
||||
</div>
|
||||
<div>(3)经您许可的其他用途。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- 第三部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">
|
||||
三、我们如何使用Cookie 和同类技术
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
为使您获得更轻松的访问体验,您使用{{ appName }}产品或服务时,我们可能会通过采用各种技术收集和存储您访问{{ appName }}服务的相关数据,
|
||||
在您访问或再次访问{{ appName }}服务时,我们能识别您的身份,并通过分析数据为您提供更好更多的服务。
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
包括使用小型数据文件识别您的身份,这么做是为了解您的使用习惯,帮您省去重复输入账户信息的步骤,或者帮助判断您的账户安全。
|
||||
这些数据文件可能是Cookie、Flash
|
||||
Cookie,或您的浏览器或关联应用程序提供的其他本地存储(统称“Cookie”)。
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
请您理解,我们的某些服务只能通过使用Cookie才可得到实现。如果您的浏览器或浏览器附加服务允许,
|
||||
您可以修改对Cookie的接受程度或者拒绝{{ appName }}的Cookie,但拒绝{{ appName }}的Cookie在某些情况下您可能无法使用依赖于cookies的{{ appName }}服务的部分功能。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- 第四部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">
|
||||
四、我们如何共享、转让、公开披露您的信息
|
||||
</div>
|
||||
|
||||
<!-- 共享 -->
|
||||
<div class="mb-2 font-semibold">(一) 共享</div>
|
||||
<div class="leading-relaxed">
|
||||
我们不会和其他公司、组织和个人共享您的个人信息,但以下情况除外:
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
<div>
|
||||
(1)在获取您同意的情况下共享:获得您的明确同意后,我们会与其他方共享您的个人信息。
|
||||
</div>
|
||||
<div>
|
||||
(2)在法定情形下的共享:我们可能会根据法律法规规定、诉讼争议解决需要,或按行政、司法机关依法提出的要求,对外共享您的个人信息。
|
||||
</div>
|
||||
<div>
|
||||
(3)只有透露您的资料,才能提供您所要求的第三方产品和服务,在您通过{{ appName }}客户端购买查询服务的,您同意{{ appName }}向实际产品提供者提供您的身份信息,包括真实姓名和身份证号等。为了提升实人认证的准确性,您同意第三方公司仅限于个人信息进行验证相关服务,将您提供的个人信息与法律法规允许的机构或政府机关授权的机构的数据进行校验。
|
||||
</div>
|
||||
<div>
|
||||
(4)在您被他人投诉侵犯知识产权或其他合法权利时,需要向投诉人披露您的必要资料,以便进行投诉处理的;
|
||||
</div>
|
||||
<div>
|
||||
(5){{ appName }}服务可能含有其他网站的链接。除法律另有规定外,{{ appName }}对其他网站的隐私保护措施不负相应法律责任。我们可能在需要的时候增加商业伙伴,但是提供给他们的将仅是综合信息,我们将不会公开您的个人信息。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 转让 -->
|
||||
<div class="mb-2 font-semibold">(二) 转让</div>
|
||||
<div class="leading-relaxed">
|
||||
我们不会将您的个人信息转让给任何公司、组织和个人,但以下情况除外:
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
<div>
|
||||
(1)在获取明确同意的情况下转让:获得您的明确同意后,我们会向其他方转让您的个人信息。
|
||||
</div>
|
||||
<div>
|
||||
(2)在{{ appName }}发生合并、收购或破产清算情形,或其他涉及合并、收购或破产清算情形时,如涉及到个人信息转让,我们会要求新的持有您个人信息的公司、组织继续受本政策的约束,否则我们将要求该公司、组织和个人重新向您征求授权同意。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 公开披露 -->
|
||||
<div class="mb-2 font-semibold">(三) 公开披露</div>
|
||||
<div class="leading-relaxed">
|
||||
我们仅会在以下情况下,公开披露您的个人信息:
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
<div>
|
||||
(1)获得您明确同意或基于您的主动选择,我们可能会公开披露您的个人信息;
|
||||
</div>
|
||||
<div>
|
||||
(2)如果我们确定您出现违反法律法规或严重违反{{ appName }}相关协议规则的情况,或为保护{{ appName }}及其关联公司用户或公众的人身财产安全免遭侵害,我们可能依据法律法规或{{ appName }}相关协议规则征得您同意的情况下披露关于您的个人信息,包括相关违规行为以及{{ appName }}已对您采取的措施。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 特殊情况 -->
|
||||
<div class="mb-2 font-semibold">
|
||||
(四)
|
||||
共享、转让、公开披露个人信息时事先征得授权同意的例外
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
以下情形中,共享、转让、公开披露您的个人信息无需事先征得您的授权同意:
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
<div>(1)与国家安全、国防安全有关的;</div>
|
||||
<div>
|
||||
(2)与公共安全、公共卫生、重大公共利益有关的;
|
||||
</div>
|
||||
<div>
|
||||
(3)与犯罪侦查、起诉、审判和判决执行等有关的;
|
||||
</div>
|
||||
<div>
|
||||
(4)出于维护您或其他个人的生命、财产等重大合法权益但又很难得到本人同意的;
|
||||
</div>
|
||||
<div>(5)您自行向社会公众公开的个人信息;</div>
|
||||
<div>
|
||||
(6)从合法公开披露的信息中收集个人信息的,如合法的新闻报道、政府信息公开等渠道。
|
||||
请您注意,根据法律规定,共享、转让经匿名化处理的个人信息,且确保数据接收方无法复原并重新识别个人信息主体的,不属于个人信息的对外共享、转让及公开披露行为,对此类数据的保存及处理将无需另行向您通知并征得您的同意。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<!-- 第五部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">
|
||||
五、我们如何保护您的信息
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
我们会采取各种预防措施来保护您的个人信息,以保障您的个人信息免遭丢失、盗用和误用,以及被擅自取阅、披露、更改或销毁。
|
||||
为确保您个人信息的安全,我们有严格的信息安全规定和流程并严格执行上述措施。
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
{{ appName }}建立了全方位、多维度的数据安全管理体系,保证整个{{ appName }}各个平台的安全性。
|
||||
我们会采取合理可行的措施,尽力避免收集无关的个人信息,
|
||||
并在限于达成本政策所述目的所需的期限以及所适用法律法规所要求的期限内对您的个人信息进行脱敏处理。
|
||||
在您使用查询过程中所涉及的用户姓名、身份证号、手机号/账号密码信息均采用的是AES加密方式,
|
||||
所有二次输出信息均经过脱敏处理,数据库文件不存储用户明文数据。
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
在不幸发生个人信息安全事件后,我们将按照法律法规的要求(最迟不迟于30个自然日内)向您告知:
|
||||
安全事件的基本情况和可能的影响、我们已采取或将要采取的处置措施、您可自主防范和降低风险的建议、对您的补救措施等。
|
||||
事件相关情况我们将以邮件、信函、电话通知等方式告知您,
|
||||
难以逐一告知个人信息主体时,我们会采取合理、有效的方式发布公告。
|
||||
同时,我们还将按照监管部门要求,上报个人信息安全事件的处置情况。
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
互联网环境并非百分之百安全,尽管我们有这些安全措施,但仍然无法完全避免互联网中存在的各种风险,我们将尽力确保您的信息的安全性。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- 第六部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">
|
||||
六、未成年人保护
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
我们重视未成年人的信息保护,如您为未成年人的,建议您请您的父母或监护人仔细阅读本隐私权政策,
|
||||
并在征得您的父母或监护人同意的前提下使用我们的服务或向我们提供信息。
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
对于经父母或监护人同意使用我们的产品或服务而收集未成年人个人信息的情况,
|
||||
我们只会在法律法规允许,父母或监护人明确同意或者保护未成年人所必要的情况下使用、共享、转让或披露此信息。
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
我们将根据国家相关法律法规及本政策的规定保护未成年人的个人信息。
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<!-- 第七部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">
|
||||
七、您的个人信息存储
|
||||
</div>
|
||||
<!-- 存储地区 -->
|
||||
<div class="mb-2 font-semibold">(一) 存储地区</div>
|
||||
<div class="leading-relaxed">
|
||||
我们将在中华人民共和国境内运营{{ appName }}服务中收集和产生的个人信息存储在中华人民共和国境内。
|
||||
目前,我们不会将上述信息传输至境外,如果我们向境外传输,我们将会遵循相关国家规定或者征求您的同意。
|
||||
</div>
|
||||
<!-- 存储期限 -->
|
||||
<div class="mb-2 font-semibold">(二) 存储期限</div>
|
||||
<div class="leading-relaxed">
|
||||
您在使用本平台期间,我们将保存您的个人脱敏加密信息,保存期限将以不超过为您提供服务所必须的期间为原则。
|
||||
在您终止使用本平台后,除法律法规对于特定信息保留期限另有规定外,我们会对您的信息进行删除或做匿名化处理。
|
||||
如我们停止运营本平台服务,我们将在合理期限内依照所适用的法律对所持有的您的个人信息进行删除或匿名化处理。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- 第八部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">
|
||||
八、您享有的权利及权利行使路径
|
||||
</div>
|
||||
<!-- 访问查询权 -->
|
||||
<div class="mb-2 font-semibold">(一) 访问查询权</div>
|
||||
<div class="leading-relaxed">
|
||||
您对您的{{ appName }}账号内的信息(含个人信息)依法享有访问查询权,包括:
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
<div>
|
||||
<span class="font-semibold">账户信息:</span>
|
||||
您可以登录手机客户端,通过【我的-点击名字或头像】可以访问您的头像信息、姓名、绑定手机号。
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-semibold">使用信息:</span>
|
||||
您可以在{{ appName }}手机客户端相关页面访问、查询您的使用信息,包括订单信息,
|
||||
可以通过【报告列表-查看详情】进行访问、查看。
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-semibold">其他信息:</span>
|
||||
如您在此前述过程中遇到操作问题的或如需获取其他前述无法获知的个人信息内容,
|
||||
您可通过在线客服或邮箱联系我们,我们将在核实您的身份后在合理期限内向您提供,
|
||||
但法律法规另有规定的或本政策另有约定的除外。
|
||||
</div>
|
||||
</div>
|
||||
<!-- 同意的撤回与变更 -->
|
||||
<div class="mb-2 font-semibold">(二) 同意的撤回与变更</div>
|
||||
<div class="leading-relaxed">
|
||||
若您需要更改相关权限的授权(例如:相机、相册、麦克风),您可以通过您的硬件设备进行修改。
|
||||
您也可以通过注销{{ appName }}账户的方式永久撤回我们继续收集您个人信息的全部授权。
|
||||
如您在此过程中遇到操作问题的,可以通过本政策“帮助中心”方式联系我们。
|
||||
</div>
|
||||
<!-- 帮助反馈权 -->
|
||||
<div class="mb-2 font-semibold">(三) 帮助反馈权</div>
|
||||
<div class="leading-relaxed">
|
||||
我们为您提供了多种反馈渠道,具体请见设置—帮助中心。
|
||||
</div>
|
||||
<!-- 提前获知产品与/或服务停止运营权 -->
|
||||
<div class="mb-2 font-semibold">
|
||||
(四) 提前获知产品与/或服务停止运营权
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
我们将持续为您提供优质服务,若因特殊原因导致我们的部分或全部产品与/或服务被迫停止运营,
|
||||
我们将提前在显著位置或通知您,并将停止对您个人信息的收集,
|
||||
同时在超出法律法规规定的必需且最短期限后,我们将会对所持有的您的个人信息进行删除或匿名化处理。
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<!-- 第九部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">
|
||||
九、本政策如何更新
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
我们的隐私政策可能变更。
|
||||
未经您明确同意我们不会限制您按照本隐私政策所应享有的权利。
|
||||
我们会在{{ appName }}各个平台,包括客户端、相关网页上以首页弹窗形式发布对本隐私政策所做的任何变更,并以交互设计提醒您阅读并完整理解。
|
||||
对于重大变更,我们还会提供更为显著的通知(可能包括公告通知甚至向您提供弹窗提示)。
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
本政策所指的重大变更包括但不限于:
|
||||
<div>
|
||||
(1)我们的服务模式发生重大变化。如处理用户信息的目的、用户信息的使用方式等;
|
||||
</div>
|
||||
<div>
|
||||
(2)我们在控制权、组织架构等方面发生重大变化。如业务调整、破产并购等引起的所有者变更等;
|
||||
</div>
|
||||
<div>
|
||||
(3)用户信息共享、转让或公开披露的主要对象发生变化;
|
||||
</div>
|
||||
<div>
|
||||
(4)我们负责处理用户信息安全的责任部门、联络方式及投诉渠道发生变化时;
|
||||
</div>
|
||||
<div>
|
||||
(5)用户信息安全影响评估报告表明存在高风险时。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- 第十部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">
|
||||
十、如何联系我们
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
如果您对本政策或数据处理有任何疑问、意见或建议,可以通过{{ appName }}产品内的“联系客服”或邮箱
|
||||
<text class="text-blue-500"> admin@iieeii.com </text>
|
||||
与我们联系。我们将在收到您发送的响应请求或相关信息之日起十五(15)天内回复您。
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
您理解并同意,当涉及以下任一情形时,我们无法响应您的请求:
|
||||
<div>(1)与国家安全、国防安全有关的;</div>
|
||||
<div>
|
||||
(2)与公共安全、公共卫生、重大公共利益有关的;
|
||||
</div>
|
||||
<div>(3)与犯罪侦查、起诉和审判等有关的;</div>
|
||||
<div>
|
||||
(4)有充分证据表明您存在主观恶意或滥用权利的;
|
||||
</div>
|
||||
<div>
|
||||
(5)响应您的请求将导致您或其他个人、组织的合法权益受到严重损害的;
|
||||
</div>
|
||||
<div>(6)涉及{{ appName }}或任何第三方主体商业秘密的;</div>
|
||||
<div>(7)法律法规规定的其他情形。</div>
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
如果您对我们的回复不满意,特别是您认为我们的个人信息处理行为损害了您的合法权益,
|
||||
您还可以通过向有管辖权的法院提起诉讼来寻求解决方案。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- 第十一部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">十一、其他</div>
|
||||
<div class="leading-relaxed">
|
||||
(一)本《隐私政策》的解释及争议解决均应适用中华人民共和国大陆地区法律。
|
||||
与本《隐私政策》相关的任何纠纷,双方应协商友好解决;若不能协商解决,
|
||||
应将争议提交至{{ companyName }}注册地有管辖权的人民法院解决。
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
(二)本《隐私政策》的标题仅为方便及阅读而设,并不影响正文其中任何规定的含义或解释。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-right text-sm text-gray-600">
|
||||
<text>2024年11月19日</text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
604
src/views/Promote.vue
Normal file
604
src/views/Promote.vue
Normal file
@@ -0,0 +1,604 @@
|
||||
<template>
|
||||
<div class="min-h-screen promote">
|
||||
<!-- 顶部背景图 -->
|
||||
<div class="promote-header-bg">
|
||||
<img src="@/assets/images/promote/promote_bg.jpg" alt="推广背景" class="bg-image" />
|
||||
</div>
|
||||
|
||||
<div class="p-4">
|
||||
<!-- 价格设置卡片 -->
|
||||
<div class="card mb-4 card-with-header">
|
||||
<!-- 优雅的报告头部 -->
|
||||
<div class="report-header-elegant">
|
||||
<div class="report-header-content">
|
||||
<div class="report-logo-container" v-if="currentLogo">
|
||||
<div class="report-logo-wrapper">
|
||||
<img :src="currentLogo" :alt="pickerFieldText" class="report-logo-elegant" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="report-info-elegant">
|
||||
<div class="report-label-elegant">推广报告</div>
|
||||
<div class="report-title-elegant">{{ pickerFieldText || '请选择报告类型' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="report-action-elegant" @click="showTypePicker = true">
|
||||
<div class="report-action-icon">
|
||||
<van-icon name="exchange" size="16" color="#ffffff" />
|
||||
</div>
|
||||
<span class="report-action-text">切换</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4 pt-6">
|
||||
<!-- 当前价格显示 -->
|
||||
<div class="flex items-center justify-between py-3 border-b border-gray-100">
|
||||
<label class="font-medium text-gray-700">客户查询价</label>
|
||||
<span class="text-lg font-semibold text-orange-500">¥{{ clientPrice || '0.00' }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 成本和收益信息 -->
|
||||
<div class="bg-gray-50 rounded-lg p-3 space-y-2">
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm text-gray-600">底价成本</span>
|
||||
<span class="text-sm font-semibold text-orange-500">¥{{ baseCost }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm text-gray-600">提价成本</span>
|
||||
<span class="text-sm font-semibold text-orange-500">¥{{ raiseCost }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm text-gray-600">推广收益</span>
|
||||
<span class="text-sm font-semibold text-orange-500">¥{{ promotionRevenue }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮组 -->
|
||||
<div class="grid grid-cols-3 gap-3 mt-4">
|
||||
<van-button type="primary" size="large" class="custom-btn-primary"
|
||||
@click="showPricePicker = true">设置价格</van-button>
|
||||
<van-button type="primary" size="large" class="custom-btn-primary"
|
||||
@click="generatePromotionCode" :disabled="!clientPrice || !currentFeature">推广报告</van-button>
|
||||
<van-button type="default" size="large" class="custom-btn-default" @click="toExample"
|
||||
:disabled="!currentFeature">示例报告</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 报告信息卡片 -->
|
||||
<div v-if="featureData.product_name" class="card mb-4">
|
||||
<ReportFeatures :features="featureData.features" />
|
||||
</div>
|
||||
|
||||
<!-- 报告类型选择弹窗 -->
|
||||
<van-popup v-model:show="showTypePicker" destroy-on-close round position="bottom">
|
||||
<van-picker :model-value="selectedReportType" :columns="reportTypes" @cancel="showTypePicker = false"
|
||||
@confirm="onConfirmType" />
|
||||
</van-popup>
|
||||
|
||||
<!-- 价格设置弹窗 -->
|
||||
<PriceInputPopup v-model:show="showPricePicker" :default-price="clientPrice"
|
||||
:product-config="pickerProductConfig" @change="onPriceChange" />
|
||||
|
||||
<!-- 二维码弹窗 -->
|
||||
<QRcode v-model:show="showQRcode" :fullLink="fullLink" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import PriceInputPopup from '@/components/PriceInputPopup.vue';
|
||||
import QRcode from '@/components/QRcode.vue';
|
||||
import ReportFeatures from '@/components/ReportFeatures.vue';
|
||||
import { getProductConfig, generateLink } from '@/api/agent';
|
||||
|
||||
// 导入logo图片
|
||||
import personalDataLogo from '@/assets/images/promote/personal_data_logo.png';
|
||||
import companyLogo from '@/assets/images/promote/company_logo.png';
|
||||
import preloanBackgroundCheckLogo from '@/assets/images/promote/preloan_background_check_logo.png';
|
||||
import marriageRiskLogo from '@/assets/images/promote/marriage_risk_logo.png';
|
||||
import housekeepingRiskLogo from '@/assets/images/promote/housekeeping_risk_logo.png';
|
||||
import backgroundcheckLogo from '@/assets/images/promote/backgroundcheck_logo.png';
|
||||
import consumerFinanceReportLogo from '@/assets/images/promote/consumer_finance_report_logo.png';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
// 报告类型配置(从接口获取)
|
||||
const reportTypes = ref([]);
|
||||
|
||||
// 从 query 参数获取报告类型(使用 computed 以响应路由变化)
|
||||
const currentFeature = computed(() => route.query.feature || '');
|
||||
const showPricePicker = ref(false);
|
||||
const showTypePicker = ref(false);
|
||||
const pickerFieldText = ref('');
|
||||
const selectedReportType = ref([]);
|
||||
const pickerProductConfig = ref(null);
|
||||
const pickerFieldVal = ref(null); // 保持原来的变量名,用于存储报告类型的 value
|
||||
const clientPrice = ref(null);
|
||||
const productConfig = ref(null);
|
||||
const fullLink = ref(""); // 完整的推广短链
|
||||
const featureData = ref({});
|
||||
const showQRcode = ref(false);
|
||||
|
||||
// Logo映射
|
||||
const logoMap = {
|
||||
'riskassessment': personalDataLogo,
|
||||
'companyinfo': companyLogo,
|
||||
'preloanbackgroundcheck': preloanBackgroundCheckLogo,
|
||||
'marriage': marriageRiskLogo,
|
||||
'homeservice': housekeepingRiskLogo,
|
||||
'backgroundcheck': backgroundcheckLogo,
|
||||
'consumerFinanceReport': consumerFinanceReportLogo,
|
||||
};
|
||||
|
||||
// 当前报告的logo
|
||||
const currentLogo = computed(() => {
|
||||
if (!currentFeature.value) return null;
|
||||
return logoMap[currentFeature.value] || null;
|
||||
});
|
||||
|
||||
const costPrice = computed(() => {
|
||||
if (!pickerProductConfig.value) return 0.00
|
||||
|
||||
// 新系统:成本价 = 实际底价(actual_base_price)
|
||||
// actual_base_price = base_price + 等级加成
|
||||
const actualBasePrice = Number(pickerProductConfig.value.actual_base_price) || 0;
|
||||
const clientPriceNum = Number(clientPrice.value) || 0;
|
||||
const priceThreshold = Number(pickerProductConfig.value.price_threshold) || 0;
|
||||
const priceFeeRate = Number(pickerProductConfig.value.price_fee_rate) || 0;
|
||||
|
||||
// 计算提价成本
|
||||
let priceCost = 0;
|
||||
if (clientPriceNum > priceThreshold) {
|
||||
priceCost = (clientPriceNum - priceThreshold) * priceFeeRate;
|
||||
}
|
||||
|
||||
// 总成本 = 实际底价 + 提价成本
|
||||
const totalCost = actualBasePrice + priceCost;
|
||||
|
||||
return safeTruncate(totalCost);
|
||||
});
|
||||
|
||||
const baseCost = computed(() => {
|
||||
if (!pickerProductConfig.value) return "0.00";
|
||||
const actualBasePrice = Number(pickerProductConfig.value.actual_base_price) || 0;
|
||||
return safeTruncate(actualBasePrice);
|
||||
});
|
||||
|
||||
const raiseCost = computed(() => {
|
||||
if (!pickerProductConfig.value) return "0.00";
|
||||
const clientPriceNum = Number(clientPrice.value) || 0;
|
||||
const priceThreshold = Number(pickerProductConfig.value.price_threshold) || 0;
|
||||
const priceFeeRate = Number(pickerProductConfig.value.price_fee_rate) || 0;
|
||||
let priceCost = 0;
|
||||
if (clientPriceNum > priceThreshold) {
|
||||
priceCost = (clientPriceNum - priceThreshold) * priceFeeRate;
|
||||
}
|
||||
return safeTruncate(priceCost);
|
||||
});
|
||||
|
||||
const promotionRevenue = computed(() => {
|
||||
const clientPriceNum = Number(clientPrice.value) || 0;
|
||||
const costPriceNum = parseFloat(costPrice.value) || 0; // costPrice 返回字符串,需要转换为数字
|
||||
const revenue = clientPriceNum - costPriceNum;
|
||||
return safeTruncate(revenue >= 0 ? revenue : 0); // 确保收益不为负数
|
||||
});
|
||||
|
||||
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 getProductInfo = async () => {
|
||||
if (!currentFeature.value) {
|
||||
console.warn('No feature parameter found in query');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { data, error } = await useApiFetch(`/product/en/${currentFeature.value}`)
|
||||
.get()
|
||||
.json();
|
||||
|
||||
if (data.value && !error.value) {
|
||||
featureData.value = data.value.data;
|
||||
// 确保 FLXG0V4B 排在首位
|
||||
if (featureData.value.features && featureData.value.features.length > 0) {
|
||||
featureData.value.features.sort((a, b) => {
|
||||
if (a.api_id === "FLXG0V4B") return -1;
|
||||
if (b.api_id === "FLXG0V4B") return 1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch product info:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// 根据 feature 找到对应的报告类型(保持原来的数据结构匹配方式)
|
||||
const findReportTypeByFeature = (feature) => {
|
||||
return reportTypes.value.find(type => type.value === feature);
|
||||
};
|
||||
|
||||
// 选择报告类型并设置配置
|
||||
const SelectTypePicker = (reportType) => {
|
||||
if (!reportType) return;
|
||||
|
||||
// 保持原来的变量赋值方式
|
||||
pickerFieldVal.value = reportType.value;
|
||||
pickerFieldText.value = reportType.text;
|
||||
selectedReportType.value = [reportType];
|
||||
|
||||
// 如果产品配置已加载,则设置配置
|
||||
if (productConfig.value) {
|
||||
// 遍历产品配置,找到匹配的产品(根据 product_en 匹配)
|
||||
for (let i of productConfig.value) {
|
||||
if (i.product_en === reportType.value) {
|
||||
pickerProductConfig.value = i;
|
||||
// 新系统:初始价格设置为实际底价(成本价)
|
||||
clientPrice.value = Number(i.actual_base_price) || Number(i.price_range_min) || 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新路由参数
|
||||
router.replace({ query: { ...route.query, feature: reportType.value } });
|
||||
|
||||
// 重新获取产品信息
|
||||
getProductInfo();
|
||||
};
|
||||
|
||||
// 获取产品配置
|
||||
const getPromoteConfig = async () => {
|
||||
const { data, error } = await getProductConfig();
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
// 新系统数据结构:data.value.data.list 是数组
|
||||
productConfig.value = data.value.data.list || [];
|
||||
|
||||
// 根据接口返回的产品列表,生成报告类型配置
|
||||
const types = [];
|
||||
productConfig.value.forEach(config => {
|
||||
if (config.product_en) {
|
||||
types.push({
|
||||
text: config.product_name,
|
||||
value: config.product_en,
|
||||
id: config.product_id,
|
||||
});
|
||||
}
|
||||
});
|
||||
reportTypes.value = types;
|
||||
|
||||
// 根据当前 feature 找到对应的报告类型,然后设置配置
|
||||
// 如果没有 feature 参数,默认选择第一个报告类型
|
||||
let reportType;
|
||||
if (currentFeature.value) {
|
||||
reportType = findReportTypeByFeature(currentFeature.value);
|
||||
}
|
||||
|
||||
// 如果没有找到匹配的报告类型或没有feature参数,使用第一个报告类型
|
||||
if (!reportType && reportTypes.value.length > 0) {
|
||||
reportType = reportTypes.value[0];
|
||||
}
|
||||
|
||||
if (reportType) {
|
||||
SelectTypePicker(reportType);
|
||||
}
|
||||
} else {
|
||||
console.log("Error fetching product config", data.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const generatePromotionCode = async () => {
|
||||
if (!pickerFieldVal.value || !pickerProductConfig.value) {
|
||||
showToast({ message: '请选择报告类型' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 确保 price 是有效的数字
|
||||
const priceNum = Number(clientPrice.value);
|
||||
if (isNaN(priceNum) || priceNum <= 0) {
|
||||
showToast({ message: '请输入有效的查询价格' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证价格范围
|
||||
const minPrice = Number(pickerProductConfig.value.price_range_min) || 0;
|
||||
const maxPrice = Number(pickerProductConfig.value.price_range_max) || Infinity;
|
||||
|
||||
if (priceNum < minPrice) {
|
||||
showToast({ message: `价格不能低于 ${minPrice.toFixed(2)} 元` });
|
||||
return;
|
||||
}
|
||||
|
||||
if (priceNum > maxPrice) {
|
||||
showToast({ message: `价格不能高于 ${maxPrice.toFixed(2)} 元` });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建目标路径模板:推广报告页面路径(后端会将 linkIdentifier 拼接到路径中)
|
||||
// 注意:后端会在重定向时自动将 linkIdentifier 拼接到 target_path 后面
|
||||
const targetPath = `/agent/promotionInquire/`;
|
||||
|
||||
// 新系统API:使用 product_id、set_price 和 target_path
|
||||
const { data, error } = await generateLink({
|
||||
product_id: pickerProductConfig.value.product_id,
|
||||
set_price: priceNum,
|
||||
target_path: targetPath
|
||||
});
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
// 使用后端返回的完整短链
|
||||
fullLink.value = data.value.data.full_link || "";
|
||||
showQRcode.value = true;
|
||||
} else {
|
||||
console.log("Error generating promotion link", data.value);
|
||||
showToast({ message: data.value.msg || '生成推广链接失败,请重试' });
|
||||
}
|
||||
} else {
|
||||
showToast({ message: '生成推广链接失败,请重试' });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('生成推广链接失败:', err);
|
||||
showToast({ message: '生成推广链接失败,请重试' });
|
||||
}
|
||||
};
|
||||
|
||||
const onPriceChange = (price) => {
|
||||
// 确保接收的价格是数字类型
|
||||
const priceNum = Number(price);
|
||||
if (!isNaN(priceNum) && isFinite(priceNum)) {
|
||||
clientPrice.value = priceNum;
|
||||
}
|
||||
};
|
||||
|
||||
// 确认选择报告类型
|
||||
const onConfirmType = ({ selectedValues, selectedOptions }) => {
|
||||
if (selectedOptions && selectedOptions.length > 0) {
|
||||
SelectTypePicker(selectedOptions[0]);
|
||||
}
|
||||
showTypePicker.value = false;
|
||||
};
|
||||
|
||||
// 跳转到示例报告
|
||||
const toExample = () => {
|
||||
if (!currentFeature.value) return;
|
||||
router.push({ path: '/example', query: { feature: currentFeature.value } });
|
||||
};
|
||||
|
||||
|
||||
// 监听路由变化,重新加载数据
|
||||
watch(() => route.query.feature, async (newFeature) => {
|
||||
if (newFeature) {
|
||||
await Promise.all([getPromoteConfig(), getProductInfo()]);
|
||||
}
|
||||
}, { immediate: false });
|
||||
|
||||
onMounted(async () => {
|
||||
await getPromoteConfig();
|
||||
// getProductInfo 会在 SelectTypePicker 中调用
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.promote {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.promote-header-bg {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bg-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.card {
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
border-radius: 0.75rem;
|
||||
padding: 1.5rem;
|
||||
transition: all 0.3s ease;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
box-shadow:
|
||||
0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
||||
0 2px 4px -1px rgba(0, 0, 0, 0.06),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.card-with-header {
|
||||
position: relative;
|
||||
padding-top: 3rem;
|
||||
margin-top: -4rem;
|
||||
}
|
||||
|
||||
/* 优雅的报告头部设计 */
|
||||
.report-header-elegant {
|
||||
position: absolute;
|
||||
top: -2.5rem;
|
||||
left: 1rem;
|
||||
right: 1rem;
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.98) 0%, rgba(249, 250, 251, 0.98) 100%);
|
||||
border-radius: 1.25rem;
|
||||
padding: 1rem 1.5rem;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.report-header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.report-logo-container {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.report-logo-wrapper {
|
||||
position: relative;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.15) 0%, rgba(37, 99, 235, 0.15) 100%);
|
||||
border-radius: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.5rem;
|
||||
box-shadow:
|
||||
0 4px 12px rgba(59, 130, 246, 0.2),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.report-logo-elegant {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
|
||||
}
|
||||
|
||||
.report-info-elegant {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.report-label-elegant {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: #6b7280;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 0.25rem;
|
||||
text-transform: uppercase;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.report-title-elegant {
|
||||
font-size: 1.375rem;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
line-height: 1.3;
|
||||
letter-spacing: -0.01em;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.report-action-elegant {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0 0.875rem;
|
||||
border-radius: 0.75rem;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
box-shadow: 0 2px 8px rgba(100, 181, 246, 0.25);
|
||||
background: linear-gradient(135deg, #64b5f6 0%, #42a5f5 100%);
|
||||
border: none;
|
||||
height: 36px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.report-action-elegant:active {
|
||||
box-shadow: 0 1px 4px rgba(100, 181, 246, 0.2);
|
||||
}
|
||||
|
||||
.report-action-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.report-action-text {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
/* 精致按钮样式 */
|
||||
:deep(.custom-btn-small) {
|
||||
border-radius: 0.5rem !important;
|
||||
font-weight: 500 !important;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08) !important;
|
||||
transition: all 0.3s ease !important;
|
||||
padding: 0 16px !important;
|
||||
height: 32px !important;
|
||||
}
|
||||
|
||||
:deep(.custom-btn-small:active) {
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12) !important;
|
||||
}
|
||||
|
||||
:deep(.custom-btn-primary) {
|
||||
border-radius: 0.75rem !important;
|
||||
font-weight: 600 !important;
|
||||
font-size: 15px !important;
|
||||
box-shadow: 0 2px 8px rgba(100, 181, 246, 0.25) !important;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
background: linear-gradient(135deg, #64b5f6 0%, #42a5f5 100%) !important;
|
||||
border: none !important;
|
||||
height: 40px !important;
|
||||
}
|
||||
|
||||
:deep(.custom-btn-primary:not(.van-button--disabled):active) {
|
||||
box-shadow: 0 1px 4px rgba(100, 181, 246, 0.2) !important;
|
||||
}
|
||||
|
||||
:deep(.custom-btn-primary.van-button--disabled) {
|
||||
background: linear-gradient(135deg, #e0e0e0 0%, #bdbdbd 100%) !important;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08) !important;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
:deep(.custom-btn-default) {
|
||||
border-radius: 0.75rem !important;
|
||||
font-weight: 600 !important;
|
||||
font-size: 15px !important;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06) !important;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%) !important;
|
||||
border: 1px solid #e0e0e0 !important;
|
||||
color: #616161 !important;
|
||||
height: 40px !important;
|
||||
}
|
||||
|
||||
:deep(.custom-btn-default:not(.van-button--disabled):active) {
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) !important;
|
||||
background: linear-gradient(135deg, #fafafa 0%, #f5f5f5 100%) !important;
|
||||
}
|
||||
|
||||
:deep(.custom-btn-default.van-button--disabled) {
|
||||
background: linear-gradient(135deg, #fafafa 0%, #f5f5f5 100%) !important;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04) !important;
|
||||
opacity: 0.4 !important;
|
||||
color: #bdbdbd !important;
|
||||
}
|
||||
</style>
|
||||
425
src/views/PromotePage.vue
Normal file
425
src/views/PromotePage.vue
Normal file
@@ -0,0 +1,425 @@
|
||||
<script setup>
|
||||
const router = useRouter();
|
||||
import { storeToRefs } from "pinia";
|
||||
import SectionTitle from "@/components/SectionTitle.vue";
|
||||
import { showToast } from "vant";
|
||||
|
||||
const agentStore = useAgentStore();
|
||||
const { isAgent } = storeToRefs(agentStore);
|
||||
const userStore = useUserStore();
|
||||
const { isLoggedIn, mobile } = storeToRefs(userStore);
|
||||
import personalDataIcon from "@/assets/images/promote/personal_data_bg.png";
|
||||
import companyIcon from "@/assets/images/promote/company_bg.png";
|
||||
import preLoanBackgroundCheckIcon from "@/assets/images/promote/preloan_background_check_bg.png";
|
||||
import marriageRiskIcon from "@/assets/images/promote/marriage_risk_bg.png";
|
||||
import housekeepingRiskIcon from "@/assets/images/promote/housekeeping_risk_bg.png";
|
||||
import backgroundcheckIcon from "@/assets/images/promote/backgroundcheck_bg.png";
|
||||
import consumerFinanceReportIcon from "@/assets/images/promote/consumer_finance_report_bg.png";
|
||||
import banner1 from "@/assets/images/promote/banner_1.png";
|
||||
import banner2 from "@/assets/images/promote/banner_2.png";
|
||||
|
||||
function toInquire(name) {
|
||||
// 检查是否是代理
|
||||
if (!isAgent.value) {
|
||||
if (!isLoggedIn.value) {
|
||||
showToast({ message: "请先登录" });
|
||||
router.push({ path: "/login", query: { redirect: router.currentRoute.value.fullPath } });
|
||||
} else {
|
||||
showToast({ message: "请先注册成为代理" });
|
||||
router.push({ path: "/register", query: { mobile: mobile.value } });
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 跳转到推广页面,传递 feature 参数
|
||||
router.push({ name: "agentPromote", query: { feature: name } });
|
||||
}
|
||||
function toInvitation() {
|
||||
// 检查是否是代理
|
||||
if (!isAgent.value) {
|
||||
if (!isLoggedIn.value) {
|
||||
showToast({ message: "请先登录" });
|
||||
router.push({ path: "/login", query: { redirect: router.currentRoute.value.fullPath } });
|
||||
} else {
|
||||
showToast({ message: "请先注册成为代理" });
|
||||
router.push({ path: "/register", query: { mobile: mobile.value } });
|
||||
}
|
||||
return;
|
||||
}
|
||||
router.push({ name: "invitation" });
|
||||
}
|
||||
const toPromote = () => {
|
||||
// 检查是否是代理
|
||||
if (!isAgent.value) {
|
||||
if (!isLoggedIn.value) {
|
||||
showToast({ message: "请先登录" });
|
||||
router.push({ path: "/login", query: { redirect: router.currentRoute.value.fullPath } });
|
||||
} else {
|
||||
showToast({ message: "请先注册成为代理" });
|
||||
router.push({ path: "/register", query: { mobile: mobile.value } });
|
||||
}
|
||||
return;
|
||||
}
|
||||
router.push({ name: "agentPromote" });
|
||||
};
|
||||
|
||||
const toHelp = () => {
|
||||
router.push({ name: "agentSystemGuide" });
|
||||
};
|
||||
|
||||
const toService = () => {
|
||||
router.push({ name: "service" });
|
||||
};
|
||||
|
||||
function toHistory() {
|
||||
router.push(`/historyQuery`);
|
||||
}
|
||||
|
||||
const toAgent = () => {
|
||||
// 检查是否是代理
|
||||
if (!isAgent.value) {
|
||||
if (!isLoggedIn.value) {
|
||||
showToast({ message: "请先登录" });
|
||||
router.push({ path: "/login", query: { redirect: router.currentRoute.value.fullPath } });
|
||||
} else {
|
||||
showToast({ message: "请先注册成为代理" });
|
||||
router.push({ path: "/register", query: { mobile: mobile.value } });
|
||||
}
|
||||
return;
|
||||
}
|
||||
router.push({ name: "agent" });
|
||||
};
|
||||
|
||||
const toWithdraw = () => {
|
||||
// 检查是否是代理
|
||||
if (!isAgent.value) {
|
||||
if (!isLoggedIn.value) {
|
||||
showToast({ message: "请先登录" });
|
||||
router.push({ path: "/login", query: { redirect: router.currentRoute.value.fullPath } });
|
||||
} else {
|
||||
showToast({ message: "请先注册成为代理" });
|
||||
router.push({ path: "/register", query: { mobile: mobile.value } });
|
||||
}
|
||||
return;
|
||||
}
|
||||
router.push({ name: "withdraw" });
|
||||
};
|
||||
|
||||
const toTeamList = () => {
|
||||
// 检查是否是代理
|
||||
if (!isAgent.value) {
|
||||
if (!isLoggedIn.value) {
|
||||
showToast({ message: "请先登录" });
|
||||
router.push({ path: "/login", query: { redirect: router.currentRoute.value.fullPath } });
|
||||
} else {
|
||||
showToast({ message: "请先注册成为代理" });
|
||||
router.push({ path: "/register", query: { mobile: mobile.value } });
|
||||
}
|
||||
return;
|
||||
}
|
||||
router.push({ name: "teamList" });
|
||||
};
|
||||
|
||||
// 产品UI配置映射(根据 product_en 匹配)- 从 PromotePage.vue 迁移
|
||||
const services = ref([
|
||||
{
|
||||
name: 'riskassessment',
|
||||
title: "个人大数据",
|
||||
subtitle: "个人信用 精准查询",
|
||||
bg: personalDataIcon,
|
||||
goColor: "#6699ff",
|
||||
isHighlight: true, // 重点推荐
|
||||
costPrice: null,
|
||||
},
|
||||
{
|
||||
name: 'marriage',
|
||||
title: "情侣报告",
|
||||
subtitle: "相信才能相依 相爱才能永久",
|
||||
bg: marriageRiskIcon,
|
||||
goColor: "#ff99cc",
|
||||
costPrice: null,
|
||||
},
|
||||
{
|
||||
name: 'backgroundcheck',
|
||||
title: "入职背调",
|
||||
subtitle: "查询便可 慧眼识英雄",
|
||||
bg: backgroundcheckIcon,
|
||||
goColor: "#7db3ff",
|
||||
costPrice: null,
|
||||
},
|
||||
{
|
||||
name: 'companyinfo',
|
||||
title: "企业大数据",
|
||||
subtitle: "信任是合作 永恒的基石",
|
||||
bg: companyIcon,
|
||||
goColor: "#ffaa66",
|
||||
costPrice: null,
|
||||
},
|
||||
{
|
||||
name: 'homeservice',
|
||||
title: "家政报告",
|
||||
subtitle: "口碑与能力 一查便知",
|
||||
bg: housekeepingRiskIcon,
|
||||
goColor: "#66cccc",
|
||||
costPrice: null,
|
||||
},
|
||||
{
|
||||
name: 'consumerFinanceReport',
|
||||
title: "消金报告",
|
||||
subtitle: "",
|
||||
bg: consumerFinanceReportIcon,
|
||||
goColor: "#a259ff",
|
||||
costPrice: null,
|
||||
},
|
||||
]);
|
||||
|
||||
// 使用合并后的服务数组
|
||||
const allServices = computed(() => services.value);
|
||||
|
||||
// 将subtitle按照空格分割成两行 - 从 PromotePage.vue 迁移
|
||||
const formatSubtitle = (subtitle) => {
|
||||
if (!subtitle) return '';
|
||||
// 按照空格分割,取前两部分
|
||||
const parts = subtitle.split(/\s+/);
|
||||
if (parts.length >= 2) {
|
||||
// 找到中间位置,尽量平均分配
|
||||
const mid = Math.ceil(parts.length / 2);
|
||||
const firstLine = parts.slice(0, mid).join(' ');
|
||||
const secondLine = parts.slice(mid).join(' ');
|
||||
return `${firstLine}\n${secondLine}`;
|
||||
}
|
||||
return subtitle;
|
||||
};
|
||||
|
||||
// 获取成本价显示文本
|
||||
const getCostPriceText = (service) => {
|
||||
// 如果已登录且是代理,且有成本价,显示成本价
|
||||
if (isLoggedIn.value && isAgent.value && service.costPrice) {
|
||||
return `成本价 ${service.costPrice}¥`;
|
||||
}
|
||||
// 如果未登录,显示登录提示
|
||||
if (!isLoggedIn.value) {
|
||||
return '登录查看';
|
||||
}
|
||||
// 如果已登录但不是代理,显示成为代理提示
|
||||
if (!isAgent.value) {
|
||||
return '成为代理查看';
|
||||
}
|
||||
// 默认情况
|
||||
return '成本价 --';
|
||||
};
|
||||
|
||||
// 获取产品配置和成本价
|
||||
const getProductConfig = async () => {
|
||||
try {
|
||||
const { data, error } = await useApiFetch("/agent/product_config")
|
||||
.get()
|
||||
.json();
|
||||
|
||||
if (data.value && !error.value && data.value.code === 200) {
|
||||
const productConfigList = data.value.data.list;
|
||||
|
||||
// 更新每个服务的成本价,使用 product_en 匹配
|
||||
services.value.forEach(service => {
|
||||
const config = productConfigList.find(
|
||||
item => item.product_en === service.name
|
||||
);
|
||||
if (config && config.actual_base_price !== undefined) {
|
||||
service.costPrice = parseFloat(config.actual_base_price).toFixed(2);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log("Error fetching product config", data.value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch product config", error);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 只有在已登录且是代理的情况下才请求产品配置接口
|
||||
|
||||
});
|
||||
// 页面初次进入时 isLoggedIn.value 和 isAgent.value 可能还未赋值,需 watch 联动处理
|
||||
watch(
|
||||
[isLoggedIn, isAgent],
|
||||
([loggedIn, agent]) => {
|
||||
if (loggedIn && agent) {
|
||||
getProductConfig();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
const noticeText = ref([]);
|
||||
const toCooperation = () => {
|
||||
window.location.href = "https://www.tianyuandata.com";
|
||||
};
|
||||
const toBigData = () => {
|
||||
window.location.href = "https://www.tybigdata.com/";
|
||||
};
|
||||
|
||||
// 轮播图数据
|
||||
const bannerImages = [banner1, banner2];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="box-border from-blue-100 to-white bg-gradient-to-b">
|
||||
<div class="relative">
|
||||
<van-swipe :autoplay="3000" indicator-color="white">
|
||||
<van-swipe-item v-for="(banner, index) in bannerImages" :key="index" @click="toPromote">
|
||||
<img class="h-full w-full" :src="banner" />
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
</div>
|
||||
<div class="px-6 mt-4">
|
||||
<!-- 菜单项两排8个 -->
|
||||
<div class="grid grid-cols-4 gap-3">
|
||||
<!-- 第一排 -->
|
||||
<div class="text-center flex flex-col justify-center items-center" @click="toPromote">
|
||||
<div
|
||||
class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
|
||||
<img src="@/assets/images/promote/tgbg.png" alt="推广报告" class="h-14 w-14" />
|
||||
</div>
|
||||
<div class="text-center mt-1 font-bold text-sm">推广报告</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center flex flex-col justify-center items-center" @click="toInvitation">
|
||||
<div
|
||||
class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
|
||||
<img src="@/assets/images/promote/yqxj.png" alt="邀请下级" class="h-14 w-14" />
|
||||
</div>
|
||||
<div class="text-center mt-1 font-bold text-sm">邀请下级</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center flex flex-col justify-center items-center" @click="toHelp">
|
||||
<div
|
||||
class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
|
||||
<img src="@/assets/images/promote/bzzx.png" alt="帮助中心" class="h-14 w-14" />
|
||||
</div>
|
||||
<div class="text-center mt-1 font-bold text-sm">帮助中心</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center flex flex-col justify-center items-center" @click="toHistory">
|
||||
<div
|
||||
class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
|
||||
<img src="@/assets/images/promote/wdbg.png" alt="我的报告" class="h-14 w-14" />
|
||||
</div>
|
||||
<div class="text-center mt-1 font-bold text-sm">我的报告</div>
|
||||
</div>
|
||||
|
||||
<!-- 第二排 -->
|
||||
<div class="text-center flex flex-col justify-center items-center" @click="toAgent">
|
||||
<div
|
||||
class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
|
||||
<img src="@/assets/images/promote/zc.png" alt="资产" class="h-14 w-14" />
|
||||
</div>
|
||||
<div class="text-center mt-1 font-bold text-sm">资产</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center flex flex-col justify-center items-center" @click="toWithdraw">
|
||||
<div
|
||||
class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
|
||||
<img src="@/assets/images/promote/wytx.png" alt="我要提现" class="h-14 w-14" />
|
||||
</div>
|
||||
<div class="text-center mt-1 font-bold text-sm">我要提现</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center flex flex-col justify-center items-center" @click="toTeamList">
|
||||
<div
|
||||
class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
|
||||
<img src="@/assets/images/promote/wdxj.png" alt="我的团队" class="h-14 w-14" />
|
||||
</div>
|
||||
<div class="text-center mt-1 font-bold text-sm">我的团队</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center flex flex-col justify-center items-center" @click="toService">
|
||||
<div
|
||||
class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
|
||||
<img src="@/assets/images/promote/zxkf.png" alt="在线客服" class="h-14 w-14" />
|
||||
</div>
|
||||
<div class="text-center mt-1 font-bold text-sm">在线客服</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-2 mx-4 rounded-xl overflow-hiddenshadow-xl" @click="toInvitation">
|
||||
<img src="@/assets/images/promote/tghb.png" class="w-full h-full" alt="推广横幅" mode="widthFix" />
|
||||
</div>
|
||||
<div class="flex items-center justify-between mx-4 mb-2">
|
||||
<SectionTitle title="推广服务" />
|
||||
<div class="text-xs text-gray-500 flex items-center gap-1.5">
|
||||
<span class="opacity-80">‹</span>
|
||||
<span>滑动查看更多</span>
|
||||
<span class="opacity-80">›</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative p-4 pt-0">
|
||||
<div class="services-scroll-container">
|
||||
<div class="services-scroll-wrapper">
|
||||
<template v-for="(service, index) in allServices" :key="index">
|
||||
<div class="relative flex flex-col px-4 py-2 rounded-xl shadow service-card"
|
||||
:style="`background: url(${service.bg}) no-repeat; background-size: contain; background-position: center;`"
|
||||
@click="toInquire(service.name)">
|
||||
<div class="flex flex-col items-start flex-1">
|
||||
<div class="mt-1 text-left text-gray-600 font-bold">
|
||||
{{ service.title }}
|
||||
</div>
|
||||
<div class="mt-2 rounded-lg px-2 py-1 text-xs text-white w-max flex items-center"
|
||||
:style="`background-color: ${service.goColor}`">
|
||||
立即推广
|
||||
<img src="@/assets/images/index/go_icon.png" alt="右箭头"
|
||||
class="ml-0.5 h-3 w-3 inline-block align-middle" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="absolute bottom-0 left-0 right-0 rounded-b-xl px-2 py-1 text-xs text-white text-center">
|
||||
{{ getCostPriceText(service) }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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);
|
||||
}
|
||||
|
||||
.services-scroll-container {
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
/* Firefox */
|
||||
-ms-overflow-style: none;
|
||||
/* IE and Edge */
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.services-scroll-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
/* Chrome, Safari, Opera */
|
||||
}
|
||||
|
||||
.services-scroll-wrapper {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.service-card {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<route type="home" lang="json">{
|
||||
"layout": "home"
|
||||
}</route>
|
||||
66
src/views/PromotionInquire.vue
Normal file
66
src/views/PromotionInquire.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeMount } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useUserStore } from '@/stores/userStore';
|
||||
import InquireForm from "@/components/InquireForm.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const { mobile: userStoreMobile } = storeToRefs(userStore);
|
||||
|
||||
const linkIdentifier = ref("");
|
||||
const feature = ref("");
|
||||
const featureData = ref({});
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await getProduct();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
isFinishPayment();
|
||||
});
|
||||
|
||||
function isFinishPayment() {
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
let orderNo = query.get("out_trade_no");
|
||||
if (orderNo) {
|
||||
console.log("🎯 Detected payment result, navigating to report with orderNo:", orderNo);
|
||||
// 延迟跳转,确保页面已完全加载
|
||||
setTimeout(() => {
|
||||
router.push({ path: "/report", query: { orderNo } });
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
import { getLinkData } from "@/api/agent";
|
||||
|
||||
async function getProduct() {
|
||||
linkIdentifier.value = route.params.linkIdentifier;
|
||||
const { data: agentLinkData, error: agentLinkError } = await getLinkData(
|
||||
linkIdentifier.value
|
||||
);
|
||||
if (agentLinkData.value && !agentLinkError.value) {
|
||||
if (agentLinkData.value.code === 200) {
|
||||
feature.value = agentLinkData.value.data.product_en;
|
||||
featureData.value = agentLinkData.value.data;
|
||||
// 确保 FLXG0V4B 排在首位
|
||||
if (
|
||||
featureData.value.features &&
|
||||
featureData.value.features.length > 0
|
||||
) {
|
||||
featureData.value.features.sort((a, b) => {
|
||||
if (a.api_id === "FLXG0V4B") return -1;
|
||||
if (b.api_id === "FLXG0V4B") return 1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InquireForm :type="'promotion'" :feature="feature" :link-identifier="linkIdentifier" :feature-data="featureData" />
|
||||
</template>
|
||||
540
src/views/Register.vue
Normal file
540
src/views/Register.vue
Normal file
@@ -0,0 +1,540 @@
|
||||
<script setup>
|
||||
import { ref, computed, onUnmounted, onMounted, nextTick } from 'vue'
|
||||
import { showToast } from 'vant'
|
||||
import { registerByInviteCode, applyForAgent } from '@/api/agent'
|
||||
import { useAgentStore } from '@/stores/agentStore'
|
||||
import { useUserStore } from '@/stores/userStore'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import useApiFetch from '@/composables/useApiFetch'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const agentStore = useAgentStore()
|
||||
const userStore = useUserStore()
|
||||
const appName = import.meta.env.VITE_APP_NAME || '真爱查';
|
||||
|
||||
const phoneNumber = ref('')
|
||||
const verificationCode = ref('')
|
||||
const inviteCode = ref('')
|
||||
const isAgreed = ref(false)
|
||||
const isCountingDown = ref(false)
|
||||
const countdown = ref(60)
|
||||
const isPhoneDisabled = ref(false)
|
||||
let timer = null
|
||||
|
||||
// 填充默认邀请码
|
||||
function fillInviteCode() {
|
||||
inviteCode.value = '16800'
|
||||
}
|
||||
|
||||
// 从URL参数中读取邀请码并自动填入,如果用户已登录且有手机号则自动填充
|
||||
onMounted(async () => {
|
||||
const inviteCodeParam = route.query.invite_code;
|
||||
if (inviteCodeParam) {
|
||||
inviteCode.value = inviteCodeParam;
|
||||
}
|
||||
|
||||
// 从路由参数获取手机号(已注册用户继续注册成为代理的情况)
|
||||
const mobileParam = route.query.mobile;
|
||||
if (mobileParam) {
|
||||
phoneNumber.value = mobileParam;
|
||||
isPhoneDisabled.value = true;
|
||||
} else {
|
||||
// 如果用户已登录且有手机号,自动填充手机号
|
||||
const token = localStorage.getItem("token");
|
||||
if (token) {
|
||||
// 确保用户信息已加载
|
||||
if (!userStore.mobile) {
|
||||
await userStore.fetchUserInfo();
|
||||
}
|
||||
if (userStore.mobile) {
|
||||
phoneNumber.value = userStore.mobile;
|
||||
isPhoneDisabled.value = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 是否是已注册用户(根据注册接口返回判断)
|
||||
const isRegisteredUser = ref(false)
|
||||
|
||||
const isPhoneNumberValid = computed(() => {
|
||||
return /^1[3-9]\d{9}$/.test(phoneNumber.value)
|
||||
})
|
||||
|
||||
const isInviteCodeValid = computed(() => {
|
||||
return inviteCode.value.trim().length > 0
|
||||
})
|
||||
|
||||
const canRegister = computed(() => {
|
||||
return isPhoneNumberValid.value &&
|
||||
verificationCode.value.length === 6 &&
|
||||
isInviteCodeValid.value &&
|
||||
isAgreed.value
|
||||
})
|
||||
|
||||
async function sendVerificationCode() {
|
||||
if (isCountingDown.value || !isPhoneNumberValid.value) return
|
||||
if (!isPhoneNumberValid.value) {
|
||||
showToast({ message: "请输入有效的手机号" });
|
||||
return
|
||||
}
|
||||
if (!isInviteCodeValid.value) {
|
||||
showToast({ message: "请先输入邀请码" });
|
||||
return
|
||||
}
|
||||
|
||||
const { data, error } = await useApiFetch('auth/sendSms')
|
||||
.post({ mobile: phoneNumber.value, actionType: 'agentApply' })
|
||||
.json()
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
showToast({ message: "获取成功" });
|
||||
startCountdown()
|
||||
// 聚焦到验证码输入框
|
||||
nextTick(() => {
|
||||
const verificationCodeInput = document.getElementById('verificationCode');
|
||||
if (verificationCodeInput) {
|
||||
verificationCodeInput.focus();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
showToast(data.value.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function startCountdown() {
|
||||
isCountingDown.value = true
|
||||
countdown.value = 60
|
||||
timer = setInterval(() => {
|
||||
if (countdown.value > 0) {
|
||||
countdown.value--
|
||||
} else {
|
||||
clearInterval(timer)
|
||||
isCountingDown.value = false
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
async function handleRegister() {
|
||||
if (!isPhoneNumberValid.value) {
|
||||
showToast({ message: "请输入有效的手机号" });
|
||||
return
|
||||
}
|
||||
if (!isInviteCodeValid.value) {
|
||||
showToast({ message: "请输入邀请码" });
|
||||
return
|
||||
}
|
||||
if (verificationCode.value.length !== 6) {
|
||||
showToast({ message: "请输入有效的验证码" });
|
||||
return
|
||||
}
|
||||
if (!isAgreed.value) {
|
||||
showToast({ message: "请先同意用户协议、隐私政策和代理管理协议" });
|
||||
return
|
||||
}
|
||||
// 直接执行注册逻辑
|
||||
performRegister()
|
||||
}
|
||||
|
||||
// 执行实际的注册逻辑
|
||||
async function performRegister() {
|
||||
try {
|
||||
// 先尝试通过邀请码注册(同时注册用户和代理)
|
||||
const { data, error } = await registerByInviteCode({
|
||||
mobile: phoneNumber.value,
|
||||
code: verificationCode.value,
|
||||
referrer: inviteCode.value.trim()
|
||||
})
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
// 保存token
|
||||
localStorage.setItem('token', data.value.data.accessToken)
|
||||
localStorage.setItem('refreshAfter', data.value.data.refreshAfter)
|
||||
localStorage.setItem('accessExpire', data.value.data.accessExpire)
|
||||
|
||||
// 更新代理信息到store
|
||||
if (data.value.data.agent_id) {
|
||||
agentStore.updateAgentInfo({
|
||||
isAgent: true,
|
||||
agentID: data.value.data.agent_id,
|
||||
level: data.value.data.level || 1,
|
||||
levelName: data.value.data.level_name || '普通代理'
|
||||
})
|
||||
}
|
||||
|
||||
showToast({ message: "注册成功!" });
|
||||
// 跳转到代理主页
|
||||
setTimeout(() => {
|
||||
window.location.href = '/'
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('注册失败:', err)
|
||||
showToast({ message: "注册失败,请重试" });
|
||||
}
|
||||
}
|
||||
|
||||
// 已注册用户申请成为代理
|
||||
async function applyForAgentAsRegisteredUser() {
|
||||
try {
|
||||
const { data, error } = await applyForAgent({
|
||||
mobile: phoneNumber.value,
|
||||
code: verificationCode.value,
|
||||
referrer: inviteCode.value.trim()
|
||||
})
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
// 保存token
|
||||
localStorage.setItem('token', data.value.data.accessToken)
|
||||
localStorage.setItem('refreshAfter', data.value.data.refreshAfter)
|
||||
localStorage.setItem('accessExpire', data.value.data.accessExpire)
|
||||
|
||||
showToast({ message: "申请成功!" });
|
||||
// 跳转到代理主页
|
||||
setTimeout(() => {
|
||||
window.location.href = '/'
|
||||
}, 500)
|
||||
} else {
|
||||
showToast(data.value.msg || "申请失败,请重试")
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('申请失败:', err)
|
||||
showToast({ message: "申请失败,请重试" });
|
||||
}
|
||||
}
|
||||
|
||||
function toUserAgreement() {
|
||||
router.push(`/userAgreement`)
|
||||
}
|
||||
|
||||
function toPrivacyPolicy() {
|
||||
router.push(`/privacyPolicy`)
|
||||
}
|
||||
|
||||
function toAgentManageAgreement() {
|
||||
router.push(`/agentManageAgreement`)
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
}
|
||||
})
|
||||
|
||||
const onClickLeft = () => {
|
||||
router.replace('/')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-layout ">
|
||||
<van-nav-bar fixed placeholder title="注册成为代理" left-text="" left-arrow @click-left="onClickLeft" />
|
||||
<div class="login px-4 relative z-10">
|
||||
<div class="mb-8 pt-20 text-left">
|
||||
<div class="flex flex-col items-center">
|
||||
<img class="h-16 w-16 rounded-full shadow" src="@/assets/images/logo.jpg" alt="Logo" />
|
||||
<div class="report-title-wrapper">
|
||||
<h1 class="report-title">大数据风险报告</h1>
|
||||
<div class="report-title-decoration"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 注册表单 -->
|
||||
<div class="login-form">
|
||||
<!-- 邀请码输入 -->
|
||||
<div class="form-item">
|
||||
<div class="form-label">邀请码</div>
|
||||
<div class="input-with-btn">
|
||||
<input v-model="inviteCode" class="form-input" type="text" placeholder="请输入邀请码" />
|
||||
<button class="get-invite-code-btn" @click="fillInviteCode">获取邀请码</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 手机号输入 -->
|
||||
<div class="form-item">
|
||||
<div class="form-label">手机号</div>
|
||||
<input v-model="phoneNumber" class="form-input" type="tel" placeholder="请输入手机号" maxlength="11"
|
||||
:disabled="isPhoneDisabled" />
|
||||
</div>
|
||||
|
||||
<!-- 验证码输入 -->
|
||||
<div class="form-item">
|
||||
<div class="form-label">验证码</div>
|
||||
<div class="verification-input-wrapper">
|
||||
<input v-model="verificationCode" id="verificationCode" class="form-input verification-input"
|
||||
placeholder="请输入验证码" maxlength="6" />
|
||||
<button class="get-code-btn"
|
||||
:class="{ 'disabled': isCountingDown || !isPhoneNumberValid || !isInviteCodeValid }"
|
||||
@click="sendVerificationCode"
|
||||
:disabled="isCountingDown || !isPhoneNumberValid || !isInviteCodeValid">
|
||||
{{ isCountingDown ? `${countdown}s` : '获取验证码' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 协议同意框 -->
|
||||
<div class="agreement-wrapper">
|
||||
<input type="checkbox" v-model="isAgreed" class="agreement-checkbox accent-primary"
|
||||
id="agreement" />
|
||||
<label for="agreement" class="agreement-text">
|
||||
我已阅读并同意
|
||||
<a class="agreement-link" @click="toUserAgreement">《用户协议》</a>
|
||||
<a class="agreement-link" @click="toPrivacyPolicy">《隐私政策》</a>
|
||||
和
|
||||
<a class="agreement-link" @click="toAgentManageAgreement">《代理管理协议》</a>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- 提示文字 -->
|
||||
<div class="notice-text">
|
||||
未注册手机号注册后将自动生成账号并成为代理,并且代表您已阅读并同意
|
||||
</div>
|
||||
|
||||
<!-- 注册按钮 -->
|
||||
<button class="login-btn" :class="{ 'disabled': !canRegister }" @click="handleRegister"
|
||||
:disabled="!canRegister">
|
||||
注册成为代理
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.login-layout {
|
||||
background-image: url('@/assets/images/login_bg.png');
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 登录表单 */
|
||||
.login-form {
|
||||
background-color: var(--color-bg-primary);
|
||||
padding: 2rem;
|
||||
margin-top: 0.5rem;
|
||||
box-shadow: 0px 0px 24px 0px #3F3F3F0F;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* 表单项 */
|
||||
.form-item {
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
/* 输入框和按钮组合 */
|
||||
.input-with-btn {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.input-with-btn .form-input {
|
||||
padding-right: 6rem;
|
||||
}
|
||||
|
||||
.get-invite-code-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-primary);
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 0.9375rem;
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: 0;
|
||||
margin-right: 1rem;
|
||||
font-weight: 500;
|
||||
min-width: 4rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 0.875rem 0;
|
||||
|
||||
font-size: 0.9375rem;
|
||||
color: var(--color-text-primary);
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.form-input::placeholder {
|
||||
color: var(--color-text-tertiary);
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
border-bottom-color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.form-input:disabled {
|
||||
color: var(--color-text-tertiary);
|
||||
cursor: not-allowed;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* 验证码输入 */
|
||||
.verification-input-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.verification-input {
|
||||
flex: 1;
|
||||
padding-right: 6rem;
|
||||
}
|
||||
|
||||
.get-code-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-primary);
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.get-code-btn.disabled {
|
||||
color: var(--color-gray-400);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 协议同意 */
|
||||
.agreement-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.agreement-checkbox {
|
||||
flex-shrink: 0;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.agreement-text {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.agreement-link {
|
||||
color: var(--color-primary);
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* 提示文字 */
|
||||
.notice-text {
|
||||
font-size: 0.6875rem;
|
||||
color: var(--color-text-tertiary);
|
||||
line-height: 1.5;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
/* 登录按钮 */
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
padding: 0.875rem;
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-text-white);
|
||||
border: none;
|
||||
border-radius: 1.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.3s;
|
||||
letter-spacing: 0.25rem;
|
||||
}
|
||||
|
||||
.login-btn:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.login-btn.disabled {
|
||||
background-color: var(--color-gray-300);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.report-title-wrapper {
|
||||
position: relative;
|
||||
margin-top: 1.5rem;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.report-title {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 25%, #f093fb 50%, #4facfe 75%, #00f2fe 100%);
|
||||
background-size: 200% 200%;
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
text-align: center;
|
||||
letter-spacing: 0.1em;
|
||||
margin: 0;
|
||||
padding: 0.5rem 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
animation: gradientShift 3s ease infinite;
|
||||
text-shadow: 0 2px 10px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.report-title-decoration {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 60px;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, transparent, #667eea, #764ba2, #667eea, transparent);
|
||||
border-radius: 2px;
|
||||
animation: decorationPulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes gradientShift {
|
||||
0%, 100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes decorationPulse {
|
||||
0%, 100% {
|
||||
opacity: 0.6;
|
||||
transform: translateX(-50%) scaleX(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) scaleX(1.2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
179
src/views/Report.vue
Normal file
179
src/views/Report.vue
Normal file
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<BaseReport v-if="queryState === 'success'" :order-id="orderId" :order-no="orderNo" :feature="feature"
|
||||
:reportData="reportData" :reportParams="reportParams" :reportName="reportName" :reportDateTime="reportDateTime"
|
||||
:isEmpty="isEmpty" :isDone="isDone" :isExample="false" />
|
||||
<div v-else-if="queryState === 'pending'" class="loading-container">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>报告生成中,请稍候...</p>
|
||||
</div>
|
||||
<div class="p-4" v-else-if="queryState === 'failed'">
|
||||
<LEmpty />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import LEmpty from "@/components/LEmpty.vue";
|
||||
import { aesDecrypt } from "@/utils/crypto";
|
||||
|
||||
const AES_KEY = import.meta.env.VITE_INQUIRE_AES_KEY;
|
||||
|
||||
const route = useRoute();
|
||||
const feature = ref("");
|
||||
const reportData = ref([]);
|
||||
const reportParams = ref({});
|
||||
const reportName = ref("");
|
||||
const reportDateTime = ref(null);
|
||||
const isEmpty = ref(false);
|
||||
const isDone = ref(false);
|
||||
const orderId = ref(null);
|
||||
const orderNo = ref("");
|
||||
const queryState = ref("");
|
||||
const pollingInterval = ref(null);
|
||||
|
||||
|
||||
onBeforeMount(() => {
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
orderNo.value = query.get("out_trade_no");
|
||||
orderId.value = query.get("order_id");
|
||||
|
||||
|
||||
if (!orderNo.value && !orderId.value) {
|
||||
orderId.value = route.query.orderId;
|
||||
orderNo.value = route.query.orderNo;
|
||||
}
|
||||
|
||||
if (!orderId.value && !orderNo.value) return;
|
||||
|
||||
getReport();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (pollingInterval.value) {
|
||||
clearInterval(pollingInterval.value);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
const getReport = async () => {
|
||||
let queryUrl = "";
|
||||
if (orderNo.value) {
|
||||
queryUrl = `/query/orderNo/${orderNo.value}`;
|
||||
} else if (orderId.value) {
|
||||
queryUrl = `/query/orderId/${orderId.value}`;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
const { data, error } = await useApiFetch(queryUrl).get().json();
|
||||
|
||||
if (!AES_KEY) {
|
||||
console.error("缺少解密密钥");
|
||||
isEmpty.value = true;
|
||||
isDone.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
let decryptedData = data.value.data;
|
||||
|
||||
if (typeof decryptedData === "string") {
|
||||
try {
|
||||
const decryptedStr = aesDecrypt(decryptedData, AES_KEY);
|
||||
decryptedData = JSON.parse(decryptedStr);
|
||||
} catch (err) {
|
||||
console.error("报告数据解密失败", err);
|
||||
isEmpty.value = true;
|
||||
isDone.value = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!decryptedData) {
|
||||
isEmpty.value = true;
|
||||
isDone.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
queryState.value = decryptedData.query_state;
|
||||
if (queryState.value === "success") {
|
||||
feature.value = decryptedData.product || "";
|
||||
|
||||
const sortedQueryData = Array.isArray(decryptedData.query_data)
|
||||
? [...decryptedData.query_data].sort((a, b) => {
|
||||
return a.feature.sort - b.feature.sort;
|
||||
})
|
||||
: [];
|
||||
|
||||
reportData.value = sortedQueryData;
|
||||
reportParams.value = decryptedData.query_params || {};
|
||||
reportName.value = decryptedData.product_name || "";
|
||||
reportDateTime.value = decryptedData.create_time || null;
|
||||
isDone.value = true;
|
||||
|
||||
// 如果成功,清除轮询
|
||||
if (pollingInterval.value) {
|
||||
clearInterval(pollingInterval.value);
|
||||
pollingInterval.value = null;
|
||||
}
|
||||
} else if (queryState.value === "pending") {
|
||||
// 如果是pending状态且没有轮询,启动轮询
|
||||
if (!pollingInterval.value) {
|
||||
pollingInterval.value = setInterval(() => {
|
||||
getReport();
|
||||
}, 2000); // 每2秒轮询一次
|
||||
}
|
||||
} else if (queryState.value === "failed") {
|
||||
isEmpty.value = true;
|
||||
isDone.value = true;
|
||||
// 如果失败,清除轮询
|
||||
if (pollingInterval.value) {
|
||||
clearInterval(pollingInterval.value);
|
||||
pollingInterval.value = null;
|
||||
}
|
||||
}
|
||||
} else if (data.value.code === 200003) {
|
||||
isEmpty.value = true;
|
||||
isDone.value = true;
|
||||
} else if (data.value.code === 200002) {
|
||||
isPending.value = true;
|
||||
isDone.value = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
|
||||
.loading-spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #3498db;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
251
src/views/ReportShare.vue
Normal file
251
src/views/ReportShare.vue
Normal file
@@ -0,0 +1,251 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
<van-nav-bar title="真爱查" left-arrow @click-left="goHome" fixed placeholder safe-area-inset-top
|
||||
z-index="3000">
|
||||
<template #right>
|
||||
<van-button type="primary" size="small" class="!bg-blue-500 !border-blue-500" @click="goHome">
|
||||
首页
|
||||
</van-button>
|
||||
</template>
|
||||
</van-nav-bar>
|
||||
|
||||
<BaseReport v-if="queryState === 'success' && !isExpired" :isShare="true" :feature="feature"
|
||||
:reportData="reportData" :reportParams="reportParams" :reportName="reportName"
|
||||
:reportDateTime="reportDateTime" :isEmpty="isEmpty" :isDone="isDone" />
|
||||
<div v-else-if="queryState === 'pending'" class="loading-container">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>报告生成中,请稍候...</p>
|
||||
</div>
|
||||
<div v-else-if="isExpired" class="expired-container">
|
||||
<div class="expired-content">
|
||||
<van-icon name="clock-o" size="48" color="#999" />
|
||||
<h2 class="text-xl font-bold text-gray-700 mt-4">
|
||||
分享链接已过期
|
||||
</h2>
|
||||
<p class="text-gray-500 mt-2">
|
||||
该分享链接已超过7天有效期,请重新获取分享链接
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4" v-else-if="queryState === 'failed'">
|
||||
<LEmpty />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import LEmpty from "@/components/LEmpty.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { aesDecrypt } from "@/utils/crypto";
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const feature = ref("");
|
||||
const reportData = ref([]);
|
||||
const reportParams = ref({});
|
||||
const reportName = ref("");
|
||||
const reportDateTime = ref(null);
|
||||
const isEmpty = ref(false);
|
||||
const isDone = ref(false);
|
||||
const isExpired = ref(false);
|
||||
const queryState = ref("");
|
||||
const pollingInterval = ref(null);
|
||||
const AES_KEY = import.meta.env.VITE_INQUIRE_AES_KEY;
|
||||
|
||||
onBeforeMount(() => {
|
||||
// 从动态路由参数中获取 linkIdentifier
|
||||
const linkIdentifier = route.params.linkIdentifier;
|
||||
if (!linkIdentifier) {
|
||||
isEmpty.value = true;
|
||||
isDone.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 解码 linkIdentifier
|
||||
try {
|
||||
const decodedLink = decodeURIComponent(linkIdentifier);
|
||||
getReport(decodedLink);
|
||||
} catch (err) {
|
||||
console.error("分享链接无效");
|
||||
isEmpty.value = true;
|
||||
isDone.value = true;
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (pollingInterval.value) {
|
||||
clearInterval(pollingInterval.value);
|
||||
}
|
||||
});
|
||||
|
||||
const getReport = async (linkId) => {
|
||||
if (!linkId) return;
|
||||
|
||||
const { data, error } = await useApiFetch(`/query/share/${linkId}`)
|
||||
.get()
|
||||
.json();
|
||||
if (!AES_KEY) {
|
||||
console.error("缺少解密密钥");
|
||||
isEmpty.value = true;
|
||||
isDone.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
let decryptedData = data.value.data;
|
||||
|
||||
if (typeof decryptedData === "string") {
|
||||
try {
|
||||
const decryptedStr = aesDecrypt(decryptedData, AES_KEY);
|
||||
decryptedData = JSON.parse(decryptedStr);
|
||||
} catch (err) {
|
||||
console.error("分享报告数据解密失败", err);
|
||||
isEmpty.value = true;
|
||||
isDone.value = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!decryptedData) {
|
||||
isEmpty.value = true;
|
||||
isDone.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查分享链接状态
|
||||
if (decryptedData.status === "expired") {
|
||||
isExpired.value = true;
|
||||
isDone.value = true;
|
||||
// 如果过期,清除轮询
|
||||
if (pollingInterval.value) {
|
||||
clearInterval(pollingInterval.value);
|
||||
pollingInterval.value = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
queryState.value = decryptedData.query.query_state;
|
||||
if (queryState.value === "success") {
|
||||
const sortedQueryData = Array.isArray(decryptedData.query.query_data)
|
||||
? [...decryptedData.query.query_data].sort((a, b) => {
|
||||
return a.feature.sort - b.feature.sort;
|
||||
})
|
||||
: [];
|
||||
|
||||
reportData.value = sortedQueryData;
|
||||
feature.value = decryptedData.query.product || "";
|
||||
reportParams.value = decryptedData.query.query_params || {};
|
||||
reportName.value = decryptedData.query.product_name || "";
|
||||
reportDateTime.value = decryptedData.query.create_time || null;
|
||||
isDone.value = true;
|
||||
|
||||
// 如果成功,清除轮询
|
||||
if (pollingInterval.value) {
|
||||
clearInterval(pollingInterval.value);
|
||||
pollingInterval.value = null;
|
||||
}
|
||||
} else if (queryState.value === "pending") {
|
||||
// 如果是pending状态且没有轮询,启动轮询
|
||||
if (!pollingInterval.value) {
|
||||
pollingInterval.value = setInterval(() => {
|
||||
getReport(linkId);
|
||||
}, 2000); // 每2秒轮询一次
|
||||
}
|
||||
} else if (queryState.value === "failed") {
|
||||
isEmpty.value = true;
|
||||
isDone.value = true;
|
||||
// 如果失败,清除轮询
|
||||
if (pollingInterval.value) {
|
||||
clearInterval(pollingInterval.value);
|
||||
pollingInterval.value = null;
|
||||
}
|
||||
}
|
||||
} else if (data.value.code === 200003) {
|
||||
isEmpty.value = true;
|
||||
isDone.value = true;
|
||||
} else if (data.value.code === 200002) {
|
||||
isPending.value = true;
|
||||
isDone.value = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const goHome = () => {
|
||||
router.push("/");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
|
||||
.loading-spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #3498db;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.expired-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
background-color: #f8f9fa;
|
||||
padding: 20px;
|
||||
|
||||
.expired-content {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.van-nav-bar) {
|
||||
.van-nav-bar__title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.van-nav-bar__left {
|
||||
.van-icon {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.van-button) {
|
||||
height: 32px;
|
||||
padding: 0 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
58
src/views/Service.vue
Normal file
58
src/views/Service.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="support-chat">
|
||||
<h1>在线客服</h1>
|
||||
<p>如果您有任何问题,请通过在线客服联系我们。</p>
|
||||
<p>请点击右下角按钮联系客服</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
onMounted(() => {
|
||||
(function (d, t) {
|
||||
const BASE_URL = "https://service.quannengcha.com";
|
||||
const g = d.createElement(t),
|
||||
s = d.getElementsByTagName(t)[0];
|
||||
g.src = BASE_URL + "/packs/js/sdk.js";
|
||||
g.defer = true;
|
||||
g.async = true;
|
||||
|
||||
s.parentNode.insertBefore(g, s);
|
||||
g.onload = function () {
|
||||
window.chatwootSDK.run({
|
||||
websiteToken: "XJqwnEWKVNte8iJW8DLroEzd",
|
||||
baseUrl: BASE_URL,
|
||||
type: "expanded_bubble", // 使用自动展开模式
|
||||
});
|
||||
|
||||
console.log("Chatwoot script initialized", g);
|
||||
};
|
||||
})(document, "script");
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.support-chat {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.support-chat h1 {
|
||||
font-size: 28px;
|
||||
color: #005a9e;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.support-chat p {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
iframe {
|
||||
background: #000000;
|
||||
}
|
||||
|
||||
iframe::content .file-uploads.file-uploads-html5.text-black-900 {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
306
src/views/SubordinateDetail.vue
Normal file
306
src/views/SubordinateDetail.vue
Normal file
@@ -0,0 +1,306 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import useApiFetch from '@/composables/useApiFetch'
|
||||
|
||||
const route = useRoute()
|
||||
const loading = ref(false)
|
||||
const refreshing = ref(false)
|
||||
const finished = ref(false)
|
||||
const page = ref(1)
|
||||
const pageSize = 8
|
||||
const activeTab = ref('order') // 'order' 或 'invite'
|
||||
|
||||
// 数据
|
||||
const userInfo = ref({})
|
||||
const orderStats = ref({})
|
||||
const rebateStats = ref({})
|
||||
const inviteStats = ref({})
|
||||
const orderList = ref([])
|
||||
const inviteList = ref([])
|
||||
const orderListTotal = ref(0)
|
||||
const inviteListTotal = ref(0)
|
||||
|
||||
// 获取详情数据
|
||||
const fetchDetail = async () => {
|
||||
if (loading.value) return
|
||||
if (finished.value && page.value > 1) return
|
||||
|
||||
loading.value = true
|
||||
const tabType = activeTab.value
|
||||
const { data, error } = await useApiFetch(
|
||||
`/agent/subordinate/contribution/detail?subordinate_id=${route.params.id}&page=${page.value}&page_size=${pageSize}&tab_type=${tabType}`
|
||||
)
|
||||
.get()
|
||||
.json()
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
if (page.value === 1) {
|
||||
// 更新用户信息
|
||||
userInfo.value = {
|
||||
createTime: data.value.data.create_time,
|
||||
level: data.value.data.level_name || '普通',
|
||||
mobile: data.value.data.mobile,
|
||||
}
|
||||
// 更新统计数据
|
||||
orderStats.value = data.value.data.order_stats || {}
|
||||
rebateStats.value = data.value.data.rebate_stats || {}
|
||||
inviteStats.value = data.value.data.invite_stats || {}
|
||||
|
||||
// 清空列表
|
||||
if (tabType === 'order') {
|
||||
orderList.value = []
|
||||
} else {
|
||||
inviteList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 处理列表数据
|
||||
if (tabType === 'order') {
|
||||
if (data.value.data.order_list) {
|
||||
if (page.value === 1) {
|
||||
orderList.value = data.value.data.order_list
|
||||
} else {
|
||||
orderList.value.push(...data.value.data.order_list)
|
||||
}
|
||||
orderListTotal.value = data.value.data.order_list_total || 0
|
||||
finished.value = data.value.data.order_list.length < pageSize
|
||||
if (!finished.value) {
|
||||
page.value++
|
||||
}
|
||||
} else {
|
||||
finished.value = true
|
||||
}
|
||||
} else {
|
||||
if (data.value.data.invite_list) {
|
||||
if (page.value === 1) {
|
||||
inviteList.value = data.value.data.invite_list
|
||||
} else {
|
||||
inviteList.value.push(...data.value.data.invite_list)
|
||||
}
|
||||
inviteListTotal.value = data.value.data.invite_list_total || 0
|
||||
finished.value = data.value.data.invite_list.length < pageSize
|
||||
if (!finished.value) {
|
||||
page.value++
|
||||
}
|
||||
} else {
|
||||
finished.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
// 切换标签页
|
||||
const switchTab = (tab) => {
|
||||
if (activeTab.value === tab) return
|
||||
activeTab.value = tab
|
||||
page.value = 1
|
||||
finished.value = false
|
||||
loading.value = false
|
||||
// 延迟一下确保tab切换完成
|
||||
setTimeout(() => {
|
||||
fetchDetail()
|
||||
}, 100)
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
const onRefresh = () => {
|
||||
finished.value = false
|
||||
page.value = 1
|
||||
fetchDetail().finally(() => {
|
||||
refreshing.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 获取等级标签样式
|
||||
const getLevelClass = (level) => {
|
||||
const levelNum = typeof level === 'number' ? level : parseInt(level)
|
||||
switch (levelNum) {
|
||||
case 3:
|
||||
return 'bg-purple-100 text-purple-600'
|
||||
case 2:
|
||||
return 'bg-yellow-100 text-yellow-600'
|
||||
case 1:
|
||||
default:
|
||||
return 'bg-gray-100 text-gray-600'
|
||||
}
|
||||
}
|
||||
|
||||
// 获取等级显示名称
|
||||
const getLevelName = (level) => {
|
||||
const levelNum = typeof level === 'number' ? level : parseInt(level)
|
||||
const levelMap = {
|
||||
1: '普通',
|
||||
2: '黄金',
|
||||
3: '钻石'
|
||||
}
|
||||
return levelMap[levelNum] || '普通'
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = timeStr => {
|
||||
if (!timeStr) return '-'
|
||||
return timeStr.split(' ')[0]
|
||||
}
|
||||
|
||||
// 格式化金额
|
||||
const formatNumber = num => {
|
||||
if (!num) return '0.00'
|
||||
return Number(num).toFixed(2)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="subordinate-detail">
|
||||
<!-- 用户信息卡片 -->
|
||||
<div class="p-4">
|
||||
<div class="bg-white rounded-xl shadow-sm p-5 mb-4">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="text-xl font-semibold text-gray-800">{{ userInfo.mobile }}</div>
|
||||
<span class="px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-600">
|
||||
{{ userInfo.level }}代理
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500 mb-4">成为下级代理时间:{{ formatTime(userInfo.createTime) }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 订单统计卡片 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-4 mb-4">
|
||||
<div class="text-base font-medium text-gray-800 mb-3">订单统计(仅统计有返佣的订单)</div>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div class="text-center">
|
||||
<div class="text-gray-500 text-sm mb-1">总订单量</div>
|
||||
<div class="text-xl font-semibold text-blue-600">{{ orderStats.total_orders || 0 }}</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-gray-500 text-sm mb-1">月订单</div>
|
||||
<div class="text-xl font-semibold text-green-600">{{ orderStats.month_orders || 0 }}</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-gray-500 text-sm mb-1">今日订单</div>
|
||||
<div class="text-xl font-semibold text-orange-600">{{ orderStats.today_orders || 0 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 返佣统计卡片 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-4 mb-4">
|
||||
<div class="text-base font-medium text-gray-800 mb-3">返佣统计</div>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div class="text-center">
|
||||
<div class="text-gray-500 text-sm mb-1">总返佣金额</div>
|
||||
<div class="text-xl font-semibold text-blue-600">¥{{ formatNumber(rebateStats.total_rebate_amount) }}</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-gray-500 text-sm mb-1">月返佣金额</div>
|
||||
<div class="text-xl font-semibold text-green-600">¥{{ formatNumber(rebateStats.month_rebate_amount) }}</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-gray-500 text-sm mb-1">今日返佣金额</div>
|
||||
<div class="text-xl font-semibold text-orange-600">¥{{ formatNumber(rebateStats.today_rebate_amount) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 邀请统计卡片 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-4 mb-4">
|
||||
<div class="text-base font-medium text-gray-800 mb-3">邀请统计</div>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div class="text-center">
|
||||
<div class="text-gray-500 text-sm mb-1">总邀请</div>
|
||||
<div class="text-xl font-semibold text-blue-600">{{ inviteStats.total_invites || 0 }}</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-gray-500 text-sm mb-1">月邀请</div>
|
||||
<div class="text-xl font-semibold text-green-600">{{ inviteStats.month_invites || 0 }}</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-gray-500 text-sm mb-1">今日邀请</div>
|
||||
<div class="text-xl font-semibold text-orange-600">{{ inviteStats.today_invites || 0 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab标签页 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-4 mb-4">
|
||||
<van-tabs v-model:active="activeTab" @change="switchTab">
|
||||
<van-tab title="订单列表" name="order">
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="fetchDetail">
|
||||
<div class="p-2">
|
||||
<div v-if="orderList.length === 0" class="text-center text-gray-500 py-8">暂无订单记录</div>
|
||||
<div v-else v-for="item in orderList" :key="item.order_no"
|
||||
class="order-item mb-3 border-b border-gray-200 pb-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-gray-800 mb-1">{{ item.product_name || '未知产品' }}</div>
|
||||
<div class="text-xs text-gray-500 mb-1">订单号:{{ item.order_no }}</div>
|
||||
<div class="text-xs text-gray-500">{{ formatTime(item.create_time) }}</div>
|
||||
</div>
|
||||
<div class="text-right ml-4">
|
||||
<div class="text-sm text-gray-500 mb-1">订单金额</div>
|
||||
<div class="text-base font-semibold text-blue-600 mb-2">¥{{ formatNumber(item.order_amount) }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-500 mb-1">返佣金额</div>
|
||||
<div class="text-base font-semibold text-green-600">¥{{ formatNumber(item.rebate_amount) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
</van-tab>
|
||||
<van-tab title="邀请列表" name="invite">
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="fetchDetail">
|
||||
<div class="p-2">
|
||||
<div v-if="inviteList.length === 0" class="text-center text-gray-500 py-8">暂无邀请记录</div>
|
||||
<div v-else v-for="item in inviteList" :key="item.agent_id"
|
||||
class="invite-item mb-3 border-b border-gray-200 pb-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-3 flex-1">
|
||||
<div class="text-lg font-semibold text-gray-800">{{ item.mobile }}</div>
|
||||
<span :class="['px-3 py-1 rounded-full text-sm font-medium', getLevelClass(item.level)]">
|
||||
{{ item.level_name }}代理
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">{{ formatTime(item.create_time) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.subordinate-detail {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.order-item,
|
||||
.invite-item {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.order-item:active,
|
||||
.invite-item:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
</style>
|
||||
227
src/views/SubordinateList.vue
Normal file
227
src/views/SubordinateList.vue
Normal file
@@ -0,0 +1,227 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useAgentStore } from '@/stores/agentStore'
|
||||
import { getSubordinateList } from '@/api/agent'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const agentStore = useAgentStore()
|
||||
const subordinates = ref([])
|
||||
const loading = ref(false)
|
||||
const finished = ref(false)
|
||||
const page = ref(1)
|
||||
const pageSize = 8
|
||||
const refreshing = ref(false)
|
||||
const router = useRouter()
|
||||
|
||||
// 加载更多数据
|
||||
const onLoad = async () => {
|
||||
if (!finished.value) {
|
||||
await fetchSubordinates()
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
// 初始化时重置状态
|
||||
page.value = 1
|
||||
finished.value = false
|
||||
})
|
||||
// 计算统计数据
|
||||
const statistics = ref({
|
||||
totalSubordinates: 0,
|
||||
})
|
||||
|
||||
// 获取下级列表
|
||||
const fetchSubordinates = async () => {
|
||||
if (loading.value || finished.value) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const { data, error } = await getSubordinateList({
|
||||
page: page.value,
|
||||
page_size: pageSize
|
||||
})
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
statistics.value.totalSubordinates = data.value.data.total
|
||||
if (page.value === 1) {
|
||||
subordinates.value = data.value.data.list || []
|
||||
} else {
|
||||
subordinates.value.push(...(data.value.data.list || []))
|
||||
}
|
||||
|
||||
// 判断是否加载完成
|
||||
if (data.value.data.list && data.value.data.list.length < pageSize) {
|
||||
finished.value = true
|
||||
} else if (subordinates.value.length >= data.value.data.total) {
|
||||
finished.value = true
|
||||
} else {
|
||||
page.value++
|
||||
}
|
||||
} else {
|
||||
// 接口返回错误,停止翻页
|
||||
finished.value = true
|
||||
console.error('获取下级列表失败:', data.value.msg || '未知错误')
|
||||
}
|
||||
} else {
|
||||
// 请求失败或返回错误,停止翻页
|
||||
finished.value = true
|
||||
console.error('获取下级列表失败:', error.value || '请求失败')
|
||||
}
|
||||
} catch (err) {
|
||||
// 捕获异常,停止翻页
|
||||
finished.value = true
|
||||
console.error('获取下级列表失败:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
const onRefresh = () => {
|
||||
finished.value = false
|
||||
page.value = 1
|
||||
fetchSubordinates().finally(() => {
|
||||
refreshing.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = timeStr => {
|
||||
if (!timeStr) return '-'
|
||||
return timeStr.split(' ')[0]
|
||||
}
|
||||
|
||||
// 格式化金额
|
||||
const formatNumber = num => {
|
||||
if (!num) return '0.00'
|
||||
return Number(num).toFixed(2)
|
||||
}
|
||||
|
||||
// 获取等级标签样式(新系统:1=普通,2=黄金,3=钻石)
|
||||
const getLevelClass = (level) => {
|
||||
// level可能是数字或字符串
|
||||
const levelNum = typeof level === 'number' ? level : parseInt(level)
|
||||
switch (levelNum) {
|
||||
case 3:
|
||||
return 'bg-purple-100 text-purple-600'
|
||||
case 2:
|
||||
return 'bg-yellow-100 text-yellow-600'
|
||||
case 1:
|
||||
default:
|
||||
return 'bg-gray-100 text-gray-600'
|
||||
}
|
||||
}
|
||||
|
||||
// 获取等级显示名称
|
||||
const getLevelName = (level) => {
|
||||
const levelNum = typeof level === 'number' ? level : parseInt(level)
|
||||
const levelMap = {
|
||||
1: '普通',
|
||||
2: '黄金',
|
||||
3: '钻石'
|
||||
}
|
||||
return levelMap[levelNum] || '普通'
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const viewDetail = item => {
|
||||
router.push({
|
||||
name: 'subordinateDetail',
|
||||
params: { id: item.agent_id || item.id },
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchSubordinates()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="subordinate-list">
|
||||
<!-- 顶部统计卡片 -->
|
||||
<div class="p-4 pb-0">
|
||||
<div class="bg-white rounded-xl shadow-sm p-4">
|
||||
<div class="flex items-center justify-center">
|
||||
<div class="text-center">
|
||||
<div class="text-gray-500 text-sm mb-1">下级总数</div>
|
||||
<div class="text-2xl font-semibold text-blue-600">{{ statistics.totalSubordinates }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
|
||||
<div class="p-4">
|
||||
<div v-for="(item, index) in subordinates" :key="item.id" class="subordinate-item">
|
||||
<div class="flex flex-col p-5 bg-white rounded-xl shadow-sm mb-4">
|
||||
<!-- 顶部信息 -->
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div
|
||||
class="w-6 h-6 flex items-center justify-center bg-blue-100 text-blue-600 rounded-full text-sm font-medium">
|
||||
{{ index + 1 }}
|
||||
</div>
|
||||
<div class="text-xl font-semibold text-gray-800">{{ item.mobile }}</div>
|
||||
<span
|
||||
:class="['px-3 py-1 rounded-full text-sm font-medium', getLevelClass(item.level || item.level_name)]">
|
||||
{{ getLevelName(item.level || item.level_name) }}代理
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加入时间 -->
|
||||
<div class="text-sm text-gray-500 mb-5">成为下级代理时间:{{ item.create_time }}</div>
|
||||
|
||||
<!-- 数据统计 -->
|
||||
<div class="grid grid-cols-2 gap-6 mb-5">
|
||||
<div class="text-center">
|
||||
<div class="text-gray-500 text-sm mb-1">总订单数</div>
|
||||
<div class="text-xl font-semibold text-blue-600">{{ item.total_orders || 0 }}</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-gray-500 text-sm mb-1">总金额</div>
|
||||
<div class="text-xl font-semibold text-green-600">¥{{ formatNumber(item.total_amount) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 查看详情按钮 -->
|
||||
<div class="flex justify-end">
|
||||
<button @click="viewDetail(item)"
|
||||
class="inline-flex items-center px-4 py-2 text-sm bg-gradient-to-r from-blue-500 to-blue-400 text-white rounded-full shadow-sm hover:shadow-md transition-all duration-200">
|
||||
<van-icon name="eye" class="mr-1.5" />
|
||||
查看详情
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.subordinate-list {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.subordinate-item {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.subordinate-item:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
button {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
</style>
|
||||
401
src/views/TeamList.vue
Normal file
401
src/views/TeamList.vue
Normal file
@@ -0,0 +1,401 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeMount } from 'vue'
|
||||
import { useAgentStore } from '@/stores/agentStore'
|
||||
import { getTeamList } from '@/api/agent'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const agentStore = useAgentStore()
|
||||
const teamMembers = ref([])
|
||||
const loading = ref(false)
|
||||
const finished = ref(false)
|
||||
const page = ref(1)
|
||||
const pageSize = 8
|
||||
const refreshing = ref(false)
|
||||
const router = useRouter()
|
||||
const searchMobile = ref('')
|
||||
|
||||
// 加载更多数据
|
||||
const onLoad = async () => {
|
||||
if (!finished.value) {
|
||||
await fetchTeamMembers()
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
// 初始化时重置状态
|
||||
page.value = 1
|
||||
finished.value = false
|
||||
})
|
||||
|
||||
|
||||
// 获取团队列表
|
||||
const fetchTeamMembers = async () => {
|
||||
if (loading.value || finished.value) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
page: page.value,
|
||||
page_size: pageSize
|
||||
}
|
||||
// 如果有搜索条件,添加手机号参数
|
||||
if (searchMobile.value && searchMobile.value.trim()) {
|
||||
params.mobile = searchMobile.value.trim()
|
||||
}
|
||||
|
||||
const { data, error } = await getTeamList(params)
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
if (page.value === 1) {
|
||||
teamMembers.value = data.value.data.list || []
|
||||
} else {
|
||||
teamMembers.value.push(...(data.value.data.list || []))
|
||||
}
|
||||
|
||||
// 判断是否加载完成
|
||||
if (data.value.data.list && data.value.data.list.length < pageSize) {
|
||||
finished.value = true
|
||||
} else if (teamMembers.value.length >= data.value.data.total) {
|
||||
finished.value = true
|
||||
} else {
|
||||
page.value++
|
||||
}
|
||||
} else {
|
||||
// 接口返回错误,停止翻页
|
||||
finished.value = true
|
||||
console.error('获取团队列表失败:', data.value.msg || '未知错误')
|
||||
}
|
||||
} else {
|
||||
// 请求失败或返回错误,停止翻页
|
||||
finished.value = true
|
||||
console.error('获取团队列表失败:', error.value || '请求失败')
|
||||
}
|
||||
} catch (err) {
|
||||
// 捕获异常,停止翻页
|
||||
finished.value = true
|
||||
console.error('获取团队列表失败:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
const onRefresh = () => {
|
||||
finished.value = false
|
||||
page.value = 1
|
||||
fetchTeamMembers().finally(() => {
|
||||
refreshing.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 搜索功能
|
||||
const handleSearch = () => {
|
||||
finished.value = false
|
||||
page.value = 1
|
||||
fetchTeamMembers()
|
||||
}
|
||||
|
||||
// 清空搜索
|
||||
const handleClear = () => {
|
||||
searchMobile.value = ''
|
||||
finished.value = false
|
||||
page.value = 1
|
||||
fetchTeamMembers()
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = timeStr => {
|
||||
if (!timeStr) return '-'
|
||||
return timeStr.split(' ')[0]
|
||||
}
|
||||
|
||||
// 格式化金额
|
||||
const formatNumber = num => {
|
||||
if (!num) return '0.00'
|
||||
return Number(num).toFixed(2)
|
||||
}
|
||||
|
||||
// 格式化数字
|
||||
const formatCount = num => {
|
||||
if (!num) return '0'
|
||||
return Number(num).toLocaleString()
|
||||
}
|
||||
|
||||
// 获取等级标签样式(新系统:1=普通,2=黄金,3=钻石)
|
||||
const getLevelClass = (level) => {
|
||||
// level可能是数字或字符串
|
||||
const levelNum = typeof level === 'number' ? level : parseInt(level)
|
||||
switch (levelNum) {
|
||||
case 3:
|
||||
return 'bg-gray-100 text-gray-700'
|
||||
case 2:
|
||||
return 'bg-gray-100 text-gray-700'
|
||||
case 1:
|
||||
default:
|
||||
return 'bg-gray-100 text-gray-700'
|
||||
}
|
||||
}
|
||||
|
||||
// 获取等级显示名称
|
||||
const getLevelName = (level) => {
|
||||
const levelNum = typeof level === 'number' ? level : parseInt(level)
|
||||
const levelMap = {
|
||||
1: '普通',
|
||||
2: '黄金',
|
||||
3: '钻石'
|
||||
}
|
||||
return levelMap[levelNum] || '普通'
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const viewDetail = item => {
|
||||
router.push({
|
||||
name: 'subordinateDetail',
|
||||
params: { id: item.agent_id || item.id },
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchTeamMembers()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="team-list">
|
||||
<!-- 搜索框 -->
|
||||
<div class="px-4 mt-4">
|
||||
<div class="search-box">
|
||||
<van-field v-model="searchMobile" placeholder="请输入手机号搜索" clearable @clear="handleClear"
|
||||
@keyup.enter="handleSearch">
|
||||
<template #button>
|
||||
<van-button size="small" type="primary" @click="handleSearch" class="search-btn">搜索</van-button>
|
||||
</template>
|
||||
</van-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
|
||||
<div class="p-4">
|
||||
<div v-for="(item, index) in teamMembers" :key="item.agent_id || item.id" class="team-member-item">
|
||||
<div class="member-card">
|
||||
<!-- 顶部信息 -->
|
||||
<div class="member-header">
|
||||
<div class="member-index">{{ index + 1 }}</div>
|
||||
<div class="member-mobile">{{ item.mobile }}</div>
|
||||
<span :class="['member-level', getLevelClass(item.level || item.level_name)]">
|
||||
{{ getLevelName(item.level || item.level_name) }}代理
|
||||
</span>
|
||||
<span :class="['member-relation', item.is_direct ? 'member-direct' : 'member-indirect']">
|
||||
{{ item.is_direct ? '直接' : '间接' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 加入时间 -->
|
||||
<div class="member-time">加入团队时间:{{ item.create_time }}</div>
|
||||
|
||||
<!-- 数据统计 -->
|
||||
<div class="member-stats">
|
||||
<div class="member-stat-item">
|
||||
<div class="member-stat-label">今日邀请</div>
|
||||
<div class="member-stat-value">{{ formatCount(item.today_invites || 0) }}</div>
|
||||
</div>
|
||||
<div class="member-stat-item">
|
||||
<div class="member-stat-label">本月邀请</div>
|
||||
<div class="member-stat-value">{{ formatCount(item.month_invites || 0) }}</div>
|
||||
</div>
|
||||
<div class="member-stat-item">
|
||||
<div class="member-stat-label">邀请总人数</div>
|
||||
<div class="member-stat-value">{{ formatCount(item.total_invites || 0) }}</div>
|
||||
</div>
|
||||
<div class="member-stat-item">
|
||||
<div class="member-stat-label">今日查询</div>
|
||||
<div class="member-stat-value">{{ formatCount(item.today_queries || 0) }}</div>
|
||||
</div>
|
||||
<div class="member-stat-item">
|
||||
<div class="member-stat-label">本月查询</div>
|
||||
<div class="member-stat-value">{{ formatCount(item.month_queries || 0) }}</div>
|
||||
</div>
|
||||
|
||||
<div class="member-stat-item">
|
||||
<div class="member-stat-label">查询总单量</div>
|
||||
<div class="member-stat-value">{{ formatCount(item.total_queries || 0) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 查看详情按钮 -->
|
||||
<div class="member-action">
|
||||
<button @click="viewDetail(item)" class="detail-btn">
|
||||
<van-icon name="eye" class="mr-1" />
|
||||
查看详情
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.team-list {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
/* 成员卡片样式 - 更紧凑 */
|
||||
.member-card {
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.member-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.member-index {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #e5e7eb;
|
||||
color: #4b5563;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.member-mobile {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.member-level {
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.member-relation {
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.member-direct {
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
}
|
||||
|
||||
.member-indirect {
|
||||
background: #dbeafe;
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
.member-time {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.member-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.member-stat-item {
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
background: #f9fafb;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.member-stat-label {
|
||||
font-size: 11px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.member-stat-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.member-action {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.detail-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 6px 16px;
|
||||
font-size: 13px;
|
||||
background: #3b82f6;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.detail-btn:active {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
.team-member-item {
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.team-member-item:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 搜索框样式 */
|
||||
.search-box {
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
:deep(.van-field) {
|
||||
background: #ffffff;
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
:deep(.van-field__control) {
|
||||
font-size: 14px;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
background: #3b82f6;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 6px 16px;
|
||||
font-size: 13px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.search-btn:active {
|
||||
background: #2563eb;
|
||||
}
|
||||
</style>
|
||||
573
src/views/UpgradeSubordinate.vue
Normal file
573
src/views/UpgradeSubordinate.vue
Normal file
@@ -0,0 +1,573 @@
|
||||
<template>
|
||||
<div class="upgrade-subordinate-page">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<h1 class="page-title">调整下级级别</h1>
|
||||
<p class="page-desc">钻石代理可以将团队中的普通代理升级为黄金代理</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 说明卡片 -->
|
||||
<div class="info-card">
|
||||
<div class="info-content">
|
||||
<van-icon name="info-o" class="info-icon" />
|
||||
<div class="info-text">
|
||||
<p class="info-title">调整说明</p>
|
||||
<ul class="info-list">
|
||||
<li>仅可升级普通代理为黄金代理</li>
|
||||
<li>调整操作免费,无需支付费用</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<div class="search-section">
|
||||
<van-field v-model="searchMobile" placeholder="请输入手机号搜索" clearable @clear="handleClear"
|
||||
@keyup.enter="handleSearch">
|
||||
<template #button>
|
||||
<van-button size="small" type="primary" @click="handleSearch" class="search-btn">搜索</van-button>
|
||||
</template>
|
||||
</van-field>
|
||||
</div>
|
||||
|
||||
<!-- 代理列表 -->
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
|
||||
<div class="list-container">
|
||||
<div v-if="teamMembers.length === 0 && !loading" class="empty-state">
|
||||
<van-icon name="user-o" class="empty-icon" />
|
||||
<p class="empty-text">暂无可调整的普通代理</p>
|
||||
</div>
|
||||
|
||||
<div v-for="(item, index) in teamMembers" :key="item.agent_id || item.id" class="member-card">
|
||||
<div class="member-header">
|
||||
<div class="member-index">{{ index + 1 }}</div>
|
||||
<div class="member-info">
|
||||
<div class="member-mobile">{{ item.mobile || '未绑定手机' }}</div>
|
||||
<div class="member-time">加入时间:{{ formatTime(item.create_time) }}</div>
|
||||
</div>
|
||||
<div class="member-badge">
|
||||
<span class="level-badge level-normal">普通代理</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="member-stats">
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">总查询量</div>
|
||||
<div class="stat-value">{{ formatCount(item.total_queries || 0) }}</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">返佣总额</div>
|
||||
<div class="stat-value">¥{{ formatNumber(item.total_rebate_amount || 0) }}</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">邀请人数</div>
|
||||
<div class="stat-value">{{ formatCount(item.total_invites || 0) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="member-action">
|
||||
<van-button type="primary" size="small" :loading="item.upgrading"
|
||||
@click="handleUpgrade(item)">
|
||||
<van-icon name="arrow-up" class="mr-1" />
|
||||
调整为黄金代理
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
|
||||
<!-- 确认升级弹窗 -->
|
||||
<van-popup v-model:show="showConfirmDialog" round position="center"
|
||||
:style="{ width: '85%', maxWidth: '400px' }">
|
||||
<div class="confirm-dialog">
|
||||
<div class="confirm-header">
|
||||
<van-icon name="warning-o" class="warning-icon" />
|
||||
<h3 class="confirm-title">确认升级</h3>
|
||||
</div>
|
||||
<div class="confirm-content">
|
||||
<p class="confirm-text">
|
||||
确定要将 <span class="highlight">{{ currentUpgradeItem?.mobile }}</span> 升级为黄金代理吗?
|
||||
</p>
|
||||
<div class="confirm-notice">
|
||||
<p>• 升级操作不可撤销</p>
|
||||
<p>• 升级操作免费</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="confirm-actions">
|
||||
<van-button plain @click="showConfirmDialog = false">取消</van-button>
|
||||
<van-button type="primary" :loading="isUpgrading" @click="confirmUpgrade">确认升级</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeMount, computed } from 'vue'
|
||||
import { useAgentStore } from '@/stores/agentStore'
|
||||
import { getTeamList, upgradeSubordinate } from '@/api/agent'
|
||||
import { showToast, showSuccessToast, showFailToast } from 'vant'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const router = useRouter()
|
||||
const agentStore = useAgentStore()
|
||||
const { isAgent, level } = storeToRefs(agentStore)
|
||||
const teamMembers = ref([])
|
||||
const loading = ref(false)
|
||||
const finished = ref(false)
|
||||
const page = ref(1)
|
||||
const pageSize = 10
|
||||
const refreshing = ref(false)
|
||||
const searchMobile = ref('')
|
||||
const showConfirmDialog = ref(false)
|
||||
const currentUpgradeItem = ref(null)
|
||||
const isUpgrading = ref(false)
|
||||
|
||||
// 检查是否是钻石代理
|
||||
const isDiamondAgent = computed(() => {
|
||||
return isAgent.value && level.value === 3
|
||||
})
|
||||
|
||||
// 加载更多数据
|
||||
const onLoad = async () => {
|
||||
if (!finished.value) {
|
||||
await fetchTeamMembers()
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
page.value = 1
|
||||
finished.value = false
|
||||
})
|
||||
|
||||
// 获取团队列表(仅普通代理)
|
||||
const fetchTeamMembers = async () => {
|
||||
if (loading.value || finished.value) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
page: page.value,
|
||||
page_size: pageSize
|
||||
}
|
||||
if (searchMobile.value && searchMobile.value.trim()) {
|
||||
params.mobile = searchMobile.value.trim()
|
||||
}
|
||||
|
||||
const { data, error } = await getTeamList(params)
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
let list = data.value.data.list || []
|
||||
|
||||
// 过滤出普通代理(level === 1)
|
||||
list = list.filter(item => {
|
||||
const agentLevel = item.level || (item.level_name === '普通' ? 1 : item.level_name === '黄金' ? 2 : item.level_name === '钻石' ? 3 : 1)
|
||||
return agentLevel === 1
|
||||
})
|
||||
|
||||
if (page.value === 1) {
|
||||
teamMembers.value = list
|
||||
} else {
|
||||
teamMembers.value.push(...list)
|
||||
}
|
||||
|
||||
// 判断是否加载完成
|
||||
if (list.length < pageSize) {
|
||||
finished.value = true
|
||||
} else if (teamMembers.value.length >= data.value.data.total) {
|
||||
finished.value = true
|
||||
} else {
|
||||
page.value++
|
||||
}
|
||||
} else {
|
||||
// 接口返回错误,停止翻页
|
||||
finished.value = true
|
||||
showFailToast(data.value.msg || '获取团队列表失败')
|
||||
}
|
||||
} else {
|
||||
// 请求失败或返回错误,停止翻页
|
||||
finished.value = true
|
||||
showFailToast('获取团队列表失败')
|
||||
console.error('获取团队列表失败:', error.value || '请求失败')
|
||||
}
|
||||
} catch (err) {
|
||||
// 捕获异常,停止翻页
|
||||
finished.value = true
|
||||
console.error('获取团队列表失败:', err)
|
||||
showFailToast('获取团队列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
const onRefresh = () => {
|
||||
finished.value = false
|
||||
page.value = 1
|
||||
fetchTeamMembers().finally(() => {
|
||||
refreshing.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 搜索功能
|
||||
const handleSearch = () => {
|
||||
finished.value = false
|
||||
page.value = 1
|
||||
fetchTeamMembers()
|
||||
}
|
||||
|
||||
// 清空搜索
|
||||
const handleClear = () => {
|
||||
searchMobile.value = ''
|
||||
finished.value = false
|
||||
page.value = 1
|
||||
fetchTeamMembers()
|
||||
}
|
||||
|
||||
// 处理升级
|
||||
const handleUpgrade = (item) => {
|
||||
currentUpgradeItem.value = item
|
||||
showConfirmDialog.value = true
|
||||
}
|
||||
|
||||
// 确认升级
|
||||
const confirmUpgrade = async () => {
|
||||
if (!currentUpgradeItem.value) return
|
||||
|
||||
isUpgrading.value = true
|
||||
try {
|
||||
const { data, error } = await upgradeSubordinate({
|
||||
subordinate_id: currentUpgradeItem.value.agent_id || currentUpgradeItem.value.id,
|
||||
to_level: 2 // 只能升级为黄金代理
|
||||
})
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
showSuccessToast('升级成功')
|
||||
showConfirmDialog.value = false
|
||||
currentUpgradeItem.value = null
|
||||
|
||||
// 刷新列表
|
||||
finished.value = false
|
||||
page.value = 1
|
||||
await fetchTeamMembers()
|
||||
|
||||
// 刷新代理状态
|
||||
await agentStore.fetchAgentStatus()
|
||||
} else {
|
||||
showFailToast(data.value.msg || '升级失败')
|
||||
}
|
||||
} else {
|
||||
showFailToast('升级失败,请重试')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('升级失败:', err)
|
||||
showFailToast('升级失败,请重试')
|
||||
} finally {
|
||||
isUpgrading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (timeStr) => {
|
||||
if (!timeStr) return '-'
|
||||
return timeStr.split(' ')[0]
|
||||
}
|
||||
|
||||
// 格式化金额
|
||||
const formatNumber = (num) => {
|
||||
if (!num) return '0.00'
|
||||
return Number(num).toFixed(2)
|
||||
}
|
||||
|
||||
// 格式化数字
|
||||
const formatCount = (num) => {
|
||||
if (!num) return '0'
|
||||
return Number(num).toLocaleString()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 检查权限
|
||||
if (!isDiamondAgent.value) {
|
||||
showFailToast('只有钻石代理可以升级下级')
|
||||
router.back()
|
||||
return
|
||||
}
|
||||
fetchTeamMembers()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.upgrade-subordinate-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f7fa;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
background: linear-gradient(120deg, #34c9ad 70%, #64d2ff 100%);
|
||||
padding: 20px 16px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-desc {
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 说明卡片 */
|
||||
.info-card {
|
||||
margin: 16px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.info-content {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
font-size: 24px;
|
||||
color: #1677ff;
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.info-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.info-list {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.info-list li {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
/* 搜索区域 */
|
||||
.search-section {
|
||||
margin: 0 16px 16px;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* 列表容器 */
|
||||
.list-container {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: 16px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 16px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 成员卡片 */
|
||||
.member-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.member-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.member-index {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #e5e7eb;
|
||||
color: #4b5563;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.member-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.member-mobile {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.member-time {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.member-badge {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.level-badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.level-normal {
|
||||
background: #e5e7eb;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.member-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
padding: 12px;
|
||||
background: #f9fafb;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.member-action {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.mr-1 {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* 确认弹窗 */
|
||||
.confirm-dialog {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.confirm-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.warning-icon {
|
||||
font-size: 24px;
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.confirm-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.confirm-content {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.confirm-text {
|
||||
font-size: 15px;
|
||||
color: #374151;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: #1677ff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.confirm-notice {
|
||||
background: #fef3c7;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
font-size: 13px;
|
||||
color: #92400e;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.confirm-notice p {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.confirm-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.confirm-actions .van-button {
|
||||
flex: 1;
|
||||
max-width: 120px;
|
||||
}
|
||||
</style>
|
||||
286
src/views/UserAgreement.vue
Normal file
286
src/views/UserAgreement.vue
Normal file
@@ -0,0 +1,286 @@
|
||||
<script setup>
|
||||
const companyName = import.meta.env.VITE_COMPANY_NAME
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<!-- 页面标题 -->
|
||||
<div class="mb-4 text-center text-lg font-bold">用户协议</div>
|
||||
|
||||
<!-- 内容主体 -->
|
||||
<div class="indent-[2em]">
|
||||
<div class="mb-4 leading-relaxed">
|
||||
本协议是您(以下又称“用户”)在使用本服务时,约定您和{{ companyName }}之间权利义务关系的有效协议。
|
||||
</div>
|
||||
|
||||
<div class="mb-4 leading-relaxed">
|
||||
在您使用本服务前,请您务必仔细阅读本协议,特别是隐私权保护及授权条款、免除或者限制{{ companyName
|
||||
}}责任的条款、争议解决和法律适用条款。一旦您有对本服务的任何部分或全部的注册、查看、定制、使用等任何使用行为,即视为您已充分阅读、理解并接受本协议的全部内容,并与{{ companyName
|
||||
}}达成本协议。如您对本协议有任何疑问,应向{{ companyName }}客服咨询。如果您不同意本协议的部分或全部约定,您应立即停止使用本服务。
|
||||
</div>
|
||||
<div class="mb-4 leading-relaxed">
|
||||
您与{{ companyName }}达成本协议后,您承诺接受并遵守本协议的约定,并不得以未阅读本协议的内容或者未获得{{ companyName
|
||||
}}对您问询的解答等理由,主张本协议无效,或要求撤销本协议。在本协议履行过程中,{{ companyName
|
||||
}}可以依其单独判断暂时停止提供、限制或改变本服务,并有权根据自身业务需要修订本协议。一旦本协议的内容发生变动,{{ companyName
|
||||
}}将通过平台公布最新的服务协议,不再向您作个别通知。如果您不同意{{ companyName }}对本服务协议所做的修改,您应立即停止使用本服务或通过{{ companyName }}客服与{{
|
||||
companyName }}联系。如果您继续使用本服务,则视为您接受{{ companyName }}对本协议所做的修改,并应遵照修改后的协议执行。
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<div class="mb-2 font-bold leading-relaxed">一、服务内容</div>
|
||||
<div class="leading-relaxed">
|
||||
本服务向您提供多项个人信息整理服务。您知悉并认可,如您需使用该类服务,必须满足如下所述条件;且您承诺,您向{{ companyName }}提请服务申请时,已经满足如下所述条件。
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
<div>A.您已注册成为本服务的会员;</div>
|
||||
<div>
|
||||
B.您已在服务页面对应框中填写被查询主体的姓名、身份证号、手机号、银行卡号和被查询主体的手机号收到的动态验证码(以下称“被查询主体信息”);
|
||||
</div>
|
||||
<div>
|
||||
C.您确保被查询主体信息是您本人的信息或者被查询主体已授权您本人使用被查询主体信息进行查询(授权内容应包括本条D项所述内容),并且被查询主体已知悉该授权的风险。
|
||||
</div>
|
||||
<div>
|
||||
D.被查询主体不可撤销地授权{{ companyName
|
||||
}}为查询、评估被查询主体的信息状况:a.可以委托合法存续的第三方机构收集、查询、验证、使用并提供您或被查询主体的个人信息;b.可以向数据源机构采集您或被查询主体的个人信息;c.可以整理、保存、加工、使用您或被查询主体的个人信息,并向您提供数据报告;d.可以向为您提供服务的第三方商户提供脱敏后的个人信息或数据报告。本条所述的个人信息包括但不限于身份信息、联系方式、职业和居住地址等个人基本信息,个人社保、公积金、收入及在商业活动中形成的各类交易记录,个人公共费用缴纳、违法违规信息、财产状况等;
|
||||
</div>
|
||||
<div>
|
||||
E.被查询主体已被明确告知提供被查询主体信息并作出D项授权可能给被查询主体带来的各类损失以及其他可能的不利后果,包括采集上述个人信息对被查询主体信用方面可能产生不良影响以及上述信息被信息使用者依法提供给第三方后被他人不当利用的风险。
|
||||
</div>
|
||||
<div>F.您已全额支付相应的查询服务费用;</div>
|
||||
<div>
|
||||
G.验证码请不要轻易提供给他人,一旦填入手机号对应验证码,视为手机号机主本人操作。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<div class="mb-2 font-bold leading-relaxed">
|
||||
二、服务中断或故障
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
您同意,因下列原因导致{{ companyName }}无法正常提供本服务的,{{ companyName }}不承担责任:
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
<div>(1)承载本服务的系统停机维护期间;</div>
|
||||
<div>
|
||||
(2)您的电脑、手机软硬件和通信线路、供电线路出现故障的;
|
||||
</div>
|
||||
<div>
|
||||
(3)您操作不当或通过非{{ companyName }}授权或认可的方式使用本服务的;
|
||||
</div>
|
||||
<div>
|
||||
(4)因病毒、木马、恶意程序攻击、网络拥堵、系统不稳定、系统或设备故障、通讯故障、电力故障或政府行为等原因;
|
||||
</div>
|
||||
<div>
|
||||
(5)由于黑客攻击、网络供应商技术调整或故障、网站升级、手机运营商系统方面的问题等原因而造成的本服务中断或延迟;
|
||||
</div>
|
||||
<div>
|
||||
(6)因台风、地震、海啸、洪水、停电、战争、恐怖袭击等不可抗力之因素,造成本服务系统障碍不能执行业务的。
|
||||
</div>
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
{{ companyName }}不对因使用本服务而对用户造成的间接的、附带的、特殊的、后果性的损失承担任何法律责任;尽管有前款约定{{ companyName
|
||||
}}将采取合理行动积极促使本服务恢复正常。
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<div class="mb-2 font-bold leading-relaxed">
|
||||
三、信息的使用和保护
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
{{ companyName }}深知您注重个人信息安全和保护,并理解保护被查询主体个人信息的重要性。
|
||||
{{ companyName }}会严格遵守中国关于收集、使用、保存用户个人信息的相关法律法规,
|
||||
尽最大努力采用相应安全技术和管理手段保护您或被查询主体的个人信息,
|
||||
防止您或被查询主体个人信息遭受未经授权的访问、适用或泄露、毁损、篡改或者丢失。
|
||||
未经您或被查询主体的授权不会向任何第三方提供。
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
您使用本服务,即表示您已授权{{ companyName }}将您相关信息披露给{{ companyName }}关联公司
|
||||
(关联公司是指直接或间接控制于本协议一方的任何法律实体,或者与本协议一方共同于另一法律实体的任何法律实体)使用,
|
||||
且{{ companyName }}关联公司仅为了向您提供服务而使用您的相关信息。
|
||||
如{{ companyName }}关联公司使用您的相关信息,则受本协议约束且会按照与{{ companyName }}同等谨慎程度保护您的相关信息。
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
在您使用本服务过程中,特别是在申请提现、实名认证或佣金结算时,您需要提供包括但不限于姓名、身份证号、银行卡号、手机号、税务身份信息等个人资料。
|
||||
您同意我们为履行合同义务、税务申报、身份核验、财务结算等必要目的,收集、使用、存储并在必要范围内共享该等信息。
|
||||
在进行税务代扣代缴、结算服务时,我们有权将必要信息提供给依法合作的第三方税务服务商、结算服务商,前提是该第三方承担同等信息保护义务。
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
您有权查询、更正您的个人信息,也可以根据平台流程申请注销账户或停止使用相关服务,我们将根据法律要求妥善处理相关信息。
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
{{ companyName }}就下列原因导致的您或被查询主体个人信息的泄露,不承担任何法律责任:
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
<div>
|
||||
(1)由于您个人原因将本服务的会员账号和密码告知他人或与他人共享{{ companyName }}服务账户,由此导致的与您相关的信息的泄露。
|
||||
</div>
|
||||
<div>
|
||||
(2)您使用第三方提供的服务(包括您向第三方提供的任何个人信息),须受第三方自己的服务条款及个人信息保护协议(而非本协议)约束,您需要仔细阅读其条款。本协议仅适用于{{ companyName
|
||||
}}所提供的服务,并不适用于任何第三方提供的服务或第三方的信息使用规则,{{ companyName }}对任何第三方使用由您提供的信息不承担任何责任。
|
||||
</div>
|
||||
<div>
|
||||
(3)根据相关的法律法规、相关政府主管部门或相关证券交易所的要求提供、公布与您相关的信息。
|
||||
</div>
|
||||
<div>
|
||||
(4)或其他非因{{ companyName }}原因导致的与您相关的信息的泄露。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<!-- 第四部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">
|
||||
四、用户声明与保证
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
<div>
|
||||
(1)您使用本服务的前提是您依照适用的法律,是具有完全民事权利和民事行为能力,能够独立承担民事责任的自然人。
|
||||
</div>
|
||||
<div>
|
||||
(2)您如违反本协议第一条款中的承诺,您可能会对他人造成侵权。如由此给{{ companyName }}或他人造成损失的,您需依照法律法规规定承担相应的法律责任。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- 第五部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">
|
||||
五、知识产权保护
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
本服务涉及的文档资料、软件、商标、图案、排版设计等(以下简称“{{ companyName }}产品”)的著作权、商标以及其他知识产权或权益均为{{ companyName }}享有或{{
|
||||
companyName }}获得授权使用。
|
||||
用户不得出租、出借、拷贝、仿冒、复制或修改{{ companyName }}产品任何部分或用于其他任何商业目的,
|
||||
也不得将{{ companyName }}产品做反向工程、反编译或反汇编,或以其他方式或工具取得{{ companyName }}产品之目标程序或源代码。
|
||||
如果用户违反此约定,造成{{ companyName }}及其他任何第三方任何损失的,甲方应予以全额赔偿。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- 第六部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">六、隐私保护</div>
|
||||
<div class="leading-relaxed">
|
||||
真爱查保证不对外公开或向第三方提供单个用户的注册资料及存储在真爱查的非公开内容,但下列情况下除外:
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
<div>1. 事先获得用户的明确授权;</div>
|
||||
<div>2. 根据有关的法律法规要求;</div>
|
||||
<div>3. 按照有关政府部门的要求;</div>
|
||||
<div>4. 为维护社会公众的利益;</div>
|
||||
<div>5. 为维护真爱查的合法利益。</div>
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
在不透露单个用户隐私资料的前提下,真爱查有权利对整个用户数据库进行分析并对用户数据库进行商业上的利用。
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<!-- 第七部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">七、免责条款</div>
|
||||
<div class="leading-relaxed">
|
||||
<div>
|
||||
(一)不管基于任何直接的、间接的、特殊的、惩罚性的、惩戒性的、附带的、或结果性的损害、损失或费用,我们均不对其承担责任。即使有人告知我们或我们的员工存在出现这些损害、损失或费用的可能性。这些损害、损失或费用由以下这些情况引起或与这些情况有关:
|
||||
</div>
|
||||
<div>1. 使用我们网站上或其他链接网站上的信息;</div>
|
||||
<div>2. 无法使用这些信息;</div>
|
||||
<div>
|
||||
3.
|
||||
任何在操作或传输中出现的操作失败、错误、遗漏、中断、缺陷、延迟,计算机病毒,断线或系统运行失败。
|
||||
</div>
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
<div>
|
||||
(二)我们可以在不事先通知的情况下更改信息,并且不承担更新这些信息的义务。不经任何种类的授权,不做任何专门或暗指或法定的不侵犯第三方权利、名称、可出售性、出于某种特殊目的适当措施或不携带计算机病毒的保证。
|
||||
</div>
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
<div>
|
||||
(三)我们不对您查询信息内容的正确性、适当性、完整性、准确性、可靠性或适时性做出任何证明、声明和保证。我们不对任何因个人平台产生的错误、遗漏及失准承担任何责任。
|
||||
</div>
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
<div>
|
||||
(四)对于由于您违反本协议导致任何第三方针对我们及或我们的员工提出的任何申诉、起诉、要求或者诉讼或者其他法律程序,您同意自费作出赔偿并令其免受上述损害。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- 第八部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">八、违约</div>
|
||||
<div class="leading-relaxed">
|
||||
用户不得利用本服务进行任何损害{{ companyName }}及其他第三方权益的行为,否则{{ companyName }}有权立即终止为该用户提供本服务,并要求用户赔偿损失。由此产生的任何后果由用户自行承担,与{{ companyName }}无关。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- 第九部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">
|
||||
九、数据来源及准确性说明
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
本产品数据来源于第三方,可能因数据未公开、更新延迟或信息受到限制,因此不一定能完全返回。不同数据格式及记录详细程度会有所差异,这是行业正常现象。本报告仅供参考,请结合实际情况做出决策。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- 第十部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">十、退款协议</div>
|
||||
<div class="leading-relaxed">
|
||||
除非由于本程序的技术性问题导致用户无法正常使用本产品,否则我们不提供任何退款服务。
|
||||
用户在购买前应仔细阅读本用户协议及相关使用条款,确保对本产品有充分了解。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- 第十一部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">
|
||||
十一、协议的变更和终止
|
||||
</div>
|
||||
<div class="leading-relaxed">
|
||||
鉴于网络服务的特殊性,我们变更本协议及其附件的若干条款时,将提前通过我们平台公告有关变更事项。
|
||||
修订后的条款或将来可能发布或更新的各类规则-经在我们平台公布后,立即自动生效。
|
||||
如您不同意相关修订,应当立即停止使用该项服务。
|
||||
如您在发布上述协议变更的有关公告后继续使用互联网查询的,视为您已接受协议的有关变更,并受其约束。
|
||||
本协议中的相关条款根据该变更而自动做相应修改,双方无须另行签订书面协议。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- 第十二部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">十二、适用法律</div>
|
||||
<div class="leading-relaxed">
|
||||
本协议条款的解释、效力及纠纷的解决,适用中华人民共和国大陆地区法律法规。
|
||||
如用户和{{ companyName }}之间发生任何争议,首先应友好协商解决,协商不成的,应将争议提交至{{ companyName }}注册地有管辖权的人民法院解决。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- 第十三部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">十三、问题咨询</div>
|
||||
<div class="leading-relaxed">
|
||||
如您对本协议及本服务有任何问题,请通过邮箱
|
||||
<text class="text-blue-500"> admin@iieeii.com </text> 或
|
||||
通过“联系客服”联系{{ companyName }}进行咨询。
|
||||
{{ companyName }}会尽最大努力解决您的问题。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- 第十四部分 -->
|
||||
<div class="mb-2 font-bold leading-relaxed">十四、附则</div>
|
||||
<div class="leading-relaxed">
|
||||
<div>
|
||||
(一)本协议的某一条款被确认无效,均不影响本协议其他条款的效力。
|
||||
</div>
|
||||
<div>
|
||||
(二)本协议未尽事宜,根据我国相关法律、法规及我们相关业务规定办理。如需制定补充协议,其法律效力同本协议。
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 leading-relaxed">
|
||||
本协议通过点击同意/勾选的方式签署,自签署之日生效。
|
||||
</div>
|
||||
<div class="text-right text-sm">
|
||||
<text>本协议于 2024 年 11 月 17 日生效。</text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
272
src/views/VantThemeTest.vue
Normal file
272
src/views/VantThemeTest.vue
Normal file
@@ -0,0 +1,272 @@
|
||||
<template>
|
||||
<!-- Vant 主题色测试页面 -->
|
||||
<div class="vant-theme-test p-4">
|
||||
<van-nav-bar title="Vant主题色测试" left-arrow @click-left="onClickLeft" />
|
||||
|
||||
<div class="mt-4 space-y-4">
|
||||
<!-- 按钮组件测试 -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-bold mb-3">按钮组件</h3>
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<van-button type="primary">主要按钮</van-button>
|
||||
<van-button type="success">成功按钮</van-button>
|
||||
<van-button type="warning">警告按钮</van-button>
|
||||
<van-button type="danger">危险按钮</van-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 开关组件测试 -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-bold mb-3">开关组件</h3>
|
||||
<div class="flex items-center gap-4">
|
||||
<van-switch v-model="switchValue" />
|
||||
<span>开关状态: {{ switchValue ? '开启' : '关闭' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 复选框组件测试 -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-bold mb-3">复选框组件</h3>
|
||||
<div class="space-y-2">
|
||||
<van-checkbox v-model="checkboxValue1">选项1</van-checkbox>
|
||||
<van-checkbox v-model="checkboxValue2">选项2</van-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 单选框组件测试 -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-bold mb-3">单选框组件</h3>
|
||||
<div class="space-y-2">
|
||||
<van-radio v-model="radioValue" name="1">选项1</van-radio>
|
||||
<van-radio v-model="radioValue" name="2">选项2</van-radio>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 滑动条组件测试 -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-bold mb-3">滑动条组件</h3>
|
||||
<van-slider v-model="sliderValue" />
|
||||
<p class="text-sm text-gray-600 mt-2">当前值: {{ sliderValue }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 进度条组件测试 -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-bold mb-3">进度条组件</h3>
|
||||
<van-progress :percentage="progressValue" />
|
||||
<p class="text-sm text-gray-600 mt-2">进度: {{ progressValue }}%</p>
|
||||
</div>
|
||||
|
||||
<!-- 评分组件测试 -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-bold mb-3">评分组件</h3>
|
||||
<van-rate v-model="rateValue" />
|
||||
<p class="text-sm text-gray-600 mt-2">评分: {{ rateValue }} 星</p>
|
||||
</div>
|
||||
|
||||
<!-- 步进器组件测试 -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-bold mb-3">步进器组件</h3>
|
||||
<van-stepper v-model="stepperValue" />
|
||||
<p class="text-sm text-gray-600 mt-2">当前值: {{ stepperValue }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 标签组件测试 -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-bold mb-3">标签组件</h3>
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<van-tag type="primary">主要标签</van-tag>
|
||||
<van-tag type="success">成功标签</van-tag>
|
||||
<van-tag type="warning">警告标签</van-tag>
|
||||
<van-tag type="danger">危险标签</van-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 徽章组件测试 -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-bold mb-3">徽章组件</h3>
|
||||
<div class="flex gap-4">
|
||||
<van-badge content="5">
|
||||
<div class="w-8 h-8 bg-gray-200 rounded"></div>
|
||||
</van-badge>
|
||||
<van-badge content="99+">
|
||||
<div class="w-8 h-8 bg-gray-200 rounded"></div>
|
||||
</van-badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 通知栏组件测试 -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-bold mb-3">通知栏组件</h3>
|
||||
<van-notice-bar left-icon="volume-o" text="这是一条通知栏消息,用于测试主题色配置效果。" />
|
||||
</div>
|
||||
|
||||
<!-- 轮播图组件测试 -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-bold mb-3">轮播图组件</h3>
|
||||
<van-swipe :autoplay="3000" indicator-color="white">
|
||||
<van-swipe-item>
|
||||
<div
|
||||
class="h-32 bg-gradient-to-r from-blue-400 to-purple-500 flex items-center justify-center text-white">
|
||||
轮播图 1
|
||||
</div>
|
||||
</van-swipe-item>
|
||||
<van-swipe-item>
|
||||
<div
|
||||
class="h-32 bg-gradient-to-r from-green-400 to-blue-500 flex items-center justify-center text-white">
|
||||
轮播图 2
|
||||
</div>
|
||||
</van-swipe-item>
|
||||
<van-swipe-item>
|
||||
<div
|
||||
class="h-32 bg-gradient-to-r from-purple-400 to-pink-500 flex items-center justify-center text-white">
|
||||
轮播图 3
|
||||
</div>
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
</div>
|
||||
|
||||
<!-- 加载组件测试 -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-bold mb-3">加载组件</h3>
|
||||
<div class="flex gap-4 items-center">
|
||||
<van-loading type="spinner" />
|
||||
<van-loading type="circular" />
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 步骤条组件测试 -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-bold mb-3">步骤条组件</h3>
|
||||
<van-steps :active="2" active-icon="success" inactive-icon="arrow">
|
||||
<van-step>步骤一</van-step>
|
||||
<van-step>步骤二</van-step>
|
||||
<van-step>步骤三</van-step>
|
||||
</van-steps>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗测试 -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-bold mb-3">弹窗组件</h3>
|
||||
<van-button type="primary" @click="showDialog = true">显示弹窗</van-button>
|
||||
<van-dialog v-model:show="showDialog" title="主题色测试" message="这是一个测试弹窗,用于验证主题色配置效果。"
|
||||
show-cancel-button />
|
||||
</div>
|
||||
|
||||
<!-- 动作面板测试 -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-bold mb-3">动作面板组件</h3>
|
||||
<van-button type="primary" @click="showActionSheet = true">显示动作面板</van-button>
|
||||
<van-action-sheet v-model:show="showActionSheet" :actions="actions" @select="onSelect" />
|
||||
</div>
|
||||
|
||||
<!-- Toast 测试 -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-bold mb-3">Toast 提示组件</h3>
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<van-button type="primary" @click="showSuccessToast">成功提示</van-button>
|
||||
<van-button type="warning" @click="showFailToast">失败提示</van-button>
|
||||
<van-button type="default" @click="showTextToast">文字提示</van-button>
|
||||
<van-button type="success" @click="showLoadingToastHandler">加载提示</van-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 颜色说明 -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-bold mb-3">主题色说明</h3>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-4 h-4 rounded" style="background-color: var(--van-theme-primary);"></div>
|
||||
<span>主色调: <span class="font-mono">var(--van-theme-primary)</span></span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600">
|
||||
以上所有组件的主色调都已配置为 <span class="font-mono">#8CC6F7</span>,通过 <span
|
||||
class="font-mono">var(--van-theme-primary)</span>
|
||||
CSS变量统一管理。包括按钮、开关、复选框、单选框等组件的激活状态。通过修改CSS变量可以轻松更改整个主题。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 测试数据
|
||||
const switchValue = ref(true)
|
||||
const checkboxValue1 = ref(true)
|
||||
const checkboxValue2 = ref(false)
|
||||
const radioValue = ref('1')
|
||||
const sliderValue = ref(50)
|
||||
const progressValue = ref(75)
|
||||
const rateValue = ref(4)
|
||||
const stepperValue = ref(1)
|
||||
const showDialog = ref(false)
|
||||
const showActionSheet = ref(false)
|
||||
|
||||
// 动作面板选项
|
||||
const actions = [
|
||||
{ name: '选项一', color: 'var(--van-theme-primary)' },
|
||||
{ name: '选项二', color: 'var(--van-theme-primary)' },
|
||||
{ name: '选项三', color: 'var(--van-theme-primary)' },
|
||||
]
|
||||
|
||||
// 选择动作面板选项
|
||||
const onSelect = (action) => {
|
||||
console.log('选择了:', action.name)
|
||||
showActionSheet.value = false
|
||||
}
|
||||
|
||||
// Toast 测试方法
|
||||
const showSuccessToast = () => {
|
||||
showToast({
|
||||
type: 'success',
|
||||
message: '操作成功!',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
|
||||
const showFailToast = () => {
|
||||
showToast({
|
||||
type: 'fail',
|
||||
message: '操作失败,请重试',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
|
||||
const showTextToast = () => {
|
||||
showToast({
|
||||
message: '这是一条普通的文字提示',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
|
||||
const showLoadingToastHandler = () => {
|
||||
const toast = showLoadingToast({
|
||||
message: "加载中...",
|
||||
forbidClick: true,
|
||||
duration: 0, // 设置为 0 表示不会自动关闭
|
||||
loadingType: "spinner",
|
||||
});
|
||||
|
||||
// 3秒后关闭加载提示
|
||||
setTimeout(() => {
|
||||
toast.close()
|
||||
showToast({
|
||||
type: 'success',
|
||||
message: '加载完成!'
|
||||
})
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
// 返回上一页
|
||||
const onClickLeft = () => {
|
||||
window.history.back()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vant-theme-test {
|
||||
min-height: 100vh;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
</style>
|
||||
509
src/views/Withdraw.vue
Normal file
509
src/views/Withdraw.vue
Normal file
@@ -0,0 +1,509 @@
|
||||
<template>
|
||||
<div class="p-4 bg-gradient-to-b from-blue-50/30 to-gray-50 min-h-screen">
|
||||
<!-- 实名认证提示 -->
|
||||
<div v-if="!agentStore.isRealName" class="mb-4">
|
||||
<div class="rounded-xl shadow-lg p-6" style="background-color: var(--van-theme-primary-light);">
|
||||
<div class="flex items-center mb-3">
|
||||
<van-icon name="warning-o" class="text-xl mr-2" style="color: var(--van-theme-primary);" />
|
||||
<h2 class="text-lg font-bold" style="color: var(--van-text-color);">
|
||||
未完成实名认证
|
||||
</h2>
|
||||
</div>
|
||||
<p class="text-sm mb-4" style="color: var(--van-text-color-2);">
|
||||
根据相关规定,提现功能需要完成实名认证后才能使用,提现金额将转入您实名认证的账户中。
|
||||
</p>
|
||||
<van-button type="primary" block class="text-white rounded-xl h-10"
|
||||
style="background-color: var(--van-theme-primary);"
|
||||
@click="openRealNameAuth">
|
||||
立即实名认证
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<!-- 提现卡片 -->
|
||||
<div class="rounded-xl shadow-lg p-6 mb-4" style="background: linear-gradient(135deg, var(--van-theme-primary-light), rgba(255,255,255,0.8));">
|
||||
<div class="flex items-center mb-6">
|
||||
<van-icon name="alipay" class="text-xl mr-2" style="color: #1677FF;" />
|
||||
<h1 class="text-xl font-bold" style="color: var(--van-text-color);">支付宝提现</h1>
|
||||
</div>
|
||||
|
||||
<!-- 支付宝账号 -->
|
||||
<div class="mb-6">
|
||||
<label class="text-sm mb-2 block" style="color: var(--van-text-color);">支付宝账号</label>
|
||||
<van-field v-model="alipayAccount" placeholder="请输入支付宝账号"
|
||||
class="flex items-center rounded-lg bg-white/90 backdrop-blur-sm shadow-sm"
|
||||
:rules="[{ required: true, message: ' ' }]">
|
||||
<template #left-icon>
|
||||
<van-icon name="phone-o" style="color: var(--van-text-color-2);" />
|
||||
</template>
|
||||
</van-field>
|
||||
<small class="text-xs mt-1 block" style="color: var(--van-text-color-2);">可填写支付宝账户绑定的手机号</small>
|
||||
</div>
|
||||
<!-- 支付宝实名姓名 -->
|
||||
<div class="mb-6">
|
||||
<label class="text-sm mb-2 block" style="color: var(--van-text-color);">实名姓名</label>
|
||||
<van-field v-model="realName" placeholder="请输入支付宝认证姓名"
|
||||
class="flex items-center rounded-lg bg-white/90 backdrop-blur-sm shadow-sm" :rules="[
|
||||
{
|
||||
required: true,
|
||||
message: ' ',
|
||||
validator: (val) =>
|
||||
/^[\u4e00-\u9fa5]{2,4}$/.test(val),
|
||||
},
|
||||
]">
|
||||
<template #left-icon>
|
||||
<van-icon name="contact-o" style="color: var(--van-text-color-2);" />
|
||||
</template>
|
||||
</van-field>
|
||||
<small class="text-xs mt-1 block" style="color: var(--van-text-color-2);">请填写支付宝账户认证的真实姓名</small>
|
||||
</div>
|
||||
<!-- 提现金额 -->
|
||||
<div class="mb-4">
|
||||
<label class="text-sm mb-2 block" style="color: var(--van-text-color);">提现金额</label>
|
||||
<van-field v-model.number="amount" type="number" placeholder="请输入提现金额"
|
||||
class="flex items-center rounded-lg bg-white/90 backdrop-blur-sm shadow-sm" :rules="[
|
||||
{ required: true, message: ' ' },
|
||||
{ validator: validateAmount, message: ' ' },
|
||||
]">
|
||||
<template #left-icon>
|
||||
<van-icon name="gold-coin-o" style="color: var(--van-text-color-2);" />
|
||||
</template>
|
||||
<template #right-icon> 元 </template>
|
||||
<template #button>
|
||||
<van-button size="small" type="primary"
|
||||
class="rounded-full px-3 shadow-sm"
|
||||
style="background-color: var(--van-theme-primary); color: white;"
|
||||
@click="fillMaxAmount">
|
||||
全部提现
|
||||
</van-button>
|
||||
</template>
|
||||
</van-field>
|
||||
</div>
|
||||
|
||||
<!-- 金额提示 -->
|
||||
<div class="text-sm mb-2" style="color: var(--van-text-color);">
|
||||
可提现金额:<span class="font-semibold" style="color: var(--van-theme-primary);">¥{{ availableAmount }}</span>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 提现规则 -->
|
||||
<div class="p-4 rounded-xl backdrop-blur-sm" style="background-color: var(--van-theme-primary-light);">
|
||||
<div class="flex items-center text-sm mb-2" style="color: var(--van-theme-primary);">
|
||||
<van-icon name="warning" class="mr-1" />提现须知
|
||||
</div>
|
||||
<ul class="text-xs space-y-1" style="color: var(--van-text-color);">
|
||||
<li>· 每日限提现1次,最低50元</li>
|
||||
<li>· 提现收取6%税收</li>
|
||||
<li>· 到账时间:24小时内</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 税收说明 -->
|
||||
<div class="p-4 rounded-xl backdrop-blur-sm mt-4" style="background-color: rgba(251, 191, 36, 0.1);">
|
||||
<div class="flex items-center text-sm mb-2" style="color: #f59e0b;">
|
||||
<van-icon name="info-o" class="mr-1" />税收说明
|
||||
</div>
|
||||
<div class="text-xs leading-relaxed" style="color: var(--van-text-color);">
|
||||
<p>根据相关规定,提现时将统一收取6%的税收,该税收用于相关税费支出。</p>
|
||||
<p class="mt-2" style="color: #f59e0b;">• 税率标准:统一按6%收取</p>
|
||||
<p style="color: #f59e0b;">• 适用范围:所有提现金额</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<van-button type="primary" block :loading="isSubmitting"
|
||||
class="text-white rounded-xl shadow-lg h-12 font-bold text-base"
|
||||
style="background: linear-gradient(135deg, var(--van-theme-primary), var(--van-theme-primary-dark));"
|
||||
@click="handleSubmit">
|
||||
立即提现
|
||||
</van-button>
|
||||
</div>
|
||||
|
||||
<!-- 税务确认弹窗 -->
|
||||
<van-popup v-model:show="showTaxConfirmPopup" round position="center"
|
||||
:style="{ width: '85%', borderRadius: '20px' }" :overlay-style="{ backgroundColor: 'rgba(0,0,0,0.4)' }">
|
||||
<div class="p-8 bg-gradient-to-b from-white to-blue-50/30 relative">
|
||||
<div class="text-center space-y-5">
|
||||
<!-- 标题 -->
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-gray-800 mb-2">提现确认</h2>
|
||||
<p class="text-sm text-gray-500">请确认以下提现信息</p>
|
||||
</div>
|
||||
|
||||
<!-- 金额详情 -->
|
||||
<div class="rounded-xl p-4 space-y-3" style="background-color: var(--van-background-color-light);">
|
||||
<div class="flex justify-between items-center">
|
||||
<span style="color: var(--van-text-color-2);">提现金额:</span>
|
||||
<span class="font-semibold" style="color: var(--van-text-color);">¥{{ amount }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span style="color: var(--van-text-color-2);">预估税费:</span>
|
||||
<span class="font-semibold text-red-500">
|
||||
-¥{{ estimatedTaxAmount.toFixed(2) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="border-t pt-2" style="border-color: var(--van-border-color);">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="font-medium" style="color: var(--van-text-color);">预估到账:</span>
|
||||
<span class="font-bold text-lg" style="color: var(--van-theme-primary);">¥{{ estimatedActualAmount.toFixed(2) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<div class="bg-blue-50 rounded-lg p-3 text-xs">
|
||||
<p class="text-blue-600 font-medium mb-2">
|
||||
税收说明
|
||||
</p>
|
||||
<div class="text-gray-600 space-y-1">
|
||||
<p>• 提现金额:¥{{ amount }}</p>
|
||||
<p>• 税率:6%</p>
|
||||
<p class="text-blue-600">• 税收计算:¥{{ amount }} × 6% = ¥{{ taxAmount.toFixed(2) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="flex space-x-3 mt-6">
|
||||
<van-button block round class="flex-1 h-11"
|
||||
style="background-color: var(--van-background-color-light); color: var(--van-text-color-2);"
|
||||
@click="showTaxConfirmPopup = false">
|
||||
取消
|
||||
</van-button>
|
||||
<van-button block round class="flex-1 h-11 font-medium shadow-sm"
|
||||
style="background-color: var(--van-theme-primary); color: white;"
|
||||
:loading="isSubmitting" @click="confirmWithdraw">
|
||||
确认提现
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
|
||||
<!-- 提现结果弹窗 -->
|
||||
<van-popup v-model:show="showStatusPopup" round position="center"
|
||||
:style="{ width: '85%', borderRadius: '20px' }" :overlay-style="{ backgroundColor: 'rgba(0,0,0,0.4)' }">
|
||||
<div class="p-8 bg-gradient-to-b from-white to-blue-50/30 relative">
|
||||
<!-- 状态内容 -->
|
||||
<div class="text-center space-y-5">
|
||||
<!-- 状态图标 -->
|
||||
<div class="relative inline-block">
|
||||
<div class="absolute inset-0 bg-gradient-to-r opacity-20 rounded-full animate-pulse blur-sm"
|
||||
:class="statusBg[status]"></div>
|
||||
<van-icon :name="statusIcon[status]" size="56" class="p-1 rounded-full border-[3px]"
|
||||
:class="statusIconClass[status]" />
|
||||
</div>
|
||||
|
||||
<!-- 状态文案 -->
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold mb-1" :class="statusTextColors[status]">
|
||||
{{ statusMessages[status] }}
|
||||
</h2>
|
||||
<template v-if="status === 2">
|
||||
<p class="text-sm" style="color: var(--van-text-color-2);">
|
||||
已向
|
||||
<span style="color: var(--van-theme-primary);">{{
|
||||
alipayAccount
|
||||
}}</span>
|
||||
转账
|
||||
</p>
|
||||
<p class="text-2xl font-bold mt-2" style="color: #10b981;">
|
||||
¥{{ amount }}
|
||||
</p>
|
||||
</template>
|
||||
<template v-if="status === 3">
|
||||
<p class="text-sm px-4" style="color: #ef4444;">
|
||||
{{ failMsg }}
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 进度条(处理中状态) -->
|
||||
<van-progress v-if="status === 1" :percentage="60" stroke-width="8"
|
||||
:color="`linear-gradient(to right, var(--van-theme-primary), var(--van-theme-primary-light))`"
|
||||
:track-color="`var(--van-theme-primary-light)`"
|
||||
class="!rounded-full" />
|
||||
|
||||
<!-- 辅助文案 -->
|
||||
<div class="text-xs space-y-1.5" style="color: var(--van-text-color-2);">
|
||||
<template v-if="status === 2">
|
||||
<p>预计24小时内到账</p>
|
||||
<p>可在支付宝账单中查看详情</p>
|
||||
</template>
|
||||
<template v-if="status === 1">
|
||||
<p>您的申请已进入处理队列</p>
|
||||
<p>5分钟后结果在提现记录种查看</p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<van-button block round size="small"
|
||||
class="mt-4 h-11 font-medium shadow-sm"
|
||||
:style="{ backgroundColor: statusButtonColor[status], color: status === 1 ? 'var(--van-theme-primary)' : 'white' }"
|
||||
@click="handlePopupAction">
|
||||
{{
|
||||
status === 1
|
||||
? "知道了"
|
||||
: status === 2
|
||||
? "完成"
|
||||
: "重新提现"
|
||||
}}
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
</div>
|
||||
<!-- 引入实名认证对话框 -->
|
||||
<RealNameAuthDialog />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { showToast } from "vant";
|
||||
import RealNameAuthDialog from "@/components/RealNameAuthDialog.vue";
|
||||
import { applyWithdrawal, getRevenueInfo } from "@/api/agent";
|
||||
|
||||
const agentStore = useAgentStore();
|
||||
const dialogStore = useDialogStore();
|
||||
// 状态管理
|
||||
const status = ref(null);
|
||||
const failMsg = ref("");
|
||||
const isSubmitting = ref(false);
|
||||
const showStatusPopup = ref(false);
|
||||
const showTaxConfirmPopup = ref(false);
|
||||
|
||||
// 税费信息(新系统由后端自动计算)
|
||||
const taxAmount = ref(0);
|
||||
const actualAmount = ref(0);
|
||||
|
||||
// 计算预估税费和实际到账金额(前端仅作展示,实际由后端计算)
|
||||
const estimatedTaxAmount = computed(() => {
|
||||
if (!amount.value) return 0;
|
||||
const withdrawAmount = Number(amount.value);
|
||||
// 预估税费:6%(实际以后端计算为准)
|
||||
return withdrawAmount * 0.06;
|
||||
});
|
||||
|
||||
const estimatedActualAmount = computed(() => {
|
||||
if (!amount.value) return 0;
|
||||
return Number(amount.value) - estimatedTaxAmount.value;
|
||||
});
|
||||
|
||||
// 样式配置
|
||||
const statusIcon = {
|
||||
1: "clock",
|
||||
2: "checked",
|
||||
3: "close",
|
||||
};
|
||||
|
||||
const statusIconClass = {
|
||||
1: "text-blue-400 border-blue-100 bg-blue-50",
|
||||
2: "text-green-500 border-green-100 bg-green-50",
|
||||
3: "text-red-500 border-red-100 bg-red-50",
|
||||
};
|
||||
|
||||
const statusBg = {
|
||||
1: "from-blue-100 to-blue-50",
|
||||
2: "from-green-100 to-green-50",
|
||||
3: "from-red-100 to-red-50",
|
||||
};
|
||||
|
||||
const statusTextColors = {
|
||||
1: "text-blue-600",
|
||||
2: "text-green-600",
|
||||
3: "text-red-600",
|
||||
};
|
||||
|
||||
const statusButtonColor = {
|
||||
1: "var(--van-theme-primary-light)",
|
||||
2: "#10b981",
|
||||
3: "#ef4444",
|
||||
};
|
||||
const statusMessages = {
|
||||
1: "提现申请处理中,请稍后再查询结果",
|
||||
2: "提现成功",
|
||||
3: "提现失败",
|
||||
};
|
||||
|
||||
// 表单数据
|
||||
const alipayAccount = ref("");
|
||||
const amount = ref(0);
|
||||
const availableAmount = ref(null);
|
||||
const realName = ref("");
|
||||
|
||||
const getData = async () => {
|
||||
const { data, error } = await getRevenueInfo();
|
||||
|
||||
if (data.value?.code === 200 && !error.value) {
|
||||
availableAmount.value = data.value.data.balance;
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
getData();
|
||||
});
|
||||
|
||||
// 表单验证
|
||||
const validateAmount = (val) => {
|
||||
const num = Number(val);
|
||||
return num >= 50 && num <= availableAmount.value;
|
||||
};
|
||||
|
||||
const validateForm = () => {
|
||||
if (!realName.value.trim()) {
|
||||
showToast("请输入账户实名姓名");
|
||||
return false;
|
||||
}
|
||||
if (!/^[\u4e00-\u9fa5]{2,4}$/.test(realName.value)) {
|
||||
showToast("请输入2-4位中文姓名");
|
||||
return false;
|
||||
}
|
||||
if (!alipayAccount.value.trim()) {
|
||||
showToast("请输入支付宝账号");
|
||||
return false;
|
||||
}
|
||||
|
||||
const amountNum = Number(amount.value);
|
||||
if (!amount.value || isNaN(amountNum)) {
|
||||
showToast("请输入有效金额");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (amountNum < 50) {
|
||||
showToast("提现金额不能低于50元");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (amountNum > availableAmount.value) {
|
||||
showToast("超过可提现金额");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// 打开实名认证对话框
|
||||
const openRealNameAuth = () => {
|
||||
dialogStore.openRealNameAuth();
|
||||
};
|
||||
|
||||
// 注意:新系统中税费由后端自动计算,无需前端调用税务豁免接口
|
||||
|
||||
const handleSubmit = async () => {
|
||||
// 检查实名认证状态
|
||||
if (!agentStore.isRealName) {
|
||||
showToast("请先完成实名认证");
|
||||
openRealNameAuth();
|
||||
return;
|
||||
}
|
||||
|
||||
// 先进行表单验证
|
||||
if (!validateForm()) return;
|
||||
|
||||
// 显示税务确认弹窗
|
||||
showTaxConfirmPopup.value = true;
|
||||
};
|
||||
|
||||
// 确认提现
|
||||
const confirmWithdraw = async () => {
|
||||
isSubmitting.value = true;
|
||||
try {
|
||||
const { applyWithdrawal } = await import('@/api/agent');
|
||||
const { data, error } = await applyWithdrawal({
|
||||
payee_account: alipayAccount.value,
|
||||
amount: Number(amount.value),
|
||||
payee_name: realName.value,
|
||||
});
|
||||
if (data.value?.code === 200 && !error.value) {
|
||||
status.value = 1; // 新系统:申请后状态为1(待审核)
|
||||
showTaxConfirmPopup.value = false;
|
||||
showStatusPopup.value = true;
|
||||
} else {
|
||||
showToast(data.value?.msg || '提现申请失败,请重试');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('提现申请失败:', err);
|
||||
showToast('提现申请失败,请重试');
|
||||
} finally {
|
||||
isSubmitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 弹窗操作
|
||||
const handlePopupAction = () => {
|
||||
if (status.value === 3) {
|
||||
showStatusPopup.value = false;
|
||||
resetForm();
|
||||
} else {
|
||||
showStatusPopup.value = false;
|
||||
if (status.value === 2) resetPage();
|
||||
}
|
||||
};
|
||||
// 填充最大金额
|
||||
const fillMaxAmount = () => {
|
||||
amount.value = availableAmount.value;
|
||||
};
|
||||
|
||||
// 重置页面
|
||||
const resetForm = () => {
|
||||
status.value = null;
|
||||
alipayAccount.value = "";
|
||||
amount.value = "";
|
||||
realName.value = "";
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 自定义表单样式 */
|
||||
.van-field__control {
|
||||
@apply py-1 px-4 text-gray-800;
|
||||
}
|
||||
|
||||
.van-field__error-message {
|
||||
@apply mt-1;
|
||||
}
|
||||
|
||||
.van-button--disabled {
|
||||
@apply opacity-60 cursor-not-allowed;
|
||||
}
|
||||
|
||||
/* 弹窗入场动画 */
|
||||
.van-popup {
|
||||
transition: transform 0.4s cubic-bezier(0.22, 0.61, 0.36, 1),
|
||||
opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.van-popup-enter-active,
|
||||
.van-popup-leave-active {
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.van-popup-enter-from,
|
||||
.van-popup-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.van-popup-enter-active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.van-popup-enter-to {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
/* 状态图标动画 */
|
||||
.animate-pulse {
|
||||
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* 实名认证提示样式 */
|
||||
.pointer-events-none {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
219
src/views/WithdrawDetails.vue
Normal file
219
src/views/WithdrawDetails.vue
Normal file
@@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
<!-- 提现记录列表 -->
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
|
||||
<div v-for="(item, index) in data.list" :key="index" class="mx-4 my-2 bg-white rounded-lg p-4 shadow-sm">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span class="text-gray-500 text-sm">{{
|
||||
item.create_time || "-"
|
||||
}}</span>
|
||||
<span class="font-bold" :class="getAmountColor(item.status)">{{ item.amount.toFixed(2) }}元</span>
|
||||
</div>
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium"
|
||||
:class="getStatusStyle(item.status)">
|
||||
<span class="w-2 h-2 rounded-full mr-1" :class="getDotColor(item.status)"></span>
|
||||
{{ statusToChinese(item.status) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 space-y-1">
|
||||
<div v-if="item.payee_account">
|
||||
收款账户:{{ maskName(item.payee_account) }}
|
||||
</div>
|
||||
<div v-if="item.payee_name">
|
||||
收款人:{{ item.payee_name }}
|
||||
</div>
|
||||
<div v-if="item.tax_amount > 0">
|
||||
税费:-¥{{ item.tax_amount.toFixed(2) }}
|
||||
</div>
|
||||
<div v-if="item.actual_amount > 0" class="text-green-600 font-medium">
|
||||
实际到账:¥{{ item.actual_amount.toFixed(2) }}
|
||||
</div>
|
||||
<div v-if="item.withdrawal_no">
|
||||
提现单号:{{ item.withdrawal_no }}
|
||||
</div>
|
||||
<div v-if="item.remark">备注:{{ item.remark }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getWithdrawalList } from '@/api/agent'
|
||||
|
||||
// 新系统状态映射配置:1=待审核,2=审核通过,3=审核拒绝,4=提现中,5=提现成功,6=提现失败
|
||||
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-blue-100",
|
||||
text: "text-blue-800",
|
||||
dot: "bg-blue-500",
|
||||
amount: "text-blue-500",
|
||||
},
|
||||
},
|
||||
3: {
|
||||
chinese: "审核拒绝",
|
||||
color: {
|
||||
bg: "bg-red-100",
|
||||
text: "text-red-800",
|
||||
dot: "bg-red-500",
|
||||
amount: "text-red-500",
|
||||
},
|
||||
},
|
||||
4: {
|
||||
chinese: "提现中",
|
||||
color: {
|
||||
bg: "bg-purple-100",
|
||||
text: "text-purple-800",
|
||||
dot: "bg-purple-500",
|
||||
amount: "text-purple-500",
|
||||
},
|
||||
},
|
||||
5: {
|
||||
chinese: "提现成功",
|
||||
color: {
|
||||
bg: "bg-green-100",
|
||||
text: "text-green-800",
|
||||
dot: "bg-green-500",
|
||||
amount: "text-green-500",
|
||||
},
|
||||
},
|
||||
6: {
|
||||
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 data = ref({
|
||||
total: 0,
|
||||
list: [],
|
||||
});
|
||||
const loading = ref(false);
|
||||
const finished = ref(false);
|
||||
|
||||
// 账户脱敏处理
|
||||
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 onLoad = async () => {
|
||||
if (!finished.value) {
|
||||
await getData();
|
||||
}
|
||||
};
|
||||
|
||||
// 获取数据
|
||||
const getData = async () => {
|
||||
if (loading.value || finished.value) return
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
const { data: res, error } = await getWithdrawalList({
|
||||
page: page.value,
|
||||
page_size: pageSize.value
|
||||
});
|
||||
|
||||
if (res.value?.code === 200 && !error.value) {
|
||||
// 首次加载
|
||||
if (page.value === 1) {
|
||||
data.value = res.value.data;
|
||||
} else {
|
||||
// 分页加载
|
||||
data.value.list.push(...res.value.data.list);
|
||||
}
|
||||
|
||||
// 更新分页状态
|
||||
page.value++;
|
||||
|
||||
// 判断是否加载完成
|
||||
if (
|
||||
data.value.list.length >= res.value.data.total ||
|
||||
res.value.data.list.length < pageSize.value
|
||||
) {
|
||||
finished.value = true;
|
||||
}
|
||||
} else {
|
||||
// 接口返回错误或请求失败,停止翻页
|
||||
finished.value = true;
|
||||
console.error('获取提现列表失败:', res.value?.msg || error.value || '未知错误');
|
||||
}
|
||||
} catch (err) {
|
||||
// 捕获异常,停止翻页
|
||||
finished.value = true;
|
||||
console.error('获取提现列表失败:', err);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化加载
|
||||
onMounted(async () => {
|
||||
// 重置分页状态
|
||||
page.value = 1;
|
||||
finished.value = false;
|
||||
await getData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 保持原有样式不变 */
|
||||
.list-enter-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.list-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
:deep(.van-list__finished-text) {
|
||||
@apply py-4 text-gray-400 text-sm;
|
||||
}
|
||||
|
||||
:deep(.van-list__loading) {
|
||||
@apply py-4;
|
||||
}
|
||||
</style>
|
||||
310
src/views/index.vue
Normal file
310
src/views/index.vue
Normal file
@@ -0,0 +1,310 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useAgentStore } from "@/stores/agentStore";
|
||||
import { useUserStore } from "@/stores/userStore";
|
||||
import { useAppStore } from "@/stores/appStore";
|
||||
import { showToast } from "vant";
|
||||
import SectionTitle from "@/components/SectionTitle.vue";
|
||||
import personalDataIcon from "@/assets/images/index/personal_data_bg.png";
|
||||
import companyIcon from "@/assets/images/index/company_bg.png";
|
||||
import marriageRiskIcon from "@/assets/images/index/marriage_risk_bg.png";
|
||||
|
||||
const router = useRouter();
|
||||
const agentStore = useAgentStore();
|
||||
const userStore = useUserStore();
|
||||
const appStore = useAppStore();
|
||||
const { isAgent } = storeToRefs(agentStore);
|
||||
const { isLoggedIn, mobile } = storeToRefs(userStore);
|
||||
|
||||
function toInquire(name) {
|
||||
// 普通用户查询,需要登录且绑定手机号
|
||||
router.push(`/inquire/${name}`);
|
||||
}
|
||||
|
||||
function toInvitation() {
|
||||
// 检查是否是代理
|
||||
if (!isAgent.value) {
|
||||
if (!isLoggedIn.value) {
|
||||
showToast({ message: "请先登录" });
|
||||
router.push({ path: "/login", query: { redirect: router.currentRoute.value.fullPath } });
|
||||
} else {
|
||||
showToast({ message: "请先注册成为代理" });
|
||||
router.push({ path: "/register", query: { mobile: mobile.value } });
|
||||
}
|
||||
return;
|
||||
}
|
||||
router.push({ name: "invitation" });
|
||||
}
|
||||
|
||||
const toPromote = () => {
|
||||
// 检查是否是代理
|
||||
if (!isAgent.value) {
|
||||
if (!isLoggedIn.value) {
|
||||
showToast({ message: "请先登录" });
|
||||
router.push({ path: "/login", query: { redirect: router.currentRoute.value.fullPath } });
|
||||
} else {
|
||||
showToast({ message: "请先注册成为代理" });
|
||||
router.push({ path: "/register", query: { mobile: mobile.value } });
|
||||
}
|
||||
return;
|
||||
}
|
||||
router.push({ name: "promote" });
|
||||
};
|
||||
|
||||
const toHelp = () => {
|
||||
router.push("/help");
|
||||
};
|
||||
|
||||
const toService = () => {
|
||||
router.push({ name: "service" });
|
||||
};
|
||||
|
||||
function toHistory() {
|
||||
router.push(`/historyQuery`);
|
||||
}
|
||||
|
||||
const toAgent = () => {
|
||||
// 检查是否是代理
|
||||
if (!isAgent.value) {
|
||||
if (!isLoggedIn.value) {
|
||||
showToast({ message: "请先登录" });
|
||||
router.push({ path: "/login", query: { redirect: router.currentRoute.value.fullPath } });
|
||||
} else {
|
||||
showToast({ message: "请先注册成为代理" });
|
||||
router.push({ path: "/register", query: { mobile: mobile.value } });
|
||||
}
|
||||
return;
|
||||
}
|
||||
router.push({ path: "/promote" });
|
||||
};
|
||||
|
||||
const toAgentSystemGuide = () => {
|
||||
router.push({ name: "agentSystemGuide" });
|
||||
};
|
||||
|
||||
const toRegister = () => {
|
||||
// 检查是否已登录
|
||||
if (!isLoggedIn.value) {
|
||||
showToast({ message: "请先登录" });
|
||||
router.push({ path: "/login", query: { redirect: router.currentRoute.value.fullPath } });
|
||||
return;
|
||||
}
|
||||
// 如果已登录,直接跳转到注册页
|
||||
router.push({ path: "/register", query: { mobile: mobile.value } });
|
||||
};
|
||||
|
||||
const services = ref([
|
||||
{
|
||||
title: "婚恋风险",
|
||||
name: "marriage",
|
||||
subtitle: "深度了解,坦诚相爱",
|
||||
bg: marriageRiskIcon,
|
||||
goColor: "#ff99cc",
|
||||
},
|
||||
{
|
||||
title: "个人大数据",
|
||||
name: "riskassessment",
|
||||
subtitle: "数据洞察,规避风险",
|
||||
bg: personalDataIcon,
|
||||
goColor: "#6699ff",
|
||||
},{
|
||||
title: "老板企业风险",
|
||||
name: "companyinfo",
|
||||
subtitle: "风险监控,稳健经营",
|
||||
bg: companyIcon,
|
||||
goColor: "#ffaa66",
|
||||
},
|
||||
|
||||
]);
|
||||
|
||||
// const riskServices = ref([
|
||||
|
||||
// ]);
|
||||
|
||||
const noticeText = ref([]);
|
||||
|
||||
// 报告类型列表
|
||||
const reportTypes = [
|
||||
"婚恋风险",
|
||||
"个人大数据",
|
||||
"老板企业风险",
|
||||
];
|
||||
|
||||
// 生成随机手机号(11位,以1开头,中间部分脱敏)
|
||||
const generateRandomPhone = () => {
|
||||
// 第二位数字通常是3、4、5、6、7、8、9
|
||||
const secondDigit = [3, 4, 5, 6, 7, 8, 9][Math.floor(Math.random() * 7)];
|
||||
// 第三位随机数字
|
||||
const thirdDigit = Math.floor(Math.random() * 10);
|
||||
// 后4位随机数字
|
||||
const lastFour = Math.floor(Math.random() * 10000).toString().padStart(4, '0');
|
||||
// 格式:前3位 + **** + 后4位,例如:138****5678
|
||||
return `1${secondDigit}${thirdDigit}****${lastFour}`;
|
||||
};
|
||||
|
||||
// 生成随机查询消息
|
||||
const generateNoticeMessages = () => {
|
||||
const messages = [];
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const phone = generateRandomPhone();
|
||||
const reportType = reportTypes[Math.floor(Math.random() * reportTypes.length)];
|
||||
// 按顺序添加时间,从1分钟前开始递增
|
||||
const minutesAgo = i + 1;
|
||||
messages.push(`${minutesAgo}分钟前 ${phone} 查询了${reportType}报告`);
|
||||
}
|
||||
return messages;
|
||||
};
|
||||
|
||||
// 页面加载时生成消息
|
||||
onMounted(() => {
|
||||
noticeText.value = generateNoticeMessages();
|
||||
});
|
||||
|
||||
const toCooperation = () => {
|
||||
window.location.href = "https://www.tianyuandata.com";
|
||||
};
|
||||
const toBigData = () => {
|
||||
window.location.href = "https://www.tybigdata.com/";
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="box-border min-h-screen from-blue-100 to-white bg-gradient-to-b">
|
||||
<div class="relative">
|
||||
<img class="h-full w-full" src="@/assets/images/index/banner.png" />
|
||||
</div>
|
||||
<div class="px-4 mt-2">
|
||||
<SectionTitle title="查询服务" class="" />
|
||||
<div class="grid grid-cols-4 gap-4 mt-4">
|
||||
<div class="flex flex-col items-center justify-center cursor-pointer" @click="toRegister">
|
||||
<div class="w-24 h-16 rounded-full bg-gradient-to-br from-blue-400 to-blue-600 flex items-center justify-center shadow-lg mb-2">
|
||||
<span class="text-white text-xs font-semibold">成为代理</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<div class="w-24 h-16 rounded-full bg-gradient-to-br from-purple-400 to-purple-600 flex items-center justify-center shadow-lg mb-2">
|
||||
<span class="text-white text-xs font-semibold">推广报告</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<div class="w-24 h-16 rounded-full bg-gradient-to-br from-green-400 to-green-600 flex items-center justify-center shadow-lg mb-2">
|
||||
<span class="text-white text-xs font-semibold">资产</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<div class="w-24 h-16 rounded-full bg-gradient-to-br from-orange-400 to-orange-600 flex items-center justify-center shadow-lg mb-2">
|
||||
<span class="text-white text-xs font-semibold">在线推广</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative p-4 pt-0">
|
||||
<div class="grid grid-cols-2 gap-4 my-4" style="grid-template-rows: repeat(2, 1fr);">
|
||||
<template v-for="(service, index) in services" :key="index">
|
||||
<div class="relative flex flex-col px-4 py-2 rounded-xl min-h-24 shadow-lg"
|
||||
:class="index === 0 ? 'row-span-2' : ''"
|
||||
:style="`background: url(${service.bg}) no-repeat; background-size: cover; background-position: center;`"
|
||||
@click=" toInquire(service.name)">
|
||||
<div class="min-h-18 flex flex-col items-start px-1">
|
||||
<div class="mt-1 max-w-max text-left text-gray-600 font-bold">
|
||||
{{ service.title }}
|
||||
</div>
|
||||
<!-- <div class="max-w-max text-left text-xs text-gray-600">
|
||||
<div>{{ service.subtitle }}</div>
|
||||
</div> -->
|
||||
<div class="mt-2 rounded-lg px-1 text-sm text-white shadow-xl w-max flex items-center"
|
||||
:style="`background-color: ${service.goColor}`">
|
||||
GO
|
||||
<img src="@/assets/images/index/go_icon.png" alt="右箭头"
|
||||
class="ml-0.5 h-4 w-4 inline-block align-middle" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-4 my-4">
|
||||
<template v-for="(service, index) in riskServices" :key="index">
|
||||
<div class="relative flex flex-col px-3 py-2 rounded-xl min-h-24 shadow-lg"
|
||||
:style="`background: url(${service.bg}) no-repeat; background-size: cover; background-position: center;`"
|
||||
@click="toInquire(service.name)">
|
||||
<div class="min-h-18 flex flex-col items-start px-1">
|
||||
<div class="mt-1 max-w-max text-left text-gray-600 font-bold">
|
||||
{{ service.title }}
|
||||
</div>
|
||||
<!-- <div class="max-w-max text-left text-xs text-gray-600">
|
||||
<div>{{ service.subtitle }}</div>
|
||||
</div> -->
|
||||
<div class="mt-1 rounded-lg px-1 text-xs text-white shadow-xl w-max flex items-center"
|
||||
:style="`background-color: ${service.goColor}`">
|
||||
GO
|
||||
<img src="@/assets/images/index/go_icon.png" alt="右箭头"
|
||||
class="ml-0.5 h-3 w-3 inline-block align-middle" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- <div class="mt-8 rounded-2xl overflow-hidden shadow-xl" @click="toAgent">
|
||||
<img src="@/assets/images/index/index_b_b.png" class="w-full h-full" mode="widthFix" />
|
||||
</div> -->
|
||||
|
||||
<!-- 代理系统指南入口 -->
|
||||
<!-- <div class="mt-4 box-border h-14 w-full flex items-center rounded-lg shadow-lg text-gray-700 px-4 "
|
||||
@click="toAgentSystemGuide">
|
||||
<div class="mr-4 h-full flex items-center justify-center">
|
||||
<van-icon name="description" size="24" style="color: #8b5cf6;" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-gray-800 font-semibold">代理系统全面指南</div>
|
||||
<div class="text-xs text-gray-500">了解代理政策、等级特权与收益计算</div>
|
||||
</div>
|
||||
|
||||
<img src="@/assets/images/index/right.png" alt="右箭头" class="h-6 w-6" />
|
||||
</div> -->
|
||||
<div class="mt-4 box-border h-14 w-full flex items-center rounded-lg shadow-lg bg-white text-gray-700 px-4"
|
||||
@click="toHistory">
|
||||
<div class="mr-4 h-full flex items-center justify-center">
|
||||
<img class="w-10 h-10" src="@/assets/images/icon_bg.svg" mode="widthFix" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-gray-800">我的历史查询记录</div>
|
||||
<div class="text-xs text-gray-500">查询记录有效期为{{ appStore.queryRetentionDays }}天</div>
|
||||
</div>
|
||||
<img src="@/assets/images/index/right.png" alt="右箭头" class="h-6 w-6" />
|
||||
</div>
|
||||
|
||||
<SectionTitle title="商务合作" class="mt-4" />
|
||||
<div class="mt-2 rounded-xl overflow-hidden bg-white shadow-xl" @click="toCooperation">
|
||||
<img src="@/assets/images/index_a_banner.png" class="w-full h-full" alt="大数据服务横幅" mode="widthFix" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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);
|
||||
}
|
||||
|
||||
.notice-swipe {
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
:deep(.van-notice-bar) {
|
||||
height: 28px !important;
|
||||
line-height: 28px !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<route type="home" lang="json">{
|
||||
"layout": "home"
|
||||
}</route>
|
||||
Reference in New Issue
Block a user