first commit

This commit is contained in:
2026-04-01 15:35:40 +08:00
commit 562a9b5172
479 changed files with 68351 additions and 0 deletions

288
src/views/Agent.vue Normal file
View File

@@ -0,0 +1,288 @@
<template>
<div class="p-4 bg-gradient-to-b from-gray-50/50 to-gray-100/30 min-h-screen">
<!-- 资产卡片 -->
<div class="rounded-xl shadow-lg mb-4 bg-gradient-to-r from-primary-50/70 to-primary-100/50 p-6">
<div class="flex justify-between items-center mb-3">
<div class="flex items-center">
<van-icon name="balance-pay" class="text-xl mr-2" style="color: var(--van-theme-primary);" />
<span class="text-lg font-bold" style="color: var(--van-text-color);">余额</span>
</div>
<span class="text-3xl font-bold" style="color: var(--van-theme-primary);">¥ {{ (data?.balance ||
0).toFixed(2) }}</span>
</div>
<div class="text-sm mb-2" style="color: var(--van-text-color-2);">
累计收益¥ {{ (data?.total_earnings || 0).toFixed(2) }}
</div>
<div class="text-sm mb-6 flex items-center" style="color: var(--van-text-color-2);">
待结账金额¥ {{ (data?.frozen_balance || 0).toFixed(2) }}
<van-popover v-model:show="showTooltip" placement="bottom-start" :offset="10">
<template #reference>
<van-icon name="question-o" class="ml-2 cursor-help" @mouseenter="showTooltip = true" @mouseleave="showTooltip = false" />
</template>
<div class="p-2 text-sm" style="max-width: 200px;">
待结账金额将在订单创建24小时后自动结账
</div>
</van-popover>
</div>
<div class="grid grid-cols-2 gap-3">
<button @click="toWithdraw"
class="text-white rounded-full py-2 px-4 shadow-md flex items-center justify-center"
style="background: linear-gradient(135deg, var(--van-theme-primary), var(--van-theme-primary-dark));">
<van-icon name="gold-coin" class="mr-1" />
提现
</button>
<button @click="toWithdrawDetails"
class="bg-white/90 border rounded-full py-2 px-4 shadow-sm flex items-center justify-center"
style="color: var(--van-text-color-2); border-color: var(--van-border-color);">
<van-icon name="notes" class="mr-1" />
提现记录
</button>
</div>
</div>
<!-- 直推报告收益 -->
<div class="rounded-xl shadow-lg mb-4 bg-gradient-to-r from-warning-50/60 to-warning-100/50 p-6">
<div class="flex justify-between items-center mb-4">
<div class="flex items-center">
<van-icon name="balance-list" class="text-xl mr-2" style="color: var(--color-warning);" />
<span class="text-lg font-bold" style="color: var(--van-text-color);">直推报告收益</span>
</div>
<div class="text-right">
<div class="text-2xl font-bold" style="color: var(--color-warning);">
¥
{{
(data?.direct_push?.total_commission || 0).toFixed(
2
)
}}
</div>
<div class="text-sm mt-1" style="color: var(--van-text-color-2);">
有效报告 {{ data?.direct_push?.total_report || 0 }}
</div>
</div>
</div>
<!-- 日期选择 -->
<div class="grid grid-cols-3 gap-2 mb-6">
<button v-for="item in promoteDateOptions" :key="item.value" @click="selectedPromoteDate = item.value"
class="rounded-full transition-all py-1 px-4 text-sm" :class="[
selectedPromoteDate === item.value
? 'text-white shadow-md'
: 'bg-white/90 border',
]" :style="selectedPromoteDate === item.value
? 'background-color: var(--color-warning);'
: 'color: var(--van-text-color-2); border-color: var(--van-border-color);'">
{{ item.label }}
</button>
</div>
<div class="grid grid-cols-2 gap-4 mb-6">
<div class="p-3 rounded-lg backdrop-blur-sm" style="background-color: rgba(245, 158, 11, 0.08);">
<div class="flex items-center text-sm" style="color: var(--van-text-color-2);">
<van-icon name="gold-coin" class="mr-1" />本日收益
</div>
<div class="text-xl font-bold mt-1" style="color: var(--color-warning);">
¥
{{
currentPromoteData.commission?.toFixed(2) || "0.00"
}}
</div>
</div>
<div class="p-3 rounded-lg backdrop-blur-sm" style="background-color: rgba(245, 158, 11, 0.08);">
<div class="flex items-center text-sm" style="color: var(--van-text-color-2);">
<van-icon name="description" class="mr-1" />有效报告
</div>
<div class="text-xl font-bold mt-1" style="color: var(--color-warning);">
{{ currentPromoteData.report || 0 }}
</div>
</div>
</div>
<div class="flex items-center justify-between text-sm font-semibold cursor-pointer pt-4"
style="color: var(--color-warning);" @click="goToPromoteDetail">
<span>查看收益明细</span>
<span class="text-lg"></span>
</div>
</div>
<!-- 活跃下级奖励 -->
<div class="rounded-xl shadow-lg bg-gradient-to-r from-success-50/50 to-success-100/40 p-6">
<div class="flex justify-between items-center mb-4">
<div class="flex items-center">
<van-icon name="friends" class="text-xl mr-2" style="color: var(--color-success);" />
<span class="text-lg font-bold" style="color: var(--van-text-color);">活跃下级奖励</span>
</div>
<div class="text-right">
<div class="text-2xl font-bold" style="color: var(--color-success);">
¥
{{
(data?.active_reward?.total_reward || 0).toFixed(2)
}}
</div>
<div class="text-sm mt-1" style="color: var(--van-text-color-2);">活跃下级 0 </div>
</div>
</div>
<!-- 日期选择 -->
<div class="grid grid-cols-3 gap-2 mb-6">
<button v-for="item in activeDateOptions" :key="item.value" @click="selectedActiveDate = item.value"
class="rounded-full transition-all py-1 px-4 text-sm" :class="[
selectedActiveDate === item.value
? 'text-white shadow-md'
: 'bg-white/90 border',
]" :style="selectedActiveDate === item.value
? 'background-color: var(--color-success);'
: 'color: var(--van-text-color-2); border-color: var(--van-border-color);'">
{{ item.label }}
</button>
</div>
<div class="grid grid-cols-2 gap-2 mb-6">
<div class="p-3 rounded-lg backdrop-blur-sm" style="background-color: rgba(16, 185, 129, 0.08);">
<div class="flex items-center text-sm" style="color: var(--van-text-color-2);">
<van-icon name="medal" class="mr-1" />本日奖励
</div>
<div class="text-xl font-bold mt-1" style="color: var(--color-success);">
¥
{{ (currentActiveData.active_reward || 0).toFixed(2) }}
</div>
</div>
<div class="p-3 rounded-lg backdrop-blur-sm" style="background-color: rgba(16, 185, 129, 0.08);">
<div class="flex items-center text-sm" style="color: var(--van-text-color-2);">
<van-icon name="discount" class="mr-1" />下级推广奖励
</div>
<div class="text-xl font-bold mt-1" style="color: var(--color-success);">
¥
{{
(currentActiveData.sub_promote_reward || 0).toFixed(
2
)
}}
</div>
</div>
<div class="p-3 rounded-lg backdrop-blur-sm" style="background-color: rgba(16, 185, 129, 0.08);">
<div class="flex items-center text-sm" style="color: var(--van-text-color-2);">
<van-icon name="fire" class="mr-1" />下级转化奖励
</div>
<div class="text-xl font-bold mt-1" style="color: var(--color-success);">
¥
{{
(
currentActiveData.sub_withdraw_reward || 0
).toFixed(2)
}}
</div>
</div>
</div>
<div class="flex items-center justify-between text-sm font-semibold cursor-pointer pt-4"
style="color: var(--color-success);" @click="goToActiveDetail">
<span>查看奖励明细</span>
<span class="text-lg"></span>
</div>
<!-- 添加查看下级按钮 -->
<div class="mt-4">
<button @click="toSubordinateList"
class="w-full text-white rounded-full py-2 px-4 shadow-md flex items-center justify-center bg-success"
style="background: linear-gradient(135deg, var(--color-success), var(--color-success-600));">
<van-icon name="friends" class="mr-1" />
查看我的下级
</button>
</div>
</div>
</div>
</template>
<script setup>
import { storeToRefs } from "pinia";
import { ref, computed } from "vue";
import { useRouter } from "vue-router";
const agentStore = useAgentStore();
const { isAgent } = storeToRefs(agentStore);
const router = useRouter();
const data = ref(null);
const showTooltip = ref(false);
// 日期选项映射
const dateRangeMap = {
today: "today",
week: "last7d",
month: "last30d",
};
// 直推报告数据
const promoteDateOptions = [
{ label: "今日", value: "today" },
{ label: "近7天", value: "week" },
{ label: "近1月", value: "month" },
];
const selectedPromoteDate = ref("today");
// 活跃下级数据
const activeDateOptions = [
{ label: "今日", value: "today" },
{ label: "近7天", value: "week" },
{ label: "近1月", value: "month" },
];
const selectedActiveDate = ref("today");
// 计算当前直推数据
const currentPromoteData = computed(() => {
const range = dateRangeMap[selectedPromoteDate.value];
return data.value?.direct_push?.[range] || { commission: 0, report: 0 };
});
// 计算当前活跃数据
const currentActiveData = computed(() => {
const range = dateRangeMap[selectedActiveDate.value];
return (
data.value?.active_reward?.[range] || {
active_reward: 0,
sub_promote_reward: 0,
sub_upgrade_reward: 0,
sub_withdraw_reward: 0,
}
);
});
const getData = async () => {
const { data: res, error } = await useApiFetch("/agent/revenue")
.get()
.json();
if (res.value?.code === 200 && !error.value) {
data.value = res.value.data;
}
};
onMounted(() => {
if (isAgent.value) {
getData();
}
});
// 路由跳转
const goToPromoteDetail = () => router.push({ name: "promoteDetails" });
const goToActiveDetail = () => router.push({ name: "rewardsDetails" });
const toWithdraw = () => router.push({ name: "withdraw" });
const toWithdrawDetails = () => router.push({ name: "withdrawDetails" });
// 添加跳转到下级列表的方法
const toSubordinateList = () => {
router.push("/agent/subordinateList");
};
</script>
<style>
/* 添加按钮悬停效果 */
button {
transition: all 0.2s ease;
}
button:hover {
transform: translateY(-1px);
}
</style>

View File

@@ -0,0 +1,811 @@
<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">
{{ COMPANY_NAME }}为加强对全国代理的统一管理规范各代理行为确保"{{ APP_NAME }}"的顺利推广特依据如下原则制定代理管理制度望各级代理认真贯彻严格遵守
</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">
{{ COMPANY_NAME }}配合各代理的工作对于代理在推广工作中遇到的问题用心配合解决
</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">
{{ COMPANY_NAME }}的目标是与代理共赢共同发展
</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">
第二条
本制度规定{{ COMPANY_NAME }}代理(以下称代理)权限运作及业务处理等相关事项旨在使{{ COMPANY_NAME }}与各代理之间持续良好合作关系促进双方共同发展;
</p>
<p class="indent-8 mb-2">
第三条
代理经{{ COMPANY_NAME }}授权并自代理协议书生效之日起应严格依照代理协议及本制度的规定履行义务享受权利
</p>
<p class="indent-8 mb-2">
第四条
{{ COMPANY_NAME }}确定的代理应遵循{{ COMPANY_NAME }}的规定从事代理活动不得做出损害{{ COMPANY_NAME }}利益和形象的行为;
</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全面赞同{{ APP_NAME }}的各项制度并能积极参加{{ APP_NAME }}为各代理所举办的各种活动;
</p>
<p class="indent-8 mb-2">企业类</p>
<p class="indent-8 mb-2">
1具有独立法人资格并能提供有效营业执照组织代码证等相关文件复印件经审查合格签定代理协议后即成为{{ COMPANY_NAME }}认证代理
</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全面赞同{{ APP_NAME }}的各项制度并能积极参加{{ APP_NAME }}为各代理所举办的各种活动;
</p>
<p class="indent-8 mb-4"><strong>代理权利和义务</strong></p>
<p class="indent-8 mb-2">
在成为{{ COMPANY_NAME }}的认证代理后可享有如下权利并承担相应的义务:
</p>
<p class="indent-8 mb-2">
1使用{{ APP_NAME }}开展广告宣传市场推广活动;
</p>
<p class="indent-8 mb-2">
2维护{{ COMPANY_NAME }}及其产品的良好形象;
</p>
<p class="indent-8 mb-2">
3开拓下级业务推广并负责对其定期进行业务培训;
</p>
<p class="indent-8 mb-2">4推广过程中做好售前售中售后工作</p>
<p class="indent-8 mb-2">
5如用户需要开具发票代理则需向用户开具咨询费发票如代理未开具发票{{ APP_NAME }}有义务配合税务机关采取相关措施
</p>
<p class="indent-8 mb-2">
6代理业务推广过程中未经{{ COMPANY_NAME }}授权不得使用"{{ APP_NAME }}官方"词汇用于广告宣传
</p>
<p class="font-bold mb-2">推广管理</p>
<p class="indent-8 mb-2">
1{{ APP_NAME }}负责建立与代理之间的沟通与联系渠道不定期地向代理提供宣传资料信息政策以及推广方案与管理制度等方面的支持
</p>
<p class="indent-8 mb-2">
2{{ COMPANY_NAME }}充分尊重代理代理推广权但有下列状况之一时{{ COMPANY_NAME }}将保留或者取消该代理的权利:
</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其他严重损害{{ COMPANY_NAME }}形象与产品形象的行为发生时;
</p>
<p class="indent-8 mb-2">e违反国家法律法规时;</p>
<p class="indent-8 mb-2">
3当代理名下发生投诉时代理需配合相关的协调否则{{ COMPANY_NAME }}有权无条件取消其代理资格终止其代理协议
</p>
<p class="indent-8 mb-2">
4代理应合规宣传{{ COMPANY_NAME }}产品形象
</p>
<p class="indent-8 mb-2">
5市场运作过程中各代理在接到市场投诉时应及时做好记录并报{{ COMPANY_NAME }}相关部门妥善处理
</p>
<p class="indent-8 mb-2"><strong>违规处罚</strong></p>
<p class="indent-8 mb-2">
1各代理在推广{{ COMPANY_NAME }}过程中有损害{{ COMPANY_NAME }}产品信誉行为时视情节轻重{{ COMPANY_NAME }}将对其提出书面警告直至取消其代理资格;
</p>
<p class="indent-8 mb-2">
2未按{{ COMPANY_NAME }}有关规定和本制度开展工作的{{ COMPANY_NAME }}将提出书面警告并限期整改;
</p>
<p class="indent-8 mb-2">
3不遵守{{ COMPANY_NAME }}的相关规章制度造成与其他推广代理纠纷时{{ COMPANY_NAME }}将视其情节轻重处以20000元以上50000元以下的罚款并取消其代理资格
</p>
<p class="indent-8 mb-2">
4违反保密义务导致{{ COMPANY_NAME }}重大损失的{{ COMPANY_NAME }}将对其处以5000-20000元罚款情节严重者将直接取消其代理资格
</p>
<p class="indent-8 mb-2">
5代理如严重违反{{ COMPANY_NAME }}相关规章制度{{ COMPANY_NAME }}可随时解除双方约定的部分或全部协议
</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为执行项 &nbsp;×为不执行项
</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">更多详细内容请认真阅读{{ APP_NAME }}代理协议</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{{ COMPANY_NAME }}将本着"诚信为本、长期服务"的宗旨和"公平合理"的原则对代理进行合理布局和调整以实现互利互惠共同快速发展的目的
</p>
<p class="indent-8 mb-2">
3因其他原因需终止代理关系需向{{ COMPANY_NAME }}提出书面申请
</p>
<p class="indent-8 mb-2">
4代理之间发生业务竞争和冲突{{ COMPANY_NAME }}将依据公平公正公开的原则按相关制度予以调解处理
</p>
<p class="indent-8 mb-2">
5{{ COMPANY_NAME }}与各代理之间出现协议上的纠纷{{ COMPANY_NAME }}所在地法院裁决
</p>
<p class="indent-8 mb-2">
6本制度的制定修改与废止皆经由{{ COMPANY_NAME }}讨论决定解释权归{{ COMPANY_NAME }}所有
</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 COMPANY_NAME = import.meta.env.VITE_COMPANY_NAME;
if (!COMPANY_NAME) throw new Error("缺少环境变量: VITE_COMPANY_NAME");
const APP_NAME = import.meta.env.VITE_APP_NAME;
if (!APP_NAME) throw new Error("缺少环境变量: VITE_APP_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></style>

View File

@@ -0,0 +1,228 @@
<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">
<!-- 修改点 1: 使用 desen 函数处理 mobile -->
<span class="text-gray-500 text-sm">{{ desen(item.query_params?.mobile, 'mobile') || '-' }}</span>
<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 class="flex items-center justify-between mb-2">
<h4 class="text-gray-700 font-medium">直接收益</h4>
<div class="flex flex-col items-end">
<!-- 主金额显示净佣金 -->
<span :class="getAmountColor(item)" class="font-bold text-lg">
{{ getAmountPrefix(item) }}{{ (item.net_amount || 0).toFixed(2) }}
</span>
<!-- 如果有部分退款显示原始金额和已退金额 -->
<span v-if="item.refunded_amount > 0 && item.net_amount > 0" class="text-gray-400 text-xs mt-1">
原始 {{ item.amount.toFixed(2) }}已退 {{ item.refunded_amount.toFixed(2) }}
</span>
</div>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-500 text-sm">{{ desen(item.query_params?.name) || '-' }}</span>
<!-- 状态标签 -->
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium"
:class="getStatusStyle(item)">
{{ getStatusText(item) }}
</span>
</div>
<div class="flex justify-between items-center mb-2">
<span class="text-gray-500 text-sm">{{ item.create_time || '-' }}</span>
</div>
<div class="flex items-center mt-2">
<span class="text-gray-500 text-sm">订单号</span>
<span class="text-gray-700 text-sm font-mono">{{ item.order_id || '-' }}</span>
</div>
</div>
</van-list>
</div>
</template>
<script setup>
// 颜色配置(根据产品名称映射)
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' },
'个人大数据': { 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' }
}
// 新增点 4: 定义脱敏函数
const desen = (value, type) => {
// 如果值是空,直接返回空字符串,让外层的 || '-' 生效
if (!value) {
return '';
}
// 根据类型进行不同的替换
switch (type) {
case 'mobile': // 手机号保留前3位和后4位中间4位替换为 ****
return value.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2');
case 'id_card':// 身份证保留前6位和后1位中间替换为 ********
return value.replace(/^(.{6}).*(.{1})$/, '$1********$2');
default:
return value; // 其他类型不处理
}
};
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 getAmountColor = (item) => {
// 如果净佣金为0或状态为已退款显示红色
if (item.net_amount <= 0 || item.status === 2) {
return 'text-red-500'
}
// 如果有部分退款,显示橙色
if (item.refunded_amount > 0) {
return 'text-orange-500'
}
// 正常情况显示绿色
return 'text-green-500'
}
// 获取金额前缀(+ 或 -
const getAmountPrefix = (item) => {
if (item.net_amount <= 0 || item.status === 2) {
return '-'
}
return '+'
}
// 获取状态文本
const getStatusText = (item) => {
if (item.status === 2 || item.net_amount <= 0) {
return '已退款'
}
if (item.status === 1) {
// 冻结中
if (item.refunded_amount > 0) {
return '冻结中(部分退款)'
}
return '冻结中'
}
if (item.status === 0) {
// 已结算
if (item.refunded_amount > 0) {
return '已结算(部分退款)'
}
return '已结算'
}
return '未知状态'
}
// 获取状态样式
const getStatusStyle = (item) => {
if (item.status === 2 || item.net_amount <= 0) {
return 'bg-red-100 text-red-800'
}
if (item.status === 1) {
// 冻结中
if (item.refunded_amount > 0) {
return 'bg-orange-100 text-orange-800'
}
return 'bg-yellow-100 text-yellow-800'
}
if (item.status === 0) {
// 已结算
if (item.refunded_amount > 0) {
return 'bg-blue-100 text-blue-800'
}
return 'bg-green-100 text-green-800'
}
return 'bg-gray-100 text-gray-800'
}
// 加载更多数据
const onLoad = async () => {
if (!finished.value) {
page.value++
await getData()
}
}
// 获取数据
const getData = async () => {
try {
loading.value = true
const { data: res, error } = await useApiFetch(
`/agent/commission?page=${page.value}&page_size=${pageSize.value}`
).get().json()
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
}
}
} 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>

View File

@@ -0,0 +1,140 @@
<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">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium"
:class="getReportTypeStyle(item.type)">
<span class="w-2 h-2 rounded-full mr-1" :class="getDotColor(item.type)"></span>
{{ typeToChinese(item.type) }}
</span>
</div>
</div>
</van-list>
</div>
</template>
<script setup>
// 类型映射配置
const typeConfig = {
descendant_promotion: {
chinese: '下级推广奖励',
color: { bg: 'bg-blue-100', text: 'text-blue-800', dot: 'bg-blue-500' }
},
descendant_upgrade_vip: {
chinese: '下级升级VIP奖励',
color: { bg: 'bg-green-100', text: 'text-green-800', dot: 'bg-green-500' }
},
descendant_upgrade_svip: {
chinese: '下级升级SVIP奖励',
color: { bg: 'bg-purple-100', text: 'text-purple-800', dot: 'bg-purple-500' }
},
descendant_withdraw: {
chinese: '下级提现奖励',
color: { bg: 'bg-indigo-100', text: 'text-indigo-800', dot: 'bg-indigo-500' }
},
default: {
chinese: '其他奖励',
color: { bg: 'bg-gray-100', text: 'text-gray-800', dot: 'bg-gray-500' }
}
}
const page = ref(1)
const pageSize = ref(10)
const data = ref({
total: 0,
list: []
})
const loading = ref(false)
const finished = ref(false)
// 类型转中文
const typeToChinese = (type) => {
return typeConfig[type]?.chinese || typeConfig.default.chinese
}
// 获取颜色样式
const getReportTypeStyle = (type) => {
const config = typeConfig[type] || typeConfig.default
return `${config.color.bg} ${config.color.text}`
}
// 获取小圆点颜色
const getDotColor = (type) => {
return typeConfig[type]?.color.dot || typeConfig.default.color.dot
}
// 加载更多数据
const onLoad = async () => {
if (!finished.value) {
page.value++
await getData()
}
}
// 获取数据
const getData = async () => {
try {
loading.value = true
const { data: res, error } = await useApiFetch(
`/agent/rewards?page=${page.value}&page_size=${pageSize.value}`
).get().json()
if (res.value?.code === 200 && !error.value) {
if (page.value === 1) {
data.value = res.value.data
// 策略A前端下线“新增活跃/月度活跃奖励”
if (Array.isArray(data.value.list)) {
data.value.list = data.value.list.filter(
i => i.type !== 'descendant_new_active' && i.type !== 'descendant_stay_active'
)
}
} else {
if (Array.isArray(res.value.data.list)) {
const list = res.value.data.list.filter(
i => i.type !== 'descendant_new_active' && i.type !== 'descendant_stay_active'
)
data.value.list.push(...list)
}
}
if (data.value.list.length >= res.value.data.total ||
res.value.data.list.length < pageSize.value) {
finished.value = true
}
}
} 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>

View File

@@ -0,0 +1,277 @@
<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">戎行技术有限公司</span>
</p>
<p class="text-left">&nbsp;</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">戎行技术有限公司</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
&nbsp; &nbsp;</span><span class="text-black">戎行技术有限公司</span><span
class="text-black">信息技术服务服务
指乙方通过信息化人工智能和信息科技等技术手段对</span><span class="text-black">大数据</span><span
class="text-black">进行以公众号小程序APPweb页面以下简称平台或标准接口形式为客户提供的服务协助客户完成信息的整理管理等业务流程</span>
</p>
<p class="text-left"><span class="text-black">1.2
&nbsp; &nbsp;本合同 指本文本协议及其形成本合同不可分割的附件</span></p>
<p class="text-left"><span class="text-black">1.3
&nbsp; &nbsp;本合同标题仅供方便参考之用不影响本合同的含义与解释</span></p>
<p class="text-left"><span class="text-black">1.4
&nbsp; &nbsp;本合同甲方乙方单独称为一方合称为双方</span></p>
<p class="text-left"><span class="text-black">1.5
&nbsp; &nbsp;法律法规 指中国法律行政法规部门规章地方性法规地方性政府部门规章以及由政府机构颁布的其他规范性文件</span></p>
<p class="text-left"><span class="text-black">1.6
&nbsp; &nbsp;工作日 指法定节假日休息日之外的日期</span></p>
<p class="text-left"><span class="text-black">1.7
&nbsp; &nbsp;服务有效期 指乙方依据本合同提供服务的期限</span></p>
<p class="text-left"><span class="text-black">1.8
&nbsp; &nbsp;授权
指甲方最终用户以书面签名或法律效力等同于书面的电子签名等方式明确同意甲方向第三方服务商指乙方并含其关联方下同提供本人单位相关数据信息包括但不限于个人信息行为交易设备不良信息下同及同意该第三方服务提供商查询核实搜集处理共享使用含合法业务应用其本人单位相关数据的行为但是法律法规规定可以不经同意的除外</span>
</p>
<p class="text-left"><span class="text-black">1.9
&nbsp; &nbsp;APIApplication Programming Interface,应用程序编程接口
指一些预先定义的函数目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力而又无需访问源码或理解内部工作机制的细节</span></p>
<p class="text-left"><span class="text-black">1.10
&nbsp; &nbsp;Web应用 指一种可以通过Web访问的应用程序由完成特定任务的各种Web组件web
components构成的并通过Web将服务展示给客户</span></p>
<p class="text-left"><span class="text-black">1.11
&nbsp; &nbsp;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
&nbsp; &nbsp;根据本合同约定的条件和条款甲方使用乙方提供的</span><span
class="text-black">戎行技术有限公司</span><span class="text-black">相关</span><span
class="text-black">信息技术服务简称乙方服务本服务</span></p>
<p class="text-left"><span class="text-black">2.2
&nbsp; &nbsp;
乙方负责提供服务平台或接口供甲方通过平台或接口使用本服务乙方向甲方提供的平台或接口是使用本服务的重要凭证除非另有约定或说明甲方通过该平台或接口向乙方所发出的指令及相关行为均视为甲方的行为</span>
</p>
<p class="text-left"><span class="text-black">2.3
&nbsp; &nbsp; 甲方通过乙方提供的平台或APIWeb应用向乙方发起服务需求乙方通过平台或APIWeb应用将服务结果返回给甲方</span>
</p>
<p class="text-left"><span class="text-black">
费用及支付</span></p>
<p class="text-left"><span class="text-black">3.1
&nbsp; &nbsp; 本合同采用先付费后使用的计价模式甲方依据自主选择的服务项目对应费用向乙方支付服务费用</span></p>
<p class="text-left"><span class="text-black">3.2
&nbsp; &nbsp;服务有效期及支付方式</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
&nbsp; &nbsp;支付方式</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
&nbsp; &nbsp;甲方权利与义务</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
&nbsp; &nbsp;乙方的权利和义务</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.65.1.75.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
&nbsp; &nbsp;
乙方享有本合同产品和服务相关内容之知识产权与所有权包括但不限于软件程序源代码文档专利商标著作权域名专有技术商业秘密文字表达及其组合数据数据变量数据算法数据模型图标图饰图表色彩界面设计除非经乙方许可本合同并不赋予甲方享有乙方任何知识产权上的权利</span>
</p>
<p class="text-left"><span class="text-black">5.2
&nbsp; &nbsp; 除非另有约定任何一方均不可凭借本合同取得另一方所拥有的著作权专利权商标权或其他知识产权</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">
除非双方签订商业保密协议对保密另有约定外需履行以下保密约定 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </span></p>
<p class="text-left"><span class="text-black">6.1
&nbsp; &nbsp;
保密信息提供方或其母公司子公司关联公司向接受方披露的信息包括但不限于本合同内容合作模式商业计划投资经营方案分析或计算方法系统数据数据变量数据算法数据模型程序装置规格序列设计研究或开发活动和方案知识产权专有技术服务信息与服务说明业务与营销方案推广方法销售额客户名单商业机会成本价格及其他财务信息</span>
</p>
<p class="text-left"><span class="text-black">6.2
&nbsp; &nbsp;
保密信息不包括1在收到保密信息之时或之前已合法知悉的信息且该信息不受保密义务约束2非因接受方违约而成为公众信息的信息3接受方从披露方及其关联方以外的其他信息源所获知的信息4提供方未明示为保密信息的信息5接受方未利用任何保密信息而合法独立开发的信息或通过接受方信息数据分析而获知的信息</span>
</p>
<p class="text-left"><span class="text-black">6.3
&nbsp; &nbsp;
上述保密信息可以以数据文字及记载上述内容的书面资料图书录音资料录像资料光盘软件网页客户端等有形媒介体现也可通过口头等形式体现</span>
</p>
<p class="text-left"><span class="text-black">6.4
任何一方在提供保密信息时如以书面形式提供应注明保密等相关字样如以口头或可视形式透露应在披露前明示接受方为保密信息</span></p>
<p class="text-left"><span class="text-black">6.5
&nbsp; &nbsp;
除另有约定外未经披露方书面同意须将保密信息严格保密并不得直接或间接导致准许或容许向任何第三方披露公布转移挪用或泄露保密信息接受方均不向第三方及其不必要知悉的员工披露保密信息</span>
</p>
<p class="text-left"><span class="text-black">6.6
&nbsp; &nbsp;
除另有约定外承担保密义务的范围不包括1由乙方提供给数据源方的2提供给乙方关联公司的3将经适当汇总编辑修改整理的保密信息在必要与合理范围内提供给律师和会计师及其他专业服务提供者4按照法律法规监管有管辖权的法院要求须提供的信息但在不禁止的情况下应立即向保密信息提供方予以通报</span>
</p>
<p class="text-left"><span class="text-black">6.7
&nbsp; &nbsp; 双方确认除非保密信息依法公开保密义务自本合同签订时开始持续有效甲方是否继续使用乙方的服务不影响保密义务的承担</span>
</p>
<p class="text-left"><span class="text-black">6.8
&nbsp; &nbsp;
双方均应当遵守法律法规关于信息安全的管理规定并采取有效措施保障信息安全包括但不限于保障计算机系统及其相关配套设备设施含网络运行环境信息系统功能的安全运行等</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
&nbsp; &nbsp; 双方都清楚并愿意严格遵守反商业贿赂的法律规定任何形式的贿赂和贪渎行为都将触犯法律并将受到不利后果</span></p>
<p class="text-left"><span class="text-black">7.2
&nbsp; &nbsp;
双方均不得向对方或对方经办人或其他相关人员索要收受提供给予本合同约定外的任何利益包括但不限于明扣暗扣现金购物卡实物有价证券旅游或其他非物质利益等但如该等利益属于行业惯例或通常礼仪做法的除外</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
&nbsp; &nbsp;
除非另有约定任何一方未履行本合同或违反约定且未及时改正或采取补救措施的违约方应向守约方按照已发生合同金额的</span><span
class="text-black">30</span><span
class="text-black">%向守约方承担违约责任并赔偿守约方因此受到的直接经济损失包括调查仲裁诉讼律师等合理费用</span></p>
<p class="text-left"><span class="text-black">8.2
&nbsp; &nbsp;
如因甲方原因导致本合同终止的其无权要求乙方退回已收取而未使用的服务费若一方因对方或其它非己方原因未能履行本合同下的义务一方无需赔偿对方承受的损失</span>
</p>
<p class="text-left"><span class="text-black">8.3
&nbsp; &nbsp; 一方未行使迟延行使或部分行使其权利并不意味该权利被放弃某一权利不行使并不意味着其它权利被放弃</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
&nbsp; &nbsp;
不可抗力不可抗力是指合同双方当事人不能预见不能避免并不能克服的客观情况包括但不限于战争骚乱恐怖主义洪水地震台风国家公布的疫情等事件</span>
</p>
<p class="text-left"><span class="text-black">9.2
&nbsp; &nbsp;
因法律法规规章规定指引通知政策命令及其他规范性文件或政府行为原因导致本合同不能履行的适用关于不可抗力的规定</span></p>
<p class="text-left"><span class="text-black">9.3
&nbsp; &nbsp; 乙方为改善服务质量或数据源方对系统进行调整升级扩容等措施而导致服务中断延时等情况</span></p>
<p class="text-left"><span class="text-black">9.4
&nbsp; &nbsp; 因网络设备黑客攻击计算机病毒侵入或发作通信或电力故障等不可预测因素造成不能提供服务的情形</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
&nbsp; 因履行本合同而发生的一切争议甲乙双方应协商解决协商不成的任何一方均有权向乙方所在地有管辖权法院提起诉讼</span></p>
<p class="text-left"><span class="text-black">十一
通知与送达</span></p>
<p class="text-left"><span class="text-black">11.1
&nbsp;
本合同项下的通知应以电子邮件快递传真专人递送按合同签署页所列示联系方式发出,除非任何一方已书面通知对方变更联系方式并经对方确认</span></p>
<p class="text-left"><span class="text-black">11.2
&nbsp;
书面通知的形式包括在网站公告电子邮件站内信微信手机短信和传真等电子方式及纸质文件通知在下列日期视为送达被通知方如以电子邮件发送显示成功发送确认时或发送后第一个工作日内未被退回如是以快递发送以交邮后第五个工作日如以传真发送于发件人传真机记录传输确认时如以专人递送被通知方签收日</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
&nbsp;
本合同附件作为本合同的组成部分与本合同具有同等的法律效力未尽事宜经双方协商后签订补充合同补充合同与本合同条款如有冲突以补充合同为准</span>
</p>
<p class="text-left"><span class="text-black">12.2
&nbsp; 本合同终止日与服务有效期终止日相同在合同期限届满前由双方友好协商是否续约</span></p>
<p class="text-left"><span class="text-black">12.3
&nbsp;
本合同终止不影响合同中有关授权付款保密知识产权反商业贿赂违约责任争议解决与法律适用等可以独立存在的条款的效力一方未履行完毕的义务仍需继续履行</span>
</p>
<p class="text-left"><span class="text-black">12.4
&nbsp; 本合同自甲方支付/充值费用之日</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 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>

40
src/views/AgentVip.vue Normal file
View File

@@ -0,0 +1,40 @@
<template>
<div class="relative">
<img class="" src="@/assets/images/vip_bg.png" alt="代理会员" />
<div
class="absolute left-[50%] translate-x-[-50%] bottom-80 flex flex-col gap-4 items-center"
>
<div
@click="toVipApply"
class="bg-gradient-to-r from-amber-500 to-amber-600 py-2 px-6 rounded-lg text-white text-[24px] font-bold shadow-[0_0_15px_rgba(255,255,255,0.3)] hover:scale-105 transition-transform"
>
申请VIP代理
</div>
<div
@click="toService"
class="bg-gradient-to-r from-gray-900 via-black to-gray-900 py-2 px-4 rounded-lg text-white text-[20px] font-bold shadow-[0_0_15px_rgba(255,255,255,0.3)] hover:scale-105 transition-transform"
>
联系客服
</div>
</div>
</div>
</template>
<script setup>
import { useRouter } from "vue-router";
const router = useRouter();
function toVipApply() {
router.push("/agent/vipApply");
}
function toService() {
// window.location.href = '/service' // 跳转到客服页面
const url = import.meta.env.VITE_CUSTOMER_SERVICE_URL;
if (!url) throw new Error("缺少环境变量: VITE_CUSTOMER_SERVICE_URL");
window.location.href = url;
}
</script>
<style lang="scss" scoped></style>

833
src/views/AgentVipApply.vue Normal file
View File

@@ -0,0 +1,833 @@
<template>
<div class="agent-VIP-apply w-full min-h-screen bg-gradient-to-b from-amber-50 via-amber-100 to-amber-50 pb-24">
<!-- 装饰元素 -->
<div
class="absolute top-0 right-0 w-32 h-32 bg-gradient-to-br from-amber-300 to-amber-500 rounded-bl-full opacity-20">
</div>
<div
class="absolute top-40 left-0 w-16 h-16 bg-gradient-to-tr from-amber-400 to-amber-600 rounded-tr-full opacity-20">
</div>
<div
class="absolute bottom-60 right-0 w-24 h-24 bg-gradient-to-bl from-amber-300 to-amber-500 rounded-tl-full opacity-20">
</div>
<!-- 顶部标题区域 -->
<div class="header relative pt-8 px-4 pb-6 text-center">
<div
class="animate-pulse absolute -top-2 left-1/2 -translate-x-1/2 w-24 h-1 bg-gradient-to-r from-amber-300 via-amber-500 to-amber-300 rounded-full">
</div>
<h1 class="text-3xl font-bold text-amber-800 mb-1">
{{ isVipOrSvip ? '代理会员续费' : 'VIP代理申请' }}
</h1>
<p class="text-sm text-amber-700 mt-2 max-w-xs mx-auto">
<template v-if="isVipOrSvip">
您的会员有效期至 {{ formatExpiryTime(ExpiryTime) }}续费后有效期至
{{ renewalExpiryTime }}
</template>
<template v-else>
平台为疯狂推广者定制的赚买计划助您收益<span class="text-red-500 font-bold">翻倍增升</span>
</template>
</p>
<!-- 装饰性金币图标 -->
<div class="absolute top-6 left-4 transform -rotate-12">
<div
class="w-8 h-8 bg-gradient-to-br from-yellow-300 to-yellow-500 rounded-full flex items-center justify-center shadow-lg">
<span class="text-white font-bold text-xs">¥</span>
</div>
</div>
<div class="absolute top-10 right-6 transform rotate-12">
<div
class="w-6 h-6 bg-gradient-to-br from-yellow-400 to-yellow-600 rounded-full flex items-center justify-center shadow-lg">
<span class="text-white font-bold text-xs">¥</span>
</div>
</div>
</div>
<!-- 选择代理类型 -->
<div class="card-container px-4 mb-8">
<div class="bg-white rounded-xl shadow-lg overflow-hidden border border-amber-100 transform transition-all">
<h2
class="bg-gradient-to-r from-amber-500 to-amber-600 text-white py-3 px-4 text-center font-bold relative overflow-hidden">
<span class="relative z-10">选择代理类型</span>
<div class="absolute inset-0 bg-amber-500 opacity-30">
<div
class="absolute top-0 left-0 w-full h-full bg-gradient-to-r from-transparent via-white to-transparent opacity-20 transform -skew-x-30 translate-x-full animate-shimmer">
</div>
</div>
</h2>
<div class="flex p-6 gap-4">
<div
class="flex-1 border-2 rounded-lg p-4 text-center cursor-pointer transition-all duration-300 relative transform hover:-translate-y-1"
:class="[
selectedType === 'vip'
? 'border-amber-500 bg-amber-50 shadow-md'
: 'border-gray-200 hover:border-amber-300',
]" @click="selectType('vip')">
<div class="text-xl font-bold text-amber-700">VIP代理</div>
<div class="text-amber-600 font-bold mt-1 text-lg">{{ vipConfig.price }}{{ vipConfig.priceUnit }}</div>
<div class="mt-2 text-gray-600 text-sm">标准VIP权益</div>
<div v-if="selectedType === 'vip'"
class="absolute -top-2 -right-2 w-6 h-6 bg-gradient-to-br from-amber-500 to-amber-600 rounded-full flex items-center justify-center shadow-md">
<van-icon name="success" color="#fff" size="14" />
</div>
</div>
<div
class="flex-1 border-2 rounded-lg p-4 text-center cursor-pointer transition-all duration-300 relative transform hover:-translate-y-1"
:class="[
selectedType === 'svip'
? 'border-amber-500 bg-amber-50 shadow-md'
: 'border-gray-200 hover:border-amber-300',
]" @click="selectType('svip')">
<div class="text-xl font-bold text-amber-700">SVIP代理</div>
<div class="text-amber-600 font-bold mt-1 text-lg">{{ vipConfig.svipPrice }}{{ vipConfig.priceUnit }}</div>
<div class="mt-2 text-gray-600 text-sm">超级VIP权益</div>
<div v-if="selectedType === 'svip'"
class="absolute -top-2 -right-2 w-6 h-6 bg-gradient-to-br from-amber-500 to-amber-600 rounded-full flex items-center justify-center shadow-md">
<van-icon name="success" color="#fff" size="14" />
</div>
</div>
</div>
</div>
</div>
<!-- 六大超值权益 -->
<div class="card-container px-4 mb-8">
<div class="bg-white rounded-xl shadow-lg overflow-hidden border border-amber-100">
<h2
class="bg-gradient-to-r from-amber-500 to-amber-600 text-white py-3 px-4 text-center font-bold relative overflow-hidden">
<span class="relative z-10">六大超值权益</span>
<div class="absolute inset-0 bg-amber-500 opacity-30">
<div
class="absolute top-0 left-0 w-full h-full bg-gradient-to-r from-transparent via-white to-transparent opacity-20 transform -skew-x-30 translate-x-full animate-shimmer">
</div>
</div>
</h2>
<div class="grid grid-cols-2 gap-4 p-4">
<!-- 权益1 -->
<div
class="bg-gradient-to-br from-amber-50 to-amber-100 rounded-lg p-3 border border-amber-200 transition-all duration-300 hover:shadow-md hover:border-amber-300">
<div class="text-amber-800 font-bold mb-2 flex items-center">
<span
class="w-6 h-6 bg-gradient-to-br from-amber-500 to-amber-600 rounded-full flex items-center justify-center text-white text-xs mr-2">1</span>
下级贡献收益
</div>
<p class="text-sm text-gray-600">
下级完全收益您来定涨多少赚多少一单最高收益<span class="text-red-500 font-bold">10</span>
</p>
</div>
<!-- 权益2 -->
<div
class="bg-gradient-to-br from-amber-50 to-amber-100 rounded-lg p-3 border border-amber-200 transition-all duration-300 hover:shadow-md hover:border-amber-300">
<div class="text-amber-800 font-bold mb-2 flex items-center">
<span
class="w-6 h-6 bg-gradient-to-br from-amber-500 to-amber-600 rounded-full flex items-center justify-center text-white text-xs mr-2">2</span>
下级提现收益
</div>
<p class="text-sm text-gray-600">
下级定价标准由您定超过标准部分收益更丰厚一单最高多赚<span class="text-red-500 font-bold">10</span>
</p>
</div>
<!-- 权益3 -->
<div
class="bg-gradient-to-br from-amber-50 to-amber-100 rounded-lg p-3 border border-amber-200 transition-all duration-300 hover:shadow-md hover:border-amber-300">
<div class="text-amber-800 font-bold mb-2 flex items-center">
<span
class="w-6 h-6 bg-gradient-to-br from-amber-500 to-amber-600 rounded-full flex items-center justify-center text-white text-xs mr-2">3</span>
转换高额奖励
</div>
<p class="text-sm text-gray-600">
下级成为VIPSVIP高额奖励立马发放<span class="text-red-500 font-bold">399</span>
</p>
</div>
<!-- 权益4 -->
<div
class="bg-gradient-to-br from-amber-50 to-amber-100 rounded-lg p-3 border border-amber-200 transition-all duration-300 hover:shadow-md hover:border-amber-300">
<div class="text-amber-800 font-bold mb-2 flex items-center">
<span
class="w-6 h-6 bg-gradient-to-br from-amber-500 to-amber-600 rounded-full flex items-center justify-center text-white text-xs mr-2">4</span>
下级提现奖励
</div>
<p class="text-sm text-gray-600">下级成为SVIP每次提现都奖励1%坐享被动收入</p>
</div>
<!-- 权益6 -->
<div
class="bg-gradient-to-br from-amber-50 to-amber-100 rounded-lg p-3 border border-amber-200 transition-all duration-300 hover:shadow-md hover:border-amber-300">
<div class="text-amber-800 font-bold mb-2 flex items-center">
<span
class="w-6 h-6 bg-gradient-to-br from-amber-500 to-amber-600 rounded-full flex items-center justify-center text-white text-xs mr-2">6</span>
平台专项扶持
</div>
<p class="text-sm text-gray-600">一对一专属客服服务为合作伙伴提供全方位成长赋能</p>
</div>
</div>
</div>
</div>
<!-- 权益对比表 -->
<div class="card-container px-4 mb-8" v-if="selectedType">
<div class="bg-white rounded-xl shadow-lg overflow-hidden border border-amber-100">
<h2
class="bg-gradient-to-r from-amber-500 to-amber-600 text-white py-3 px-4 text-center font-bold relative overflow-hidden">
<span class="relative z-10">{{ selectedType === 'vip' ? 'VIP' : 'SVIP' }}代理权益对比</span>
<div class="absolute inset-0 bg-amber-500 opacity-30">
<div
class="absolute top-0 left-0 w-full h-full bg-gradient-to-r from-transparent via-white to-transparent opacity-20 transform -skew-x-30 translate-x-full animate-shimmer">
</div>
</div>
</h2>
<div class="p-4 overflow-x-auto">
<table class="w-full border-collapse">
<thead>
<tr class="bg-gradient-to-r from-amber-100 to-amber-200">
<th class="border border-amber-200 p-2 text-left text-amber-800">权益项目</th>
<th class="border border-amber-200 p-2 text-center text-amber-800">普通代理</th>
<th class="border border-amber-200 p-2 text-center text-amber-800"
:class="{ 'bg-amber-200': selectedType === 'vip' }">
VIP代理
</th>
<th class="border border-amber-200 p-2 text-center text-amber-800"
:class="{ 'bg-amber-200': selectedType === 'svip' }">
SVIP代理
</th>
</tr>
</thead>
<tbody>
<tr class="hover:bg-amber-50 transition-colors">
<td class="border border-amber-200 p-2 font-medium">会员权益</td>
<td class="border border-amber-200 p-2 text-center">普通代理<br />免费</td>
<td class="border border-amber-200 p-2 text-center font-bold" :class="{
'text-amber-700 bg-amber-50': selectedType === 'vip',
}">
{{ vipConfig.price }}{{ vipConfig.priceUnit }}
</td>
<td class="border border-amber-200 p-2 text-center font-bold" :class="{
'text-amber-700 bg-amber-50': selectedType === 'svip',
}">
{{ vipConfig.svipPrice }}{{ vipConfig.priceUnit }}
</td>
</tr>
<tr class="hover:bg-amber-50 transition-colors">
<td class="border border-amber-200 p-2 font-medium">下级贡献收益</td>
<td class="border border-amber-200 p-2 text-center">1/</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
}">
{{ vipConfig.vipCommission }}/
</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
}">
{{ vipConfig.svipCommission }}/
</td>
</tr>
<tr class="hover:bg-amber-50 transition-colors">
<td class="border border-amber-200 p-2 font-medium">自定义设置下级成本</td>
<td class="border border-amber-200 p-2 text-center"></td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
}">
</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
}">
</td>
</tr>
<tr class="hover:bg-amber-50 transition-colors">
<td class="border border-amber-200 p-2 font-medium">下级价格浮动收益</td>
<td class="border border-amber-200 p-2 text-center"></td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
}">
最高{{ vipConfig.vipFloatingRate }}%
</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
}">
最高{{ vipConfig.svipFloatingRate }}%
</td>
</tr>
<tr class="hover:bg-amber-50 transition-colors">
<td class="border border-amber-200 p-2 font-medium">下级提现奖励</td>
<td class="border border-amber-200 p-2 text-center"></td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
}">
</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
}">
{{ vipConfig.withdrawRatio }}%
</td>
</tr>
<tr class="hover:bg-amber-50 transition-colors">
<td class="border border-amber-200 p-2 font-medium">下级转化奖励</td>
<td class="border border-amber-200 p-2 text-center"></td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
}">
{{ vipConfig.vipConversionBonus }}*10
</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
}">
{{ vipConfig.svipConversionBonus }}*10
</td>
</tr>
<tr class="hover:bg-amber-50 transition-colors">
<td class="border border-amber-200 p-2 font-medium">提现次数额度</td>
<td class="border border-amber-200 p-2 text-center">800/</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
}">
{{ vipConfig.vipWithdrawalLimit }}/
</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
}">
{{ vipConfig.svipWithdrawalLimit }}/
</td>
</tr>
<tr class="hover:bg-amber-50 transition-colors">
<td class="border border-amber-200 p-2 font-medium">提现次数</td>
<td class="border border-amber-200 p-2 text-center">1/</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-bold bg-amber-50': selectedType === 'vip',
}">
1/
</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-bold bg-amber-50': selectedType === 'svip',
}">
2/
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 收益预估 -->
<div class="card-container px-4 mb-8" v-if="selectedType">
<div class="bg-white rounded-xl shadow-lg overflow-hidden border border-amber-100">
<h2
class="bg-gradient-to-r from-amber-500 to-amber-600 text-white py-3 px-4 text-center font-bold relative overflow-hidden">
<span class="relative z-10">收益预估对比</span>
<div class="absolute inset-0 bg-amber-500 opacity-30">
<div
class="absolute top-0 left-0 w-full h-full bg-gradient-to-r from-transparent via-white to-transparent opacity-20 transform -skew-x-30 translate-x-full animate-shimmer">
</div>
</div>
</h2>
<div class="p-4">
<!-- 顶部收益概览 -->
<div class="mb-6 rounded-lg overflow-hidden border border-amber-200">
<div class="bg-gradient-to-r from-amber-100 to-amber-200 py-2 px-4 text-center font-bold text-amber-800">
VIP与SVIP代理收益对比
</div>
<div class="grid grid-cols-2 divide-x divide-amber-200">
<div class="p-4 text-center" :class="{ 'bg-amber-50': selectedType === 'vip' }">
<div class="text-sm text-gray-600 mb-1">VIP月预计收益</div>
<div class="text-amber-600 font-bold text-xl">{{ revenueData.vipMonthly }}</div>
<div class="text-xs text-gray-500 mt-1">年收益{{ revenueData.vipYearly }}</div>
</div>
<div class="p-4 text-center" :class="{ 'bg-amber-50': selectedType === 'svip' }">
<div class="text-sm text-gray-600 mb-1">SVIP月预计收益</div>
<div class="text-red-500 font-bold text-xl">{{ revenueData.svipMonthly }}</div>
<div class="text-xs text-gray-500 mt-1">年收益{{ revenueData.svipYearly }}</div>
</div>
</div>
<div class="bg-gradient-to-r from-red-50 to-red-100 py-2 px-4 text-center text-red-600 font-medium">
选择SVIP相比VIP月增收益<span class="font-bold">{{ revenueData.monthlyDifference }}</span>
</div>
</div>
<!-- 详细收益表格 -->
<div class="overflow-x-auto">
<table class="w-full border-collapse">
<thead>
<tr class="bg-gradient-to-r from-amber-100 to-amber-200">
<th class="border border-amber-200 p-2 text-left text-amber-800">收益来源</th>
<th class="border border-amber-200 p-2 text-center text-amber-800 w-1/3"
:class="{ 'bg-amber-200': selectedType === 'vip' }">
VIP代理
</th>
<th class="border border-amber-200 p-2 text-center text-amber-800 w-1/3"
:class="{ 'bg-amber-200': selectedType === 'svip' }">
SVIP代理
</th>
</tr>
</thead>
<tbody>
<tr class="hover:bg-amber-50 transition-colors">
<td class="border border-amber-200 p-2 font-medium">推广收益()</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
}">
300×50=15,000
</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
}">
300×50=15,000
</td>
</tr>
<tr class="hover:bg-amber-50 transition-colors">
<td class="border border-amber-200 p-2 font-medium">下级贡献收益()</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
}">
300×{{ vipConfig.vipCommission }}=360
</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
}">
300×{{ vipConfig.svipCommission }}=450
</td>
</tr>
<tr class="hover:bg-amber-50 transition-colors">
<td class="border border-amber-200 p-2 font-medium">下级价格浮动收益()</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
}">
100×100×{{ vipConfig.vipFloatingRate }}%=500
</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
}">
200×100×{{ vipConfig.svipFloatingRate }}%=2,000
</td>
</tr>
<tr class="hover:bg-amber-50 transition-colors">
<td class="border border-amber-200 p-2 font-medium">下级提现奖励()</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
}">
-
</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
}">
{{ revenueData.withdrawReward }}
</td>
</tr>
<tr class="hover:bg-amber-50 transition-colors">
<td class="border border-amber-200 p-2 font-medium">下级转化奖励()</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
}">
{{ vipConfig.vipConversionBonus }}×2=598
</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
}">
{{ vipConfig.svipConversionBonus }}×2=798
</td>
</tr>
<tr class="hover:bg-amber-50 transition-colors">
<td class="border border-amber-200 p-2 font-medium">额外业务收益()</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-medium bg-amber-50': selectedType === 'vip',
}">
约3,000
</td>
<td class="border border-amber-200 p-2 text-center" :class="{
'text-amber-700 font-medium bg-amber-50': selectedType === 'svip',
}">
约6,000
</td>
</tr>
<tr class="bg-gradient-to-r from-amber-50 to-amber-100 font-bold">
<td class="border border-amber-200 p-3">月计收益</td>
<td class="border border-amber-200 p-3 text-center text-amber-700" :class="{
'bg-amber-50 border-amber-300': selectedType === 'vip',
}">
{{ revenueData.vipMonthly }}
</td>
<td class="border border-amber-200 p-3 text-center text-red-500" :class="{
'bg-amber-50 border-amber-300': selectedType === 'svip',
}">
{{ revenueData.svipMonthly }}
</td>
</tr>
<tr class="bg-gradient-to-r from-amber-50 to-amber-100 font-bold">
<td class="border border-amber-200 p-3">年计收益</td>
<td class="border border-amber-200 p-3 text-center text-amber-700" :class="{
'bg-amber-50 border-amber-300': selectedType === 'vip',
}">
{{ revenueData.vipYearly }}
</td>
<td class="border border-amber-200 p-3 text-center text-red-500" :class="{
'bg-amber-50 border-amber-300': selectedType === 'svip',
}">
{{ revenueData.svipYearly }}
</td>
</tr>
</tbody>
</table>
</div>
<!-- 投资回报率 -->
<div class="mt-6 p-4 bg-gradient-to-r from-amber-50 to-amber-100 rounded-lg border border-amber-200">
<div class="text-center mb-3 font-bold text-amber-800">投资收益分析</div>
<div class="grid grid-cols-1 gap-4">
<div class="p-3 bg-white rounded-lg shadow-sm">
<div class="flex items-center justify-between">
<div class="flex-1 border-r border-amber-100 pr-3">
<div class="text-amber-700 font-medium text-center mb-1">VIP方案</div>
<div class="text-center">
<div class="text-amber-600 text-sm">投资{{ vipConfig.price }}</div>
<div class="text-gray-600 text-sm">月收益{{ revenueData.vipMonthly }}</div>
</div>
</div>
<div class="flex-1 pl-3">
<div class="text-red-500 font-medium text-center mb-1">SVIP方案</div>
<div class="text-center">
<div class="text-red-500 text-sm">投资{{ vipConfig.svipPrice }}</div>
<div class="text-gray-600 text-sm">月收益{{ revenueData.svipMonthly }}</div>
</div>
</div>
</div>
</div>
<!-- 升级收益对比 -->
<div class="p-3 bg-gradient-to-r from-red-50 to-amber-50 rounded-lg shadow-sm">
<div class="text-center font-medium text-red-700 mb-2">SVIP升级优势分析</div>
<div class="flex items-center justify-center gap-3">
<div class="text-center">
<div class="text-sm text-gray-600">额外投资</div>
<div class="text-red-600 font-bold">{{ revenueData.priceDifference }}</div>
</div>
<div
class="bg-red-500 flex-shrink-0 text-white rounded-full w-6 h-6 flex items-center justify-center">
<div class="transform -translate-y-px"></div>
</div>
<div class="text-center">
<div class="text-sm text-gray-600">每月额外收益</div>
<div class="text-red-600 font-bold">{{ revenueData.monthlyDifference }}</div>
</div>
<div
class="bg-red-500 flex-shrink-0 text-white rounded-full w-6 h-6 flex items-center justify-center">
<span class="transform -translate-y-px"></span>
</div>
<div class="text-center">
<div class="text-sm text-gray-600">投资回收时间</div>
<div class="text-red-600 font-bold">{{ revenueData.recoverDays }}</div>
</div>
</div>
<div class="text-center text-red-500 font-medium mt-3">
额外投资{{ revenueData.priceDifference }}<span class="text-red-600 font-bold">年多赚{{
revenueData.yearlyDifference }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 申请按钮固定在底部 -->
<div
class="fixed bottom-0 left-0 right-0 px-4 py-3 bg-gradient-to-t from-amber-100 to-transparent backdrop-blur-sm z-30">
<div class="flex flex-col gap-2">
<button :class="buttonClass" @click="applyVip" :disabled="!canPerformAction">
<span class="relative z-10">{{ buttonText }}</span>
<div
class="absolute top-0 left-0 w-full h-full bg-gradient-to-r from-transparent via-white to-transparent opacity-20 transform -skew-x-30 translate-x-full animate-shimmer">
</div>
</button>
<button
class="w-full py-3 rounded-lg font-medium text-amber-700 border border-amber-400 bg-white shadow-md active:bg-amber-50 transform transition-transform active:scale-98"
@click="contactService">
<div class="flex items-center justify-center">
<van-icon name="service-o" class="mr-1" />
<span>联系客服咨询</span>
</div>
</button>
<!-- 最终解释权声明 -->
<div class="text-center text-xs text-gray-400 py-1">最终解释权归戎行技术有限公司所有</div>
</div>
</div>
</div>
<Payment v-model="showPayment" :data="payData" :id="payID" type="agent_vip" @close="showPayment = false" />
</template>
<script setup>
import { ref, onMounted, reactive, computed } from 'vue'
import { showToast } from 'vant'
import { useAgentStore } from '@/stores/agentStore'
import { storeToRefs } from 'pinia'
// 获取代理状态
const agentStore = useAgentStore()
const { level, ExpiryTime } = storeToRefs(agentStore)
// 计算是否已经是VIP或SVIP
const isVipOrSvip = computed(() => ['VIP', 'SVIP'].includes(level.value))
const isVip = computed(() => level.value === 'VIP')
const isSvip = computed(() => level.value === 'SVIP')
// 计算续费后的到期时间
const renewalExpiryTime = computed(() => {
if (!ExpiryTime.value) return '未知'
// 从格式化字符串中提取日期部分
const dateStr = ExpiryTime.value.split(' ')[0] // 假设格式是 "YYYY-MM-DD HH:MM:SS"
const [year, month, day] = dateStr.split('-').map(num => parseInt(num))
// 创建日期对象并加一年
const expiryDate = new Date(year, month - 1, day) // 月份从0开始所以要-1
expiryDate.setFullYear(expiryDate.getFullYear() + 1)
// 返回格式化的日期字符串
return `${expiryDate.getFullYear()}-${String(expiryDate.getMonth() + 1).padStart(2, '0')}-${String(expiryDate.getDate()).padStart(2, '0')}`
})
// 按钮文字 - 根据当前状态显示不同文案
const buttonText = computed(() => {
if (!isVipOrSvip.value) return '立即开通' // 非会员状态
if (selectedType.value === 'vip') {
if (isVip.value) return '续费VIP代理' // VIP续费VIP
return '降级不可用' // SVIP不能降级到VIP
} else {
if (isSvip.value) return '续费SVIP代理' // SVIP续费SVIP
return '升级SVIP代理' // VIP升级SVIP
}
})
// 是否可以操作按钮
const canPerformAction = computed(() => {
// 非会员可以开通任何会员
if (!isVipOrSvip.value) return true
// VIP不能降级到普通会员
if (isVip.value && selectedType.value === '') return false
// SVIP不能降级到VIP
if (isSvip.value && selectedType.value === 'vip') return false
return true
})
// 计算按钮类名
const buttonClass = computed(() => {
const baseClass =
'w-full py-4 rounded-lg font-bold text-lg shadow-lg transform transition-transform active:scale-98 relative overflow-hidden'
if (!canPerformAction.value) {
return `${baseClass} bg-gray-400 text-white cursor-not-allowed`
}
if (isVip.value && selectedType.value === 'svip') {
return `${baseClass} bg-gradient-to-r from-purple-500 to-indigo-600 text-white`
}
return `${baseClass} bg-gradient-to-r from-amber-500 to-amber-600 active:from-amber-600 active:to-amber-700 text-white`
})
// VIP价格配置
const vipConfig = reactive({})
// 计算得出的收益数据
const revenueData = computed(() => {
const baseOrders = 300 // 基础订单数
const pricePerOrder = 50 // 每单价格
const baseRevenue = baseOrders * pricePerOrder // 基础推广收益
const vipCommissionRevenue = baseOrders * vipConfig.vipCommission // VIP下级贡献收益
const svipCommissionRevenue = baseOrders * vipConfig.svipCommission // SVIP下级贡献收益
const vipFloatingRevenue = 100 * 100 * (vipConfig.vipFloatingRate / 100) // VIP浮动收益
const svipFloatingRevenue = 200 * 100 * (vipConfig.svipFloatingRate / 100) // SVIP浮动收益
const vipConversionRevenue = vipConfig.vipConversionBonus * 2 // VIP转化奖励
const svipConversionRevenue = vipConfig.svipConversionBonus * 2 // SVIP转化奖励
const vipExtraRevenue = 3000 // VIP额外收益估计
const svipExtraRevenue = 6000 // SVIP额外收益估计
// 平级提现奖励(只有SVIP才有)
const withdrawReward = 20000 * (vipConfig.withdrawRatio / 100)
// 计算月总收益
const vipMonthlyTotal =
baseRevenue +
vipCommissionRevenue +
vipFloatingRevenue +
vipConversionRevenue +
vipExtraRevenue
const svipMonthlyTotal =
baseRevenue +
svipCommissionRevenue +
svipFloatingRevenue +
withdrawReward +
svipConversionRevenue +
svipExtraRevenue
// 计算VIP和SVIP之间的差额
const monthlyDifference = svipMonthlyTotal - vipMonthlyTotal
const priceDifference = vipConfig.svipPrice - vipConfig.price
return {
vipMonthly: Math.round(vipMonthlyTotal),
svipMonthly: Math.round(svipMonthlyTotal),
vipYearly: Math.round(vipMonthlyTotal * 12),
svipYearly: Math.round(svipMonthlyTotal * 12),
monthlyDifference: Math.round(monthlyDifference),
yearlyDifference: Math.round(monthlyDifference * 12),
vipRate: Math.round(vipMonthlyTotal / vipConfig.price),
svipRate: Math.round(svipMonthlyTotal / vipConfig.svipPrice),
priceDifference,
recoverDays: Math.ceil(priceDifference / (monthlyDifference / 30)),
withdrawReward,
}
})
// 加载价格配置
onMounted(async () => {
document.documentElement.style.scrollBehavior = 'smooth'
// 从API获取会员配置信息
try {
const { data, error } = await useApiFetch('/agent/membership/info').get().json()
if (data.value && !error.value && data.value.code === 200) {
const configData = data.value.data
// 更新VIP配置
if (configData.vip_config) {
const vipData = configData.vip_config
vipConfig.price = vipData.price
vipConfig.vipCommission = vipData.report_commission // 直接显示,就是几元/单
vipConfig.vipFloatingRate = vipData.price_ratio * 100 // 转换为百分比
vipConfig.vipConversionBonus = vipData.lower_convert_vip_reward
vipConfig.vipWithdrawalLimit = vipData.exemption_amount
}
// 更新SVIP配置
if (configData.svip_config) {
const svipData = configData.svip_config
vipConfig.svipPrice = svipData.price
vipConfig.svipCommission = svipData.report_commission // 直接显示,就是几元/单
vipConfig.svipFloatingRate = svipData.price_ratio * 100 // 转换为百分比
vipConfig.withdrawRatio = svipData.lower_withdraw_reward_ratio * 100 // 转换为百分比
vipConfig.svipConversionBonus = svipData.lower_convert_svip_reward
vipConfig.svipWithdrawalLimit = svipData.exemption_amount
}
console.log('会员配置信息加载成功', configData)
} else {
console.error('获取会员配置信息失败', data.value?.msg || '未知错误')
}
} catch (error) {
console.error('加载会员配置信息失败', error)
}
})
const selectedType = ref('vip') // 默认选择VIP
const showPayment = ref(false)
const payData = ref({
product_name: `${selectedType.value.toUpperCase()}代理`,
sell_price: vipConfig.price,
})
const payID = ref('')
// 选择代理类型
function selectType(type) {
selectedType.value = type
// 更新payData中的价格和产品名称
payData.value = {
product_name: `${type === 'vip' ? 'VIP' : 'SVIP'}代理`,
sell_price: type === 'vip' ? vipConfig.price : vipConfig.svipPrice,
}
}
// 申请VIP或SVIP
async function applyVip() {
// 如果是VIP想升级到SVIP提示联系客服
if (isVip.value && selectedType.value === 'svip') {
contactService()
return
}
// 如果是SVIP要降级到VIP提示不能降级
if (isSvip.value && selectedType.value === 'vip') {
showToast('SVIP会员不能降级到VIP会员')
return
}
const { data, error } = await useApiFetch('/agent/membership/activate')
.post({
type: selectedType.value.toUpperCase(),
})
.json()
if (data.value && !error.value) {
if (data.value.code === 200) {
if (data.value.data.id) {
payID.value = data.value.data.id
showPayment.value = true
}
}
}
}
// 联系客服
function contactService() {
// 跳转到客服页面
const url = import.meta.env.VITE_CUSTOMER_SERVICE_URL;
if (!url) throw new Error("缺少环境变量: VITE_CUSTOMER_SERVICE_URL");
window.location.href = url;
}
function formatExpiryTime(expiryTimeStr) {
if (!expiryTimeStr) return '未知'
// 从格式化字符串中提取日期部分
return expiryTimeStr.split(' ')[0] // 假设格式是 "YYYY-MM-DD HH:MM:SS"
}
</script>
<style scoped>
.agent-VIP-apply {
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
sans-serif;
}
@keyframes shimmer {
0% {
transform: translateX(-100%) skewX(-30deg);
}
100% {
transform: translateX(200%) skewX(-30deg);
}
}
.animate-shimmer {
animation: shimmer 3s infinite;
}
.active\:scale-98:active {
transform: scale(0.98);
}
</style>

View File

@@ -0,0 +1,538 @@
<template>
<div class="p-4 max-w-3xl mx-auto min-h-screen">
<!-- 标题部分 -->
<div class="card mb-4 p-4 bg-gradient-to-r from-blue-500 to-blue-600 rounded-lg shadow-lg text-white">
<h1 class="text-2xl font-extrabold mb-2">专业报告定价配置</h1>
<p class="opacity-90">
请选择报告类型并设置定价策略助您实现精准定价
</p>
</div>
<div class="mb-4">
<van-field readonly clickable name="reportType" v-model="selectedReportText" label="报告类型"
placeholder="点击选择报告" @click="showPicker = true" class="card">
<template #label>
<span class="text-blue-600 font-medium">📝 选择报告</span>
</template>
<template #right-icon>
<van-icon name="arrow-down" class="text-gray-400" />
</template>
</van-field>
<van-popup v-model:show="showPicker" position="bottom">
<van-picker :columns="reportOptions" :default-index="0" @confirm="onConfirm"
@cancel="showPicker = false" />
</van-popup>
</div>
<div v-if="selectedReportText" class="space-y-6">
<!-- 配置卡片 -->
<div class="card">
<!-- 当前报告标题 -->
<div class="flex items-center mb-6">
<van-icon name="description" class="text-blue-500 text-xl mr-2" />
<h2 class="text-xl font-semibold text-gray-800">
{{ selectedReportText }}配置
</h2>
</div>
<!-- 显示当前产品的基础成本信息 -->
<div v-if="productConfigData && productConfigData.cost_price"
class="px-4 py-2 mb-4 bg-gray-50 border border-gray-200 rounded-lg shadow-sm">
<div class="text-lg font-semibold text-gray-700">
报告基础配置信息
</div>
<div class="mt-1 text-sm text-gray-600">
<div>
基础成本价<span class="font-medium">{{
productConfigData.cost_price
}}</span>
</div>
<!-- <div>区间起始价<span class="font-medium">{{ productConfigData.price_range_min }}</span> </div> -->
<div>
最高设定金额上限<span class="font-medium">{{
productConfigData.price_range_max
}}</span>
</div>
<div>
最高设定比例上限<span class="font-medium">{{
priceRatioMax
}}</span>
%
</div>
</div>
</div>
<!-- 分隔线 -->
<van-divider :style="{ borderColor: '#e5e7eb', padding: '0 16px' }" class="my-6">
<van-icon name="exchange" class="text-gray-400 mx-2" />
<span class="text-gray-400 text-sm">成本策略配置</span>
</van-divider>
<!-- 加价金额 -->
<van-field v-model.number="configData.price_increase_amount" label="加价金额" type="number" placeholder="0"
@blur="validateDecimal('price_increase_amount')" class="custom-field"
:class="{ 'van-field--error': increaseError }">
<template #label>
<span class="text-gray-600 font-medium">🚀 加价金额</span>
</template>
<template #extra>
<span class="text-blue-500 font-medium ml-2"></span>
</template>
</van-field>
<div class="text-xs text-gray-400 mt-1">
提示最大加价金额为{{ priceIncreaseAmountMax }}<br />
说明加价金额是在基础成本价上增加的额外费用决定下级报告的最低定价您将获得所有输入的金额利润
</div>
<!-- 分隔线 -->
<van-divider :style="{ borderColor: '#e5e7eb', padding: '0 16px' }" class="my-6">
<van-icon name="exchange" class="text-gray-400 mx-2" />
<span class="text-gray-400 text-sm">定价策略配置</span>
</van-divider>
<!-- 定价区间最低 -->
<van-field v-model.number="configData.price_range_from" label="定价区间最低" type="number" placeholder="0"
@blur="
() => {
validateDecimal('price_range_from');
validateRange();
}
" class="custom-field" :class="{ 'van-field--error': rangeError }">
<template #label>
<span class="text-gray-600 font-medium">💰 最低金额</span>
</template>
<template #extra>
<span class="text-blue-500 font-medium ml-2"></span>
</template>
</van-field>
<div class="text-xs text-gray-400 mt-1">
提示最低金额不能低于基础最低
{{ productConfigData?.price_range_min || 0 }} +
加价金额<br />
说明设定的最低金额为定价区间的起始值若下级设定的报告金额在区间内则区间内部分将按比例获得收益
</div>
<!-- 定价区间最高 -->
<van-field v-model.number="configData.price_range_to" label="定价区间最高" type="number" placeholder="0"
@blur="
() => {
validateDecimal('price_range_to');
validateRange();
}
" class="custom-field" :class="{ 'van-field--error': rangeError }">
<template #label>
<span class="text-gray-600 font-medium">💰 最高金额</span>
</template>
<template #extra>
<span class="text-blue-500 font-medium ml-2"></span>
</template>
</van-field>
<div class="text-xs text-gray-400 mt-1">
提示最高金额不能超过上限{{
productConfigData?.price_range_max || 0
}}和大于最低金额{{ priceIncreaseMax }}<br />
说明设定的最高金额为定价区间的结束值若下级设定的报告金额在区间内则区间内部分将按比例获得收益
</div>
<!-- 收取比例 -->
<van-field v-model.number="configData.price_ratio" label="收取比例" type="digit" placeholder="0" @blur="
() => {
validateRatio();
}
" class="custom-field" :class="{ 'van-field--error': ratioError }">
<template #label>
<span class="text-gray-600 font-medium">📈 收取比例</span>
</template>
<template #extra>
<span class="text-blue-500 font-medium ml-2">%</span>
</template>
</van-field>
<div class="text-xs text-gray-400 mt-1">
提示最大收取比例为{{ priceRatioMax }}%<br />
说明收取比例表示对定价区间内即报告金额超过最低金额小于最高金额的部分的金额按此比例进行利润分成
</div>
</div>
<!-- 保存按钮 -->
<van-button type="primary" block
class="shadow-lg bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white rounded-xl h-12"
@click="handleSubmit">
<van-icon name="success" class="mr-2" />
保存当前报告配置
</van-button>
</div>
<!-- 未选择提示 -->
<div v-else class="text-center py-12">
<van-icon name="warning" class="text-gray-400 text-4xl mb-4" />
<p class="text-gray-500">请先选择需要配置的报告类型</p>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from "vue";
import { showToast } from "vant";
import { settings } from "nprogress";
// 报告类型选项
const reportOptions = [
{ text: "小微企业", value: "companyinfo", id: 2 },
{ text: "贷前风险", value: "preloanbackgroundcheck", id: 5 },
{ text: "个人大数据", value: "personaldata", id: 27 },
{ text: "入职风险", value: "backgroundcheck", id: 1 },
{ text: "家政风险", value: "homeservice", id: 3 },
{ text: "婚恋风险", value: "marriage", id: 4 },
// { text: "租赁风险", value: "rentalrisk", id: 6 },
// { text: "个人风险", value: "riskassessment", id: 7 },
];
// 状态管理
const showPicker = ref(false);
const selectedReport = ref(reportOptions[0]);
const selectedReportText = ref(reportOptions[0].text);
const selectedReportId = ref(reportOptions[0].id);
const configData = ref({});
const productConfigData = ref({});
const priceIncreaseMax = ref(null);
const priceIncreaseAmountMax = ref(null);
const priceRatioMax = ref(null);
const rangeError = ref(false);
const ratioError = ref(false);
const increaseError = ref(false);
// 金额输入格式验证:确保最多两位小数
const validateDecimal = (field) => {
const value = configData.value[field];
if (value === null || value === undefined) return;
const numValue = Number(value);
if (isNaN(numValue)) {
configData.value[field] = null;
return;
}
const fixedValue = parseFloat(numValue.toFixed(2));
configData.value[field] = fixedValue;
if (field === "price_increase_amount") {
if (fixedValue > priceIncreaseAmountMax.value) {
configData.value[field] = priceIncreaseAmountMax.value;
showToast(`加价金额最大为${priceIncreaseAmountMax.value}`);
increaseError.value = true;
setTimeout(() => {
increaseError.value = false;
}, 2000);
} else {
increaseError.value = false;
}
// 当加价金额改变后,重新验证价格区间
validateRange();
}
};
// 价格区间验证(在 @blur 中调用)
const validateRange = () => {
console.log(
"configData.value.price_range_from",
configData.value.price_range_from
);
console.log(
"configData.value.price_range_to",
configData.value.price_range_to
);
if (
configData.value.price_range_from === null ||
configData.value.price_range_to === null
) {
rangeError.value = false;
return;
}
if (
isNaN(configData.value.price_range_from) ||
isNaN(configData.value.price_range_to)
)
return;
const additional = configData.value.price_increase_amount || 0;
const minAllowed = parseFloat(
(
Number(productConfigData.value.cost_price) + Number(additional)
).toFixed(2)
); // 使用成本价作为最小值
const maxAllowed = productConfigData.value.price_range_max; // 使用产品配置中的最大价格作为最大值
if (configData.value.price_range_from < minAllowed) {
configData.value.price_range_from = minAllowed;
showToast(`最低金额不能低于成本价 ${minAllowed}`);
rangeError.value = true;
closeRangeError();
configData.value.price_range_to = parseFloat(
(
Number(configData.value.price_range_from) +
Number(priceIncreaseMax.value)
).toFixed(2)
);
return;
}
if (configData.value.price_range_to < configData.value.price_range_from) {
showToast("最高金额不能低于最低金额");
if (
configData.value.price_range_from + priceIncreaseMax.value >
maxAllowed
) {
configData.value.price_range_to = maxAllowed;
} else {
configData.value.price_range_to =
configData.value.price_range_from + priceIncreaseMax.value;
}
rangeError.value = true;
closeRangeError();
return;
}
const diff = parseFloat(
(
configData.value.price_range_to - configData.value.price_range_from
).toFixed(2)
);
if (diff > priceIncreaseMax.value) {
showToast(`价格区间最大差值为${priceIncreaseMax.value}`);
configData.value.price_range_to =
configData.value.price_range_from + priceIncreaseMax.value;
closeRangeError();
return;
}
if (configData.value.price_range_to > maxAllowed) {
configData.value.price_range_to = maxAllowed;
showToast(`最高金额不能超过 ${maxAllowed}`);
closeRangeError();
}
if (!rangeError.value) {
rangeError.value = false;
}
};
// 收取比例验证(修改为保留两位小数,不再四舍五入取整)
const validateRatio = () => {
let value = configData.value.price_ratio;
if (value === null || value === undefined) return;
const numValue = Number(value);
if (isNaN(numValue)) {
configData.value.price_ratio = null;
ratioError.value = true;
return;
}
if (numValue > priceRatioMax.value) {
configData.value.price_ratio = priceRatioMax.value;
showToast(`收取比例最大为${priceRatioMax.value}%`);
ratioError.value = true;
setTimeout(() => {
ratioError.value = false;
}, 1000);
} else if (numValue < 0) {
configData.value.price_ratio = 0;
ratioError.value = true;
} else {
configData.value.price_ratio = parseFloat(numValue.toFixed(2));
ratioError.value = false;
}
};
// 获取配置
const getConfig = async () => {
try {
const { data, error } = await useApiFetch(
"/agent/membership/user_config?product_id=" + selectedReportId.value
)
.get()
.json();
if (data.value?.code === 200) {
const respConfigData = data.value.data.agent_membership_user_config;
configData.value = {
id: respConfigData.product_id,
price_range_from: respConfigData.price_range_from || null,
price_range_to: respConfigData.price_range_to || null,
price_ratio: respConfigData.price_ratio * 100 || null, // 转换为百分比
price_increase_amount:
respConfigData.price_increase_amount || null,
};
console.log("configData", configData.value);
// const respProductConfigData = data.value.data.product_config
productConfigData.value = data.value.data.product_config;
// 设置动态限制值
priceIncreaseMax.value = data.value.data.price_increase_max;
priceIncreaseAmountMax.value =
data.value.data.price_increase_amount;
priceRatioMax.value = data.value.data.price_ratio * 100;
}
} catch (error) {
showToast("配置加载失败");
}
};
// 提交处理
const handleSubmit = async () => {
try {
if (!finalValidation()) {
return;
}
// 前端数据转换
const submitData = {
product_id: configData.value.id,
price_range_from: configData.value.price_range_from || 0,
price_range_to: configData.value.price_range_to || 0,
price_ratio: (configData.value.price_ratio || 0) / 100, // 转换为小数
price_increase_amount: configData.value.price_increase_amount || 0,
};
console.log("submitData", submitData);
const { data, error } = await useApiFetch(
"/agent/membership/save_user_config"
)
.post(submitData)
.json();
if (data.value?.code === 200) {
setTimeout(() => {
showToast("保存成功");
}, 500);
getConfig();
}
} catch (error) {
showToast("保存失败,请稍后重试");
}
};
// 最终验证函数
const finalValidation = () => {
// 校验最低金额不能为空且大于0
if (
!configData.value.price_range_from ||
configData.value.price_range_from <= 0
) {
showToast("最低金额不能为空");
return false;
}
// 校验最高金额不能为空且大于0
if (
!configData.value.price_range_to ||
configData.value.price_range_to <= 0
) {
showToast("最高金额不能为空");
return false;
}
// 校验收取比例不能为空且大于0
if (!configData.value.price_ratio || configData.value.price_ratio <= 0) {
showToast("收取比例不能为空");
return false;
}
// 验证最低金额必须小于最高金额
if (configData.value.price_range_from >= configData.value.price_range_to) {
showToast("最低金额必须小于最高金额");
return false;
}
// 验证价格区间差值不能超过最大允许差值
const finalDiff = parseFloat(
(
configData.value.price_range_to - configData.value.price_range_from
).toFixed(2)
);
if (finalDiff > priceIncreaseMax.value) {
showToast(`价格区间最大差值为${priceIncreaseMax.value}`);
return false;
}
// 验证最高金额不能超过产品配置中设定的上限
if (
configData.value.price_range_to >
productConfigData.value.price_range_max
) {
showToast(
`最高金额不能超过${productConfigData.value.price_range_max}`
);
return false;
}
// 验证最低金额不能低于成本价+加价金额(加价金额允许为空)
const additional = configData.value.price_increase_amount || 0;
if (
configData.value.price_range_from <
productConfigData.value.cost_price + additional
) {
showToast(
`最低金额不能低于成本价${productConfigData.value.cost_price + additional
}`
);
return false;
}
return true;
};
// 选择器确认
const onConfirm = ({ selectedOptions }) => {
selectedReport.value = selectedOptions[0];
selectedReportText.value = selectedOptions[0].text;
selectedReportId.value = selectedOptions[0].id;
showPicker.value = false;
// 重置错误状态
rangeError.value = false;
ratioError.value = false;
increaseError.value = false;
getConfig();
};
const closeRangeError = () => {
setTimeout(() => {
rangeError.value = false;
}, 2000);
};
onMounted(() => {
getConfig();
});
</script>
<style>
.custom-field {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.custom-field .van-field__body {
@apply bg-gray-50 rounded-lg px-3 py-2;
transition: all 0.3s ease;
}
.custom-field:focus-within .van-field__body {
@apply ring-2 ring-blue-200;
}
.van-picker__toolbar {
@apply bg-gray-50 rounded-t-lg;
}
.van-picker__confirm {
@apply text-blue-500 font-medium;
}
.van-divider {
@apply before:bg-gray-100 after:bg-gray-100;
}
/* 错误状态样式 */
.van-field--error .van-field__control {
color: #ee0a24;
}
.van-field--error .van-field__label {
color: inherit;
}
.van-field--error .van-field__body {
@apply ring-2 ring-red-200 bg-red-50;
}
</style>

150
src/views/Authorization.vue Normal file
View File

@@ -0,0 +1,150 @@
<template>
<!-- 授权书滚动区域 -->
<div class="card flex-1 overflow-y-auto text-sm leading-relaxed text-gray-800" ref="agreementBox" @scroll="handleScroll">
<h1 class="mb-3 text-center text-base font-semibold text-gray-900">授权书</h1>
<p class="my-2">{{ COMPANY_NAME }}</p>
<p class="indent-[2em] text-justify">
本人 姓名 身份证号码拟向贵司申请业务贵司需要了解本人相关状况用于查询大数据分析报告因此本人特同意并不可撤销的授权
</p>
<ol class="mt-3 list-none space-y-3 pl-0">
<li class="text-justify">
贵司向依法成立的第三方服务商包括但不限于天津津北数字产业发展集团有限公司根据本人提交的信息进行核实并有权通过前述第三方服务机构查询使用本人的身份信息电话号码等查询本人信息包括但不限于学历婚姻资产状况及对信息主体产生负面影响的不良信息出具相关报告
</li>
<li class="text-justify">
第三方服务商应当在上述处理目的处理方式和个人信息的种类等范围内处理个人信息变更原先的处理目的处理方式的应当依法重新取得您的同意
</li>
</ol>
<p class="mt-4 text-justify">
本人在此声明已充分理解上述授权条款含义知晓并自愿承担上述因收集等本人数据可能会给本人的生活行为评分结果产生不利影响以及该等数据被使用者依法提供给第三方后被他人不当利用的风险但本人仍同意上述授权
</p>
<p class="mt-4 font-semibold text-gray-900">特别提示</p>
<p class="mt-2 text-justify">
为了保障您的合法权益请您务必阅读并充分理解与遵守本授权书若您不接受本授权书的任何条款请您立即终止授权贵司已经对上述事宜及其风险向本人做了充分说明本人已知晓并同意
</p>
<p class="mt-2 text-justify">
你通过{{ APP_NAME }}APP或代理商推广查询模式自愿支付相应费用用于购买{{ COMPANY_NAME }}的大数据报告产品
</p>
<p class="mt-2 text-justify">
你向{{ COMPANY_NAME }}的支付方式为{{ COMPANY_NAME }}及其关联公司的支付宝及微信账户
</p>
<p class="mt-2 text-justify">
本授权书一经本人在网上点击勾选同意即完成签署本授权书是本人真实意思表示本人同意承担由此带来的一切法律后果
</p>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import { useFetch } from "@vueuse/core";
import { useWebView } from "@/composables/useWebView";
const { postMessage } = useWebView();
const COMPANY_NAME = import.meta.env.VITE_COMPANY_NAME;
if (!COMPANY_NAME) throw new Error("缺少环境变量: VITE_COMPANY_NAME");
const APP_NAME = import.meta.env.VITE_APP_NAME;
if (!APP_NAME) throw new Error("缺少环境变量: VITE_APP_NAME");
const userData = ref({
name: "",
idCard: "",
phone: "",
});
const signature = ref(false);
const agreed = ref(false);
const id = ref(null);
const token = ref(null);
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>

46
src/views/Example.vue Normal file
View File

@@ -0,0 +1,46 @@
<template>
<BaseReport :feature="feature" :reportData="reportData" :reportParams="reportParams" :reportName="reportName"
:reportDateTime="reportDateTime" :isEmpty="isEmpty" :isDone="isDone" isExample />
</template>
<script setup>
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) {
reportData.value = data.value.data.query_data.sort((a, b) => {
return a.feature.sort - b.feature.sort;
});
reportParams.value = data.value.data.query_params;
reportName.value = data.value.data.product_name;
reportDateTime.value = data.value.data.create_time;
} else if (data.value.code === 200003) {
isEmpty.value = true;
}
isDone.value = true;
}
};
</script>
<style lang="scss" scoped></style>

115
src/views/Help.vue Normal file
View File

@@ -0,0 +1,115 @@
<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: [
{ id: 'vip_guide', title: '如何成为VIP代理和SVIP代理?' }
]
}
]
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>

80
src/views/HelpDetail.vue Normal file
View File

@@ -0,0 +1,80 @@
<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',
vip_guide: '/image/help/vip-guide.jpg'
}
// 标题映射
const titleMap = {
report_calculation: '推广报告的收益是如何计算的?',
report_efficiency: '报告推广效率飙升指南',
report_cost: '推广报告的成本是如何计算的?',
report_types: '赤眉有哪些大数据报告类型',
report_push: '如何推广报告',
report_secret: '报告推广秘籍大公开',
invite_earnings: '如何邀请下级成为代理',
direct_earnings: '如何成为赤眉代理',
vip_guide: '如何成为VIP代理和SVIP代理?'
}
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
View 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>

128
src/views/HistoryQuery.vue Normal file
View File

@@ -0,0 +1,128 @@
<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 num = ref(0)
const max = ref(60)
const loading = ref(false)
const finished = ref(false)
// 初始加载数据
async function fetchData() {
loading.value = true
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
}
}
}
loading.value = false
}
// 初始加载
onMounted(() => {
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 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>
</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>

51
src/views/Inquire.vue Normal file
View File

@@ -0,0 +1,51 @@
<script setup>
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
import InquireForm from "@/components/InquireForm.vue";
const route = useRoute();
const feature = ref(route.params.feature);
// 获取产品信息
const featureData = ref({});
onMounted(async () => {
isFinishPayment();
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>
<InquireForm :type="'normal'" :feature="feature" :feature-data="featureData" />
</template>

46
src/views/Invitation.vue Normal file
View File

@@ -0,0 +1,46 @@
<template>
<div>
<img src="@/assets/images/invitation.png" alt="邀请下级" />
<div
@click="showQRcode = true"
class="bg-gradient-to-t from-orange-500 to-orange-300 fixed bottom-0 h-12 w-full bg-orange-400 shadow-xl text-white rounded-t-xl flex items-center justify-center font-bold"
>
立即邀请好友
</div>
</div>
<QRcode
v-model:show="showQRcode"
mode="invitation"
:linkIdentifier="linkIdentifier"
/>
</template>
<script setup>
import { storeToRefs } from "pinia";
import { aesEncrypt } from "@/utils/crypto";
import { useAgentStore } from "@/stores/agentStore";
const agentStore = useAgentStore();
const { mobile, agentID } = storeToRefs(agentStore); // 响应式解构
const showQRcode = ref(false);
const linkIdentifier = ref("");
onBeforeMount(() => {
encryptIdentifire(agentID.value, mobile.value);
});
const encryptIdentifire = (agentID, mobile) => {
const channelKey = import.meta.env.VITE_INVITE_CHANNEL_KEY;
if (!channelKey) throw new Error("缺少环境变量: VITE_INVITE_CHANNEL_KEY");
const linkIdentifierJSON = {
agentID,
mobile,
};
const linkIdentifierStr = JSON.stringify(linkIdentifierJSON);
const encodeData = aesEncrypt(
linkIdentifierStr,
channelKey
);
linkIdentifier.value = encodeURIComponent(encodeData);
};
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,170 @@
<template>
<div class="min-h-screen bg-[#D1D6FF]">
<img src="@/assets/images/invitation_agent_apply.png" alt="邀请代理申请" />
<!-- 统一状态处理容器 -->
<div class="flex flex-col items-center justify-centerx">
<!-- 审核中状态 -->
<div v-if="displayStatus === 0" class="text-center">
<span class="text-xs text-gray-500">您的申请正在审核中</span>
<div class="bg-gray-200 p-1 rounded-3xl shadow-xl mt-1">
<div
class="text-xl font-bold px-8 py-2 bg-gray-400 text-white rounded-3xl shadow-lg cursor-not-allowed">
审核进行中
</div>
</div>
</div>
<!-- 审核通过状态 -->
<div v-if="displayStatus === 1" 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-if="displayStatus === 2" class="text-center">
<span class="text-xs text-red-500">审核未通过请重新提交</span>
<div class="bg-red-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-red-500 to-red-300 text-white rounded-3xl shadow-lg cursor-pointer">
重新提交申请
</div>
</div>
</div>
<!-- 未申请状态包含邀请状态 -->
<div v-if="displayStatus === 3" 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"
:ancestor="ancestor" :is-self="isSelf" :user-name="userName" />
</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 { status } = storeToRefs(store); // 响应式解构
const ancestor = ref("");
const isSelf = ref(false);
const agentApply = () => {
showApplyPopup.value = true;
};
// 计算显示状态当isSelf为false时强制显示为3
const displayStatus = computed(() => {
// return isSelf.value ? status.value : 3;
return status.value;
});
// 跳转到首页
const goToHome = () => {
clearInterval(intervalId);
router.push("/");
};
onBeforeMount(() => {
const channelKey = import.meta.env.VITE_INVITE_CHANNEL_KEY;
if (!channelKey) throw new Error("缺少环境变量: VITE_INVITE_CHANNEL_KEY");
if (route.name === "invitationAgentApplySelf") {
isSelf.value = true;
} else {
const linkIdentifier = route.params.linkIdentifier;
const decryptDataStr = aesDecrypt(
decodeURIComponent(linkIdentifier),
channelKey
);
const decryptData = JSON.parse(decryptDataStr);
ancestor.value = decryptData.mobile;
}
const token = localStorage.getItem("token");
if (token) {
store.fetchAgentStatus();
}
});
const submitApplication = async (formData) => {
// 提交代理申请的数据
const { region, mobile, wechat_id, code } = formData;
let postData = {
region,
mobile,
wechat_id,
code,
};
if (!isSelf.value) {
postData.ancestor = ancestor.value;
}
const { data, error } = await useApiFetch("/agent/apply")
.post(postData)
.json();
if (data.value && !error.value) {
if (data.value.code === 200) {
showApplyPopup.value = false;
showToast({ message: "已提交申请" });
// refreshAgentStatus()
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
);
refreshAgentStatus();
}
} else {
console.log("申请失败", data.value);
}
}
};
let intervalId = null; // 保存定时器 ID
const refreshAgentStatus = () => {
// 当 status.value 变化时(如通过监听或事件)
if (status.value === 3) {
// 清除已有定时器(避免重复创建)
if (intervalId) clearInterval(intervalId);
intervalId = setInterval(() => {
// 每次执行前检查状态是否仍为 3
if (status.value !== 3) {
clearInterval(intervalId);
intervalId = null;
return;
}
store.fetchAgentStatus();
}, 2000);
} else {
// 状态不是 3 时主动清除定时器
if (intervalId) {
clearInterval(intervalId);
intervalId = null;
}
}
};
</script>
<style lang="scss" scoped></style>

336
src/views/Login.vue Normal file
View File

@@ -0,0 +1,336 @@
<script setup>
import { ref, computed, onUnmounted, nextTick } from 'vue'
import { showToast } from 'vant'
import { useAliyunCaptcha } from "@/composables/useAliyunCaptcha";
const router = useRouter()
const phoneNumber = ref('')
const verificationCode = ref('')
const password = ref('')
const isPasswordLogin = ref(false)
const isAgreed = ref(false)
const isCountingDown = ref(false)
const countdown = ref(60)
let timer = null
const { runWithCaptcha } = useAliyunCaptcha();
// 聚焦状态变量
const phoneFocused = ref(false)
const codeFocused = ref(false)
const passwordFocused = ref(false)
const isPhoneNumberValid = computed(() => {
return /^1[3-9]\d{9}$/.test(phoneNumber.value)
})
const canLogin = computed(() => {
if (!isPhoneNumberValid.value) return false
if (isPasswordLogin.value) {
return password.value.length >= 6
} else {
return verificationCode.value.length === 6
}
})
async function sendVerificationCode() {
if (isCountingDown.value || !isPhoneNumberValid.value) return
if (!isPhoneNumberValid.value) {
showToast({ message: "请输入有效的手机号" });
return
}
await runWithCaptcha(
(captchaVerifyParam) =>
useApiFetch('auth/sendSms')
.post({ mobile: phoneNumber.value, actionType: 'login', captchaVerifyParam })
.json(),
(res) => {
if (res.code === 200) {
showToast({ message: "获取成功" });
startCountdown();
nextTick(() => {
const verificationCodeInput = document.getElementById('verificationCode');
if (verificationCodeInput) {
verificationCodeInput.focus();
}
});
} else {
showToast({ message: res.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 (isPasswordLogin.value) {
if (password.value.length < 6) {
showToast({ message: "密码长度不能小于6位" });
return
}
} else {
if (verificationCode.value.length !== 6) {
showToast({ message: "请输入有效的验证码" });
return
}
}
if (!isAgreed.value) {
showToast({ message: "请先同意用户协议" });
return
}
performLogin()
}
// 执行实际的登录逻辑
async function performLogin() {
const { data, error } = await useApiFetch('/user/mobileCodeLogin')
.post({ mobile: phoneNumber.value, code: verificationCode.value })
.json()
if (data.value && !error.value) {
if (data.value.code === 200) {
localStorage.setItem('token', data.value.data.accessToken)
localStorage.setItem('refreshAfter', data.value.data.refreshAfter)
localStorage.setItem('accessExpire', data.value.data.accessExpire)
window.location.href = '/'
}
}
}
function toUserAgreement() {
router.push(`/userAgreement`)
}
function toPrivacyPolicy() {
router.push(`/privacyPolicy`)
}
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="/logo.png" alt="Logo" />
<div class="text-3xl mt-4 text-slate-700 font-bold">赤眉</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>
</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 {}
/* 登录表单 */
.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;
}
/* 协议同意 */
.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;
}
</style>

82
src/views/Maintenance.vue Normal file
View File

@@ -0,0 +1,82 @@
<template>
<div class="maintenance-container">
<div class="notice-card">
<h2 class="notice-title">服务暂停通知</h2>
<div class="notice-content">
<p>因政策要求接上级主管部门通知我司现暂停婚姻报告服务恢复时间估计在元旦以后由此给您带来的不便我们深表歉意</p>
</div>
<div class="notice-footer">
<p>感谢您的理解与支持</p>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Maintenance',
data() {
return {
// 可以在这里添加需要的数据
}
},
methods: {
// 可以在这里添加需要的方法
}
}
</script>
<style scoped>
.maintenance-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f5f7fa;
padding: 20px;
}
.notice-card {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
padding: 40px;
max-width: 600px;
width: 100%;
text-align: center;
}
.notice-title {
font-size: 24px;
color: #303133;
margin-bottom: 20px;
font-weight: bold;
}
.notice-content {
font-size: 16px;
color: #606266;
line-height: 1.6;
margin-bottom: 30px;
}
.notice-footer {
font-size: 14px;
color: #909399;
margin-top: 20px;
}
@media (max-width: 768px) {
.notice-card {
padding: 20px;
}
.notice-title {
font-size: 20px;
}
.notice-content {
font-size: 14px;
}
}
</style>

321
src/views/Me.vue Normal file
View File

@@ -0,0 +1,321 @@
<template>
<div class="box-border min-h-screen">
<div class="flex flex-col p-4 space-y-6">
<!-- 用户信息卡片 -->
<div class="profile-section group relative flex items-center gap-4 rounded-xl bg-white p-6 transition-all hover:shadow-xl"
@click="!isLoggedIn ? redirectToLogin() : null">
<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-24 w-24 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">
{{ levelNames[level] }}
</div>
</div>
</div>
<div class="space-y-1">
<h2 class="text-2xl font-bold" style="color: var(--van-text-color);">
{{
!isLoggedIn
? "点击登录"
: mobile
? maskName(mobile)
: isWeChat
? "微信用户"
: "未绑定手机号"
}}
</h2>
<!-- 手机号绑定提示 -->
<template v-if="isLoggedIn && !mobile">
<p @click.stop="showBindPhoneDialog" class="text-sm cursor-pointer hover:underline"
style="color: var(--van-theme-primary);">
点击绑定手机号码
</p>
</template>
<p v-if="isAgent" class="text-sm font-medium" :class="levelGradient.text">
🎖 {{ levelText[level] }}
</p>
</div>
</div>
<VipBanner v-if="isAgent && (level === 'normal' || level === '')" />
<!-- 功能菜单 -->
<div class="">
<div class="bg-white rounded-xl shadow-sm overflow-hidden">
<template v-if="isAgent && ['VIP', 'SVIP'].includes(level)">
<button
class="w-full flex items-center justify-between px-6 py-4 hover:bg-purple-50 transition-colors border-b border-gray-100"
@click="toVipConfig">
<div class="flex items-center gap-3">
<img src="@/assets/images/me/dlbgpz.png" class="w-6 h-6" alt="代理报告配置" />
<span class="text-purple-700 font-medium">代理报告配置</span>
</div>
<img src="@/assets/images/me/right.png" class="w-4 h-4" alt="右箭头" />
</button>
<button
class="w-full flex items-center justify-between px-6 py-4 hover:bg-amber-50 transition-colors border-b border-gray-100"
@click="toVipRenewal">
<div class="flex items-center gap-3">
<img src="@/assets/images/me/xfhy.png" class="w-6 h-6" alt="代理会员" />
<div class="flex flex-col items-start">
<span class="text-amber-700 font-medium">续费代理会员</span>
<span class="text-xs text-gray-500">有效期至 {{ formatExpiryTime(ExpiryTime) }}</span>
</div>
</div>
<img src="@/assets/images/me/right.png" class="w-4 h-4" alt="右箭头" />
</button>
</template>
<button
class="w-full flex items-center justify-between px-6 py-4 hover:bg-blue-50 transition-colors border-b border-gray-100"
@click="toPromote">
<div class="flex items-center gap-3">
<img src="@/assets/images/index/tgbg.png" class="w-6 h-6" alt="推广报告" />
<span class="text-gray-700 font-medium">推广报告</span>
</div>
<img src="@/assets/images/me/right.png" class="w-4 h-4" alt="右箭头" />
</button>
<button
class="w-full flex items-center justify-between px-6 py-4 hover:bg-blue-50 transition-colors border-b border-gray-100"
@click="toInvitation">
<div class="flex items-center gap-3">
<img src="@/assets/images/index/yqhy.png" class="w-6 h-6" alt="邀请下级" />
<span class="text-gray-700 font-medium">邀请下级</span>
</div>
<img src="@/assets/images/me/right.png" class="w-4 h-4" alt="右箭头" />
</button>
<button
class="w-full flex items-center justify-between px-6 py-4 hover:bg-blue-50 transition-colors border-b border-gray-100"
@click="toHistory">
<div class="flex items-center gap-3">
<img src="@/assets/images/index/wdbg.png" class="w-6 h-6" alt="我的报告" />
<span class="text-gray-700 font-medium">我的报告</span>
</div>
<img src="@/assets/images/me/right.png" class="w-4 h-4" alt="右箭头" />
</button>
<button
class="w-full flex items-center justify-between px-6 py-4 hover:bg-blue-50 transition-colors border-b border-gray-100"
@click="toUserAgreement">
<div class="flex items-center gap-3">
<img src="@/assets/images/me/yhxy.png" class="w-6 h-6" alt="用户协议" />
<span class="text-gray-700 font-medium">用户协议</span>
</div>
<img src="@/assets/images/me/right.png" class="w-4 h-4" alt="右箭头" />
</button>
<button
class="w-full flex items-center justify-between px-6 py-4 hover:bg-blue-50 transition-colors border-b border-gray-100"
@click="toPrivacyPolicy">
<div class="flex items-center gap-3">
<img src="@/assets/images/me/yszc.png" class="w-6 h-6" alt="隐私政策" />
<span class="text-gray-700 font-medium">隐私政策</span>
</div>
<img src="@/assets/images/me/right.png" class="w-4 h-4" alt="右箭头" />
</button>
<button
class="w-full flex items-center justify-between px-6 py-4 hover:bg-blue-50 transition-colors"
@click="toService">
<div class="flex items-center gap-3">
<img src="@/assets/images/me/lxkf.png" class="w-6 h-6" alt="联系客服" />
<span class="text-gray-700 font-medium">联系客服</span>
</div>
<img src="@/assets/images/me/right.png" class="w-4 h-4" alt="右箭头" />
</button>
<button v-if="isLoggedIn && !isWeChat"
class="w-full flex items-center justify-between px-6 py-4 hover:bg-red-50 transition-colors"
@click="handleLogout">
<div class="flex items-center gap-3">
<img src="@/assets/images/me/tcdl.png" class="w-6 h-6" alt="退出登录" />
<span class="text-gray-700 font-medium">退出登录</span>
</div>
<img src="@/assets/images/me/right.png" class="w-4 h-4" alt="右箭头" />
</button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { storeToRefs } from "pinia";
import { ref, computed, onBeforeMount } from "vue";
const router = useRouter();
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";
const agentStore = useAgentStore();
const userStore = useUserStore();
const dialogStore = useDialogStore();
const { isAgent, level, ExpiryTime } = storeToRefs(agentStore);
const { userName, userAvatar, isLoggedIn, mobile } = storeToRefs(userStore);
const { isWeChat } = useEnv();
const levelNames = {
normal: "普通代理",
"": "普通代理",
VIP: "VIP代理",
SVIP: "SVIP代理",
};
const levelText = {
normal: "基础代理特权",
"": "基础代理特权",
VIP: "高级代理特权",
SVIP: "尊享代理特权",
};
const levelGradient = computed(() => ({
border: {
normal: "bg-gradient-to-r from-gray-300 to-gray-400",
"": "bg-gradient-to-r from-gray-300 to-gray-400",
VIP: "bg-gradient-to-r from-yellow-400 to-amber-500",
SVIP: "bg-gradient-to-r from-purple-400 to-pink-400 shadow-[0_0_15px_rgba(163,51,200,0.2)]",
}[level.value],
badge: {
normal: "bg-gradient-to-r from-gray-500 to-gray-600",
"": "bg-gradient-to-r from-gray-500 to-gray-600",
VIP: "bg-gradient-to-r from-yellow-500 to-amber-600",
SVIP: "bg-gradient-to-r from-purple-500 to-pink-500",
}[level.value],
text: {
normal: "text-gray-600",
"": "text-gray-600",
VIP: "text-amber-600",
SVIP: "text-purple-600",
}[level.value],
}));
const maskName = (name) => {
if (!name || name.length < 11) return name;
return name.substring(0, 3) + "****" + name.substring(7);
};
function toHistory() {
router.push(`/historyQuery`);
}
function toPromote() {
router.push({ name: "promote" });
}
function toInvitation() {
router.push({ name: "invitation" });
}
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();
}
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);
});
}
}
function toService() {
const url = import.meta.env.VITE_CUSTOMER_SERVICE_URL;
if (!url) throw new Error("缺少环境变量: VITE_CUSTOMER_SERVICE_URL");
window.location.href = url;
}
const toVipConfig = () => {
router.push({ name: "agentVipConfig" });
};
const toVipRenewal = () => {
router.push(`/agent/vipApply`);
};
function formatExpiryTime(expiryTimeStr) {
if (!expiryTimeStr) return "未知";
// 假设expiryTimeStr格式是 "YYYY-MM-DD HH:MM:SS"
// 只返回日期部分 "YYYY-MM-DD"
return expiryTimeStr.split(" ")[0];
}
const getDefaultAvatar = () => {
if (!isAgent.value) return headShot;
switch (level.value) {
case "normal":
case "":
return "/image/shot_nonal.png";
case "VIP":
return "/image/shot_vip.png";
case "SVIP":
return "/image/shot_svip.png";
default:
return headShot;
}
};
const showBindPhoneDialog = () => {
dialogStore.openBindPhone();
};
onBeforeMount(() => {
// 获取存储的用户和代理信息
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);
}
}
});
</script>
<style scoped></style>

191
src/views/NotFound.vue Normal file
View File

@@ -0,0 +1,191 @@
<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(() => {
const origin = import.meta.env.VITE_SITE_ORIGIN;
if (!origin) throw new Error("缺少环境变量: VITE_SITE_ORIGIN");
const siteName = import.meta.env.VITE_SEO_SITE_NAME;
if (!siteName) throw new Error("缺少环境变量: VITE_SEO_SITE_NAME");
updateSEO({
title: `404 - 页面未找到 | ${siteName}`,
description: `抱歉,您访问的页面不存在。${siteName}专业大数据风险管控平台,提供大数据风险报告查询、婚姻状况查询、个人信用评估等服务。`,
keywords: `404, 页面未找到, ${siteName}, 大数据风险管控`,
url: `${origin}/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>

530
src/views/PaymentResult.vue Normal file
View File

@@ -0,0 +1,530 @@
<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_vip"
? "代理会员"
: "查询服务"
}}</span>
</div>
</div>
<div v-if="paymentType === 'agent_vip'" 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_vip"
? "查看会员权益"
: "查看查询结果"
}}
</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_vip"
? "代理会员"
: "查询服务"
}}</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_vip'" 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_vip"
? "代理会员"
: "查询服务"
}}</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_vip") {
// 跳转到代理会员页面
router.replace("/agent");
agentStore.fetchAgentStatus();
userStore.fetchUserInfo();
} else {
// 跳转到查询结果页面
router.replace({
path: "/report",
query: { orderNo: orderNo.value },
});
}
}
// 返回首页
function goHome() {
router.replace("/");
}
// 联系客服
function contactService() {
// 可以替换为实际的客服联系逻辑,如打开聊天窗口或跳转到客服页面
const url = import.meta.env.VITE_CUSTOMER_SERVICE_URL;
if (!url) throw new Error("缺少环境变量: VITE_CUSTOMER_SERVICE_URL");
window.location.href = url;
}
// 暴露方法和数据供父组件或路由调用
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>

674
src/views/PrivacyPolicy.vue Normal file
View File

@@ -0,0 +1,674 @@
<script setup>
import { ref, onMounted, nextTick } from "vue";
const rootRef = ref(null);
const COMPANY_NAME = import.meta.env.VITE_COMPANY_NAME;
if (!COMPANY_NAME) throw new Error("缺少环境变量: VITE_COMPANY_NAME");
const APP_NAME = import.meta.env.VITE_APP_NAME;
if (!APP_NAME) throw new Error("缺少环境变量: VITE_APP_NAME");
const CONTACT_EMAIL = import.meta.env.VITE_CONTACT_EMAIL;
if (!CONTACT_EMAIL) throw new Error("缺少环境变量: VITE_CONTACT_EMAIL");
// 将指定字符串替换到所有文本节点,便于大段协议文案统一 env 化
function replaceTextInNode(node, from, to) {
if (!node) return;
if (node.nodeType === Node.TEXT_NODE) {
if (node.textContent.includes(from)) {
node.textContent = node.textContent.split(from).join(to);
}
return;
}
if (node.nodeType !== Node.ELEMENT_NODE) return;
for (const child of Array.from(node.childNodes)) {
replaceTextInNode(child, from, to);
}
}
onMounted(async () => {
await nextTick();
// 全局替换协议里的企业名/应用名
replaceTextInNode(rootRef.value, "戎行技术有限公司", COMPANY_NAME);
replaceTextInNode(rootRef.value, "赤眉", APP_NAME);
replaceTextInNode(rootRef.value, "admin@iieeii.com", CONTACT_EMAIL);
});
</script>
<template>
<article ref="rootRef" class="privacy-policy p-4 text-sm leading-relaxed text-gray-800">
<h1 class="mb-4 text-center text-lg font-semibold text-gray-900">隐私政策</h1>
<p class="mb-1 text-gray-600">更新日期2026年03月26日</p>
<p class="mb-4 text-gray-600">生效日期2026年03月26日</p>
<p class="mb-3 text-justify">
您的信任对我们非常重要我们深知个人信息对您的重要性我们将按法律法规要求采取相应安全保护措施尽力保护您的个人信息安全可控有鉴于此戎行技术有限公司以下简称我们赤眉作为赤眉产品及服务的提供者制定本隐私政策下称本政策并提醒您
</p>
<p class="mb-3 text-justify">
需要特别说明的是本政策不适用于其他第三方通过网页或赤眉客户端直接向您提供的服务统称第三方服务您向该第三方服务提供者提供的信息不适用于本政策您在选择使用第三服务前应充分了解第三方服务的产品功能及隐私保护政策再选择是否开通功能
</p>
<p class="mb-6 text-justify">
在使用赤眉产品或服务前请您务必仔细阅读并透彻理解本政策在确认充分理解使用相关产品或服务一旦您开始使用赤眉产品或服务即表示您已充分理解并同意本政策
</p>
<h2 class="mb-3 mt-8 text-base font-semibold text-gray-900">第一部分定义</h2>
<ol class="mb-8 list-decimal space-y-2 pl-6 marker:font-normal">
<li>
<strong>赤眉服务提供者</strong>是指研发并提供赤眉产品和服务法律主体戎行技术有限公司下称我们赤眉
</li>
<li><strong>赤眉用户</strong>是指注册赤眉账户的用户以下称</li>
<li>
<strong>个人信息</strong>指以电子或者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息
</li>
<li>
<strong>个人信息删除</strong>指在实现日常业务功能所涉及的系统中去除个人信息的行为使其保持不可被检索访问的状态具体指产品内的账号注销功能
</li>
<li>
<strong>个人信息匿名化</strong>通过对个人信息的加密技术处理使得个人信息主体无法被识别且处理后的信息不能被复原的过程
</li>
</ol>
<h2 class="mb-4 text-base font-semibold text-gray-900">第二部分隐私政策</h2>
<h3 class="mb-3 text-[15px] font-semibold text-gray-900">我们如何收集您的个人信息</h3>
<p class="mb-4 text-justify">
为了向您及赤眉企业用户提供赤眉服务维护赤眉服务的正常运行改进及优化我们的服务体验并保障您的账号安全我们会出于本政策下述目的及方式收集您在注册使用赤眉服务时主动提供授权提供或基于您使用赤眉服务时产生的信息
</p>
<h4 class="mb-2 font-semibold text-gray-900">注册赤眉用户信息</h4>
<p class="mb-3 text-justify">
为注册成为赤眉用户以便我们为您提供赤眉服务诸如数据查询视频查看功能您需要提供您的手机号码及短信验证码以注册并创建赤眉账号否则您将不能使用赤眉服务
</p>
<p class="mb-3 text-justify">
如果您仅需使用浏览搜索赤眉网页展示的产品功能及服务介绍您不需要注册成为赤眉用户并提供上述信息
</p>
<p class="mb-3 text-justify">
如您的账号是注册在企业下的关联账号当您所在企业用户注销赤眉账户时我们将会匿名化处理或删除您在该组织的相关个人信息但您作为赤眉个人用户的个人信息仍将保留除非您主动注销赤眉账户
</p>
<p class="mb-6 text-justify">在经过用户授权同意的情况下我司需要获取用户的手机号码以便开展相应业务</p>
<h4 class="mb-2 font-semibold text-gray-900">使用赤眉服务过程中收集信息</h4>
<p class="mb-4 text-justify">
当您在使用赤眉服务过程中为向您提供您需求的赤眉软件服务交互展示搜索结果识别账号异常状态维护赤眉服务的正常运行改进及优化您对赤眉服务的体验并保障您的账号安全包括您使用赤眉服务以及使用方式的信息并将这些信息进行关联
</p>
<p class="mb-2 font-medium text-gray-900">个人信息收集清单</p>
<div class="mb-6 overflow-x-auto">
<table class="min-w-full border-collapse border border-gray-200 text-left text-xs">
<thead>
<tr class="bg-gray-50">
<th class="border border-gray-200 px-2 py-2 font-semibold">信息名称</th>
<th class="border border-gray-200 px-2 py-2 font-semibold">使用目的</th>
<th class="border border-gray-200 px-2 py-2 font-semibold">使用场景</th>
<th class="border border-gray-200 px-2 py-2 font-semibold">收集情况</th>
<th class="border border-gray-200 px-2 py-2 font-semibold">信息内容</th>
</tr>
</thead>
<tbody>
<tr>
<td class="border border-gray-200 px-2 py-2">真实姓名</td>
<td class="border border-gray-200 px-2 py-2">注册认证</td>
<td class="border border-gray-200 px-2 py-2">查询报告时</td>
<td class="border border-gray-200 px-2 py-2">1</td>
<td class="border border-gray-200 px-2 py-2">*****</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2">身份证号码</td>
<td class="border border-gray-200 px-2 py-2">注册认证</td>
<td class="border border-gray-200 px-2 py-2">查询报告时</td>
<td class="border border-gray-200 px-2 py-2">1</td>
<td class="border border-gray-200 px-2 py-2">1*****</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2">电话号码</td>
<td class="border border-gray-200 px-2 py-2">注册认证</td>
<td class="border border-gray-200 px-2 py-2">查询报告时</td>
<td class="border border-gray-200 px-2 py-2">1</td>
<td class="border border-gray-200 px-2 py-2">1*****</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2">身份证明照片</td>
<td class="border border-gray-200 px-2 py-2">异议申诉</td>
<td class="border border-gray-200 px-2 py-2">异议申诉时</td>
<td class="border border-gray-200 px-2 py-2">1</td>
<td class="border border-gray-200 px-2 py-2">***</td>
</tr>
</tbody>
</table>
</div>
<section class="mb-6">
<h4 class="mb-2 font-semibold text-gray-900">1剪切板信息</h4>
<ul class="list-disc space-y-1 pl-6">
<li>个人中心---商务合作---复制邮箱地址方便商务对接</li>
<li>分享报告--复制报告链接</li>
<li>报告推广--复制推广链接</li>
<li>活动模块---邀请好友拼单---复制拼单链接</li>
</ul>
</section>
<section class="mb-6">
<h4 class="mb-2 font-semibold text-gray-900">2日志信息</h4>
<p class="mb-3 text-justify">
当您使用我们的网站或客户端提供的产品或服务时我们会自动收集您对我们服务的详细使用情况作为有关网络日志保存
</p>
<p class="text-justify">
请注意单独的设备信息日志信息是无法识别特定自然人身份的信息如果我们将这类非个人信息与其他信息结合用于识别特定自然人身份或者将其与个人信息结合使用则在结合使用期间这类非个人信息将有可能被视为个人信息除取得您授权或法律法规另有规定外我们会将该类个人信息做匿名化去标识化处理
</p>
</section>
<section class="mb-6">
<h4 class="mb-2 font-semibold text-gray-900">3您向我们提供的信息</h4>
<p class="text-justify">
在服务使用过程中您可以对赤眉产品及服务的体验问题反馈帮助我们更好地了解您使用我们产品或服务的体验和需求改善我们产品或服务,为此我们会记录您的联系信息反馈的问题或建议以便我们进一步联系您反馈您我们的处理意见
</p>
</section>
<section class="mb-6">
<h4 class="mb-2 font-semibold text-gray-900">4第三方软件开发包SDK</h4>
<p class="mb-3 text-justify">
我们产品中可能会包含第三方SDK或其他类似的应用程序如您在我们平台上使用这类由第三方提供的服务时您同意将由其直接收集和处理您的信息如以嵌入代码插件形式当您使用支付宝账号授权我们的产品时支付宝SDK需要收集您的账号信息前述服务商收集和处理信息行为遵守其自身的隐私条款而不适用于本政策但我们也会努力审查该第三方的业务准入资质并努力要求该服务商的合法合规性与安全性为了最大程度保障您的信息安全我们强烈建议您在使用任何第三方SDK类服务前先行查看其隐私条款为保障您的合法权益如您发现这SDK或其他类似的应用程序存在风险时建议您立即终止相关操作并及时与我们取得联系
</p>
<p class="mb-4 font-medium text-gray-900">以下是我们目前接入的第三方SDK类服务商的信息</p>
<p class="mb-2 font-medium text-gray-900">权限清单</p>
<div class="mb-8 overflow-x-auto">
<table class="min-w-full border-collapse border border-gray-200 text-left text-xs">
<thead>
<tr class="bg-gray-50">
<th class="border border-gray-200 px-2 py-2 font-semibold">权限功能说明</th>
<th class="border border-gray-200 px-2 py-2 font-semibold">权限名称</th>
<th class="border border-gray-200 px-2 py-2 font-semibold">使用场景或目的</th>
<th class="border border-gray-200 px-2 py-2 font-semibold">使用平台(iOS/Android)</th>
</tr>
</thead>
<tbody>
<tr>
<td class="border border-gray-200 px-2 py-2">读取和写入设备储存空间内的数据</td>
<td class="border border-gray-200 px-2 py-2">读取/写入外置存储</td>
<td class="border border-gray-200 px-2 py-2">用于保障赤眉的稳定运行以便用户在使用赤眉的功能时可读取写入/下载/保存/修改图片文件崩溃日志信息</td>
<td class="border border-gray-200 px-2 py-2">AndroidiOS</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2">使用摄像头拍摄图片</td>
<td class="border border-gray-200 px-2 py-2">摄像头</td>
<td class="border border-gray-200 px-2 py-2">用于完成照片的拍摄和发布</td>
<td class="border border-gray-200 px-2 py-2">AndroidiOS</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2">读取和写入设备相册中的内容</td>
<td class="border border-gray-200 px-2 py-2">读取/写入相册</td>
<td class="border border-gray-200 px-2 py-2">用于设置头像图片以及完成照片的发布</td>
<td class="border border-gray-200 px-2 py-2">AndroidiOS</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2">用于向用户发送提醒消息通知</td>
<td class="border border-gray-200 px-2 py-2">允许应用发送通知</td>
<td class="border border-gray-200 px-2 py-2">用于文章推送公告发布报告状态邀请下级会员升级消息通知</td>
<td class="border border-gray-200 px-2 py-2">Android</td>
</tr>
</tbody>
</table>
</div>
<h4 class="mb-2 mt-6 font-semibold text-gray-900">Android操作系统第三方SDK列表</h4>
<p class="mb-2 text-xs text-gray-600">内嵌 SDK 名单表头第三方公司名称产品/类型共享信息名称使用目的使用场景共享方式第三方个人信息处理规则联系方式</p>
<div class="mb-8 overflow-x-auto">
<table class="min-w-[960px] border-collapse border border-gray-200 text-left text-xs">
<thead>
<tr class="bg-gray-50">
<th class="border border-gray-200 px-2 py-2 font-semibold">第三方公司名称</th>
<th class="border border-gray-200 px-2 py-2 font-semibold">产品/类型</th>
<th class="border border-gray-200 px-2 py-2 font-semibold">说明共享信息目的场景方式</th>
<th class="border border-gray-200 px-2 py-2 font-semibold">规则与联系方式</th>
</tr>
</thead>
<tbody>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">腾讯科技深圳有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top">QQ SDK</td>
<td class="border border-gray-200 px-2 py-2 align-top">
设备型号信息如设备类型操作系统相册内存卡权限特定应用QQTIM的安装情况实现将微博内容分享到QQQQ授权登录分享内嵌SDK
</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://wiki.connect.qq.com/qq互联sdk隐私保护声明" target="_blank" rel="noopener noreferrer">https://wiki.connect.qq.com/qq互联sdk隐私保护声明</a><br />alarmapp@tencent.com
</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">深圳市腾讯计算机系统有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top">微信SDK</td>
<td class="border border-gray-200 px-2 py-2 align-top">
用户信息用户的头像昵称用户在第三方应用中主动选择的图片或内容但不会将前述信息用于关联和追踪用户安装微信APP的状态(安装或未安装实现将微博内容分享到微信微信授权登录微信好友和微信朋友圈分享功能内嵌SDK
</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://support.weixin.qq.com/cgi-bin/mmsupportacctnodeweb-bin/pages/RYiYJkLOrQwu0nb8" target="_blank" rel="noopener noreferrer">https://support.weixin.qq.com/cgi-bin/mmsupportacctnodeweb-bin/pages/RYiYJkLOrQwu0nb8</a><br />Dataprivacy@tencent.com
</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">支付宝中国网络技术有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top">支付宝SDK</td>
<td class="border border-gray-200 px-2 py-2 align-top">
联网信息wifi信息设备识别信息utdid阿里系的设备idandroid_id设备传感器列表a. 保障用户账户和资金安全以及支付服务的安全稳定运行b. 履行反洗钱反恐怖融资反电信网络诈骗法定义务c. 实现网络链路的选择和优化以提升用户体验用于提供支付宝支付或提现功能内嵌SDK
</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://render.alipay.com/p/c/k2cx0tg8" target="_blank" rel="noopener noreferrer">https://render.alipay.com/p/c/k2cx0tg8</a><br />95188
</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">小米科技有限责任公司</td>
<td class="border border-gray-200 px-2 py-2 align-top">小米PushSDK</td>
<td class="border border-gray-200 px-2 py-2 align-top">
设备标识符OAIDAndroid ID加密设备信息设备厂商设备型号设备内存操作系统版本设备归属地国家或地区SIM卡运营商名称其他信息网络类型收集消息创建/送达和点击时间暂存从第三方应用处获得并推送给您的消息内容用于实现对小米品牌的手机进行消息推送为客户端推送消息内嵌SDK
</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://dev.mi.com/console/doc/detail?pId=1822" target="_blank" rel="noopener noreferrer">https://dev.mi.com/console/doc/detail?pId=1822</a><br />950816
</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">维沃移动通信有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top">vivo PushSDK</td>
<td class="border border-gray-200 px-2 py-2 align-top">
应用包名版本号网络类型设备信息设备制造商国家码设备类型用于实现对vivo品牌的手机进行消息推送为客户端推送消息内嵌SDK
</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://dev.vivo.com.cn/documentCenter/doc/652" target="_blank" rel="noopener noreferrer">https://dev.vivo.com.cn/documentCenter/doc/652</a><br />dongyan.yan@vivo.com
</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">华为技术有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top">华为PushSDK</td>
<td class="border border-gray-200 px-2 py-2 align-top">
设备标识符AAID和Push Token设备硬件信息设备类型设备型号系统基本信息系统类型系统版本系统设置信息国家码用于实现对华为品牌的手机进行消息推送为客户端推送消息内嵌SDK
</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides/sdk-data-security-0000001050042177" target="_blank" rel="noopener noreferrer">https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides/sdk-data-security-0000001050042177</a><br />devComplaint@huawei.com
</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">广东欢太科技有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top">OPPOPushSDK</td>
<td class="border border-gray-200 px-2 py-2 align-top">安装应用列表用于实现对OPPO品牌的手机进行消息推送为客户端推送消息内嵌SDK</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://open.oppomobile.com/wiki/doc#id=10288" target="_blank" rel="noopener noreferrer">https://open.oppomobile.com/wiki/doc#id=10288</a>
</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">北京旷视科技有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top">Face++SDK</td>
<td class="border border-gray-200 px-2 py-2 align-top">姓名身份证号码用于核验身份信息是否真实身份验证内嵌SDK</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://assets.faceid.com/faceidopen/privacy-policy.html" target="_blank" rel="noopener noreferrer">https://assets.faceid.com/faceidopen/privacy-policy.html</a><br />business@megvii.com
</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">荣耀终端有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top">荣耀PushSDK</td>
<td class="border border-gray-200 px-2 py-2 align-top">
设备标识符AAID设备信息设备型号操作系统版本设备设置Token用于实现对荣耀品牌的手机进行消息推送为客户端推送消息内嵌SDK
</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://developer.hihonor.com/cn/kitdoc?category=%BB%F9%B4%A1%B7%FE%CE%F1&kitId=11002&navigation=guides&docId=sdk-data-security.md&token=" target="_blank" rel="noopener noreferrer">荣耀 SDK 数据安全说明</a><br />95030
</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">北京火山引擎科技有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top">转化SDK</td>
<td class="border border-gray-200 px-2 py-2 align-top">设备品牌型号软件系统版本相关信息oaidandroidId基础信息提供数据统计和分析服务内嵌SDK</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://open.oceanengine.com/labels/7/docs/1708428054592516" target="_blank" rel="noopener noreferrer">https://open.oceanengine.com/labels/7/docs/1708428054592516</a><br />IC-report@bytedance.com
</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">友盟同欣(北京)科技有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top">友盟统计分析SDK</td>
<td class="border border-gray-200 px-2 py-2 align-top">
设备信息(MAC/Android ID/IDFA/OAID/OpenUDID/GUID/IP)位置信息网络信息指定包名信息读取存储读取剪切板进行APP运营统计与分析内嵌SDK
</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://www.umeng.com/policy" target="_blank" rel="noopener noreferrer">https://www.umeng.com/policy</a><br />Umeng_Legal@service.umeng.com
</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">和讯华谷信息技术有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top">极光推送JPush极光分析JOperate</td>
<td class="border border-gray-200 px-2 py-2 align-top">
设备标识符包括Android IDGAIDOAIDUAIDIDFAAAID设备硬件信息包括设备型号设备屏幕分辨率设备硬件制造商设备产品名称设备存储空间操作系统信息包括操作系统版本系统名称系统语言网络信息包括网络类型运营商信息IP地址WIFI状态信息用户行为数据设备信息MACSSIDBSSID已安装应用列表标签数据用户行为分析与推送转化率统计推送通知内嵌SDK
</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://www.jiguang.cn/license/privacy?appName=undefined" target="_blank" rel="noopener noreferrer">https://www.jiguang.cn/license/privacy?appName=undefined</a><br />sales@jiguang.cn
</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">北京巨量引擎网络技术有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top">巨量SDK</td>
<td class="border border-gray-200 px-2 py-2 align-top">
收集硬件型号操作系统版本号设备标识符,设备Mac地址IP地址唯一设备识别码IMEI/MEID/SUPI/SUCI/android ID/IDFA/OPENUDID/GUID/OAIDSIM卡IMSI信息SIM卡信息如ICCID硬件序列号SNiOS如IDFVIDFAAPP用户归因统计内嵌SDK
</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://www.oceanengine.com/extra/legal" target="_blank" rel="noopener noreferrer">https://www.oceanengine.com/extra/legal</a><br />IC-report@bytedance.com
</td>
</tr>
</tbody>
</table>
</div>
<h4 class="mb-2 font-semibold text-gray-900">IOS操作系统第三方SDK列表</h4>
<div class="mb-8 overflow-x-auto">
<table class="min-w-[720px] border-collapse border border-gray-200 text-left text-xs">
<thead>
<tr class="bg-gray-50">
<th class="border border-gray-200 px-2 py-2 font-semibold">第三方SDK名称</th>
<th class="border border-gray-200 px-2 py-2 font-semibold">应用场景</th>
<th class="border border-gray-200 px-2 py-2 font-semibold">收集个人信息的类型</th>
<th class="border border-gray-200 px-2 py-2 font-semibold">第三方SDK提供方</th>
<th class="border border-gray-200 px-2 py-2 font-semibold">隐私政策链接</th>
<th class="border border-gray-200 px-2 py-2 font-semibold">SDK收集使用个人信息的使用目的</th>
</tr>
</thead>
<tbody>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">支付宝SDK</td>
<td class="border border-gray-200 px-2 py-2 align-top">提供支付宝支付提现能力</td>
<td class="border border-gray-200 px-2 py-2 align-top">网络状态信息设备信息本机号码</td>
<td class="border border-gray-200 px-2 py-2 align-top">支付宝</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://render.alipay.com/p/c/k2cx0tg8" target="_blank" rel="noopener noreferrer">https://render.alipay.com/p/c/k2cx0tg8</a>
</td>
<td class="border border-gray-200 px-2 py-2 align-top"></td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">旷视Face ID</td>
<td class="border border-gray-200 px-2 py-2 align-top">人脸识别</td>
<td class="border border-gray-200 px-2 py-2 align-top">人脸识别</td>
<td class="border border-gray-200 px-2 py-2 align-top">北京旷视科技有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://www.faceplusplus.com.cn/privacy-policy/" target="_blank" rel="noopener noreferrer">https://www.faceplusplus.com.cn/privacy-policy/</a>
</td>
<td class="border border-gray-200 px-2 py-2 align-top">确保本人操作</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">保存本地海报</td>
<td class="border border-gray-200 px-2 py-2 align-top">使用手机相册存储和相机</td>
<td class="border border-gray-200 px-2 py-2 align-top"></td>
<td class="border border-gray-200 px-2 py-2 align-top"></td>
<td class="border border-gray-200 px-2 py-2 align-top"></td>
<td class="border border-gray-200 px-2 py-2 align-top"></td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">微信</td>
<td class="border border-gray-200 px-2 py-2 align-top">打开用户微信分享信息给用户或分享至朋友圈</td>
<td class="border border-gray-200 px-2 py-2 align-top"></td>
<td class="border border-gray-200 px-2 py-2 align-top">深圳市腾讯计算机系统有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://weixin.qq.com/agreement?lang=zh_CN" target="_blank" rel="noopener noreferrer">https://weixin.qq.com/agreement?lang=zh_CN</a>
</td>
<td class="border border-gray-200 px-2 py-2 align-top"></td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">QQ</td>
<td class="border border-gray-200 px-2 py-2 align-top">打开用户QQ分享信息给用户</td>
<td class="border border-gray-200 px-2 py-2 align-top"></td>
<td class="border border-gray-200 px-2 py-2 align-top">深圳市腾讯计算机系统有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://privacy.qq.com/document/priview/fbd2c3f898df4c1c869925dd49d57827" target="_blank" rel="noopener noreferrer">https://privacy.qq.com/document/priview/fbd2c3f898df4c1c869925dd49d57827</a>
</td>
<td class="border border-gray-200 px-2 py-2 align-top"></td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">转化SDK</td>
<td class="border border-gray-200 px-2 py-2 align-top">提供数据统计和分析服务</td>
<td class="border border-gray-200 px-2 py-2 align-top">设备品牌型号软件系统版本相关信息oaid基础信息</td>
<td class="border border-gray-200 px-2 py-2 align-top">北京火山引擎科技有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://open.oceanengine.com/labels/7/docs/1708428054592516" target="_blank" rel="noopener noreferrer">https://open.oceanengine.com/labels/7/docs/1708428054592516</a>
</td>
<td class="border border-gray-200 px-2 py-2 align-top"></td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">友盟统计分析SDK</td>
<td class="border border-gray-200 px-2 py-2 align-top">进行APP运营统计与分析</td>
<td class="border border-gray-200 px-2 py-2 align-top">设备信息(MAC/Android ID/IDFA/OAID/OpenUDID/GUID)位置信息网络信息指定包名信息读取存储读取剪切板</td>
<td class="border border-gray-200 px-2 py-2 align-top">友盟同欣(北京)科技有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://www.umeng.com/policy" target="_blank" rel="noopener noreferrer">https://www.umeng.com/policy</a>
</td>
<td class="border border-gray-200 px-2 py-2 align-top"></td>
</tr>
</tbody>
</table>
</div>
<h4 class="mb-2 font-semibold text-gray-900">Harmony操作系统第三方SDK列表</h4>
<p class="mb-2 text-xs text-gray-600">内嵌 SDK 名单</p>
<div class="mb-4 overflow-x-auto">
<table class="min-w-[960px] border-collapse border border-gray-200 text-left text-xs">
<thead>
<tr class="bg-gray-50">
<th class="border border-gray-200 px-2 py-2 font-semibold">第三方公司名称</th>
<th class="border border-gray-200 px-2 py-2 font-semibold">产品/类型</th>
<th class="border border-gray-200 px-2 py-2 font-semibold">说明共享信息目的场景方式</th>
<th class="border border-gray-200 px-2 py-2 font-semibold">规则与联系方式</th>
</tr>
</thead>
<tbody>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">腾讯科技深圳有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top">QQ SDK</td>
<td class="border border-gray-200 px-2 py-2 align-top">特定应用QQTIM的安装情况实现内容分享到QQQQ分享内嵌SDK</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://wiki.connect.qq.com/qq互联sdk隐私保护声明" target="_blank" rel="noopener noreferrer">https://wiki.connect.qq.com/qq互联sdk隐私保护声明</a><br />alarmapp@tencent.com
</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">深圳市腾讯计算机系统有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top">微信SDK</td>
<td class="border border-gray-200 px-2 py-2 align-top">
用户信息用户的头像昵称安装微信APP的状态安装或未安装实现内容分享到微信微信授权登录微信好友和微信朋友圈分享功能内嵌SDK
</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://support.weixin.qq.com/cgi-bin/mmsupportacctnodeweb-bin/pages/RYiYJkLOrQwu0nb8" target="_blank" rel="noopener noreferrer">https://support.weixin.qq.com/cgi-bin/mmsupportacctnodeweb-bin/pages/RYiYJkLOrQwu0nb8</a><br />Dataprivacy@tencent.com
</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">支付宝中国网络技术有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top">支付宝SDK</td>
<td class="border border-gray-200 px-2 py-2 align-top">a. 保障用户账户和资金安全以及支付服务的安全稳定运行用于提供支付宝支付或提现功能内嵌SDK</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://render.alipay.com/p/c/k2cx0tg8" target="_blank" rel="noopener noreferrer">https://render.alipay.com/p/c/k2cx0tg8</a><br />95188
</td>
</tr>
<tr>
<td class="border border-gray-200 px-2 py-2 align-top">北京旷视科技有限公司</td>
<td class="border border-gray-200 px-2 py-2 align-top">Face++SDK</td>
<td class="border border-gray-200 px-2 py-2 align-top">姓名身份证号码用于核验身份信息是否真实身份验证内嵌SDK</td>
<td class="border border-gray-200 px-2 py-2 align-top break-all">
<a class="text-blue-600 underline" href="https://assets.faceid.com/faceidopen/privacy-policy.html" target="_blank" rel="noopener noreferrer">https://assets.faceid.com/faceidopen/privacy-policy.html</a><br />business@megvii.com
</td>
</tr>
</tbody>
</table>
</div>
</section>
<section class="mb-6">
<h4 class="mb-2 font-semibold text-gray-900">7为您提供安全保障收集信息</h4>
<p class="mb-3 text-justify">
您理解并同意我们向您提供的功能和服务场景是不断迭代升级的如我们未在上述场景中明示您需要收集的个人信息我们将会通过页面提示交互设计方式另行向您明示信息收集的内容范围和目的并征得您同意
</p>
<p class="text-justify">
如我们停止运营赤眉产品或服务我们将及时停止继续收集您个人信息的活动将停止运营的通知以公告或短信的形式通知您并依照所适用的法律对所持有的您的个人信息进行删除或匿名化处理
</p>
</section>
<h3 class="mb-3 mt-8 text-[15px] font-semibold text-gray-900">我们如何使用信息</h3>
<p class="mb-3 text-justify">
收集您的信息是为了向您提供服务及提升服务质量为了实现这一目的我们会把您的信息用于下列用途
</p>
<ol class="mb-8 list-decimal space-y-2 pl-6">
<li>向您提供您使用的赤眉产品或服务并维护改进优化这些服务及服务体验</li>
<li>经您许可的其他用途</li>
</ol>
<h3 class="mb-3 text-[15px] font-semibold text-gray-900">我们如何使用Cookie 和同类技术</h3>
<p class="mb-3 text-justify">
为使您获得更轻松的访问体验您使用赤眉产品或服务时我们可能会通过采用各种技术收集和存储您访问赤眉服务的相关数据在您访问或再次访问赤眉服务时,我们能识别您的身份并通过分析数据为您提供更好更多的服务包括使用小型数据文件识别您的身份这么做是为了解您的使用习惯帮您省去重复输入账户信息的步骤或者帮助判断您的账户安全这些数据文件可能是CookieFlash
Cookie或您的浏览器或关联应用程序提供的其他本地存储统称Cookie
</p>
<p class="mb-8 text-justify">
请您理解我们的某些服务只能通过使用Cookie才可得到实现如果您的浏览器或浏览器附加服务允许您可以修改对Cookie的接受程度或者拒绝赤眉的Cookie但拒绝赤眉的Cookie在某些情况下您可能无法使用依赖于cookies的赤眉服务的部分功能
</p>
<h3 class="mb-3 text-[15px] font-semibold text-gray-900">我们如何共享转让公开披露您的信息</h3>
<h4 class="mb-2 font-semibold text-gray-900">() 共享</h4>
<p class="mb-3 text-justify">我们不会和其他公司组织和个人共享您的个人信息但以下情况除外</p>
<ol class="mb-6 list-decimal space-y-2 pl-6">
<li>在获取您同意的情况下共享获得您的明确同意后我们会与其他方共享您的个人信息</li>
<li>在法定情形下的共享我们可能会根据法律法规规定诉讼争议解决需要或按行政司法机关依法提出的要求对外共享您的个人信息</li>
<li>
只有透露您的资料才能提供您所要求的第三方产品和服务在您通过赤眉客户端购买查询服务的您同意赤眉向实际产品提供者提供您的身份信息包括真实姓名和身份证号微信号身份证正面照片邮箱为了提升实人认证的准确性您同意第三方公司仅限于个人信息进行验证相关服务将您提供的个人信息与法律法规允许的机构或政府机关授权的机构的数据进行校验
</li>
<li>在您被他人投诉侵犯知识产权或其他合法权利时需要向投诉人披露您的必要资料以便进行投诉处理的</li>
<li>赤眉服务可能含有其他网站的链接除法律另有规定外赤眉对其他网站的隐私保护措施不负相应法律责任我们可能在需要的时候增加商业伙伴但是提供给他们的将仅是综合信息我们将不会公开您的个人信息</li>
</ol>
<h4 class="mb-2 font-semibold text-gray-900">() 转让</h4>
<p class="mb-3 text-justify">我们不会将您的个人信息转让给任何公司组织和个人但以下情况除外</p>
<ol class="mb-6 list-decimal space-y-2 pl-6">
<li>在获取明确同意的情况下转让获得您的明确同意后我们会向其他方转让您的个人信息</li>
<li>
在赤眉发生合并收购或破产清算情形或其他涉及合并收购或破产清算情形时如涉及到个人信息转让我们会要求新的持有您个人信息的公司组织继续受本政策的约束否则我们将要求该公司组织和个人重新向您征求授权同意
</li>
</ol>
<h4 class="mb-2 font-semibold text-gray-900">() 公开披露</h4>
<p class="mb-3 text-justify">我们仅会在以下情况下公开披露您的个人信息</p>
<ol class="mb-8 list-decimal space-y-2 pl-6">
<li>获得您明确同意或基于您的主动选择我们可能会公开披露您的个人信息</li>
<li>
如果我们确定您出现违反法律法规或严重违反赤眉相关协议规则的情况或为保护赤眉用户或公众的人身财产安全免遭侵害我们可能依据法律法规或赤眉相关协议规则征得您同意的情况下披露关于您的个人信息包括相关违规行为以及赤眉已对您采取的措施
</li>
</ol>
<h3 class="mb-3 text-[15px] font-semibold text-gray-900">我们如何保护您的信息</h3>
<p class="mb-3 text-justify">
我们会采取各种预防措施来保护您的个人信息以保障您的个人信息免遭丢失盗用和误用以及被擅自取阅披露更改或销毁为确保您个人信息的安全我们有严格的信息安全规定和流程并严格执行上述措施
</p>
<p class="mb-3 text-justify">赤眉建立了全方位多维度的数据安全管理体系保证整个赤眉各个平台的安全性</p>
<p class="mb-3 text-justify">
我们会采取合理可行的措施尽力避免收集无关的个人信息并在限于达成本政策所述目的所需的期限以及所适用法律法规所要求的期限内对你的个人信息进行脱敏处理在您使用查询过程中所涉及的用户姓名身份证号手机号均采用加密储存账号密码信息采用的是MD5密文储存所有二次输出信息均经过脱敏处理数据库文件不存储用户明文数据
</p>
<p class="mb-3 text-justify">
在不幸发生个人信息安全事件后我们将按照法律法规的要求最迟不迟于30
个自然日内向您告知安全事件的基本情况和可能的影响我们已采取或将要采取的处置措施您可自主防范和降低风险的建议对您的补救措施事件相关情况我们将以邮件信函电话通知其一方式告知您难以逐一告知个人信息主体时我们会采取合理有效的方式发布公告同时我们还将按照监管部门要求上报个人信息安全事件的处置情况
</p>
<p class="mb-8 text-justify">
互联网环境并非百分之百安全尽管我们有这些安全措施但仍然无法完全避免互联网中存在的各种风险我们将尽力确保您的信息的安全性
</p>
<h3 class="mb-3 text-[15px] font-semibold text-gray-900">未成年人保护</h3>
<p class="mb-8 text-justify">
我们重视未成年人的信息保护如您为未成年人的建议您请您的父母或监护人仔细阅读本隐私权政策并在征得您的父母或监护人同意的前提下使用我们的服务或向我们提供信息对于经父母或监护人同意使用我们的产品或服务而收集未成年人个人信息的情况我们只会在法律法规允许父母或监护人明确同意或者保护未成年人所必要的情况下使用共享转让或披露此信息我们将根据国家相关法律法规及本政策的规定保护未成年人的个人信息
</p>
<h3 class="mb-3 text-[15px] font-semibold text-gray-900">您的个人信息存储</h3>
<h4 class="mb-2 font-semibold text-gray-900">存储地区</h4>
<p class="mb-4 text-justify">
我们将在中华人民共和国境内运营赤眉服务中收集和产生的个人信息存储在中华人民共和国境内目前我们不会将上述信息传输至境外如果我们向境外传输我们将会遵循相关国家规定或者征求您的同意
</p>
<h4 class="mb-2 font-semibold text-gray-900">存储期限</h4>
<p class="mb-8 text-justify">
您在使用本平台期间我们将保存您的个人脱敏加密信息保存期限将以不超过为您提供服务所必须的期间为原则在您终止使用本平台后除法律法规对于特定信息保留期限另有规定外我们会对您的信息进行删除或做匿名化处理如我们停止运营本平台服务我们将在合理期限内依照所适用的法律对所持有的您的个人信息进行删除或匿名化处理
</p>
<h3 class="mb-3 text-[15px] font-semibold text-gray-900">您享有的权利及权利行使路径</h3>
<h4 class="mb-2 font-semibold text-gray-900">访问查询权</h4>
<p class="mb-3 text-justify">您对您的赤眉账号内的信息含个人信息依法享有访问查询权包括</p>
<p class="mb-2 text-justify">
账户信息您可以登录手机客户端通过我的-点击名字或头像可以访问您的头像信息姓名绑定手机号
</p>
<p class="mb-2 text-justify">
使用信息您可以在赤眉手机客户端相关页面访问查询您的使用信息包括订单信息可以通过报告列表-查看详情进行访问查看
</p>
<p class="mb-6 text-justify">
其他信息如您在此前述过程中遇到操作问题的或如需获取其他前述无法获知的个人信息内容您可通过在线客服或邮箱联系我们我们将在核实您的身份后在合理期限内向您提供但法律法规另有规定的或本政策另有约定的除外
</p>
<h4 class="mb-2 font-semibold text-gray-900">注销权</h4>
<p class="mb-3 text-justify">
对于您的账户信息您可以在赤眉客户端相关功能页面根据操作指引自行注销您了解并知悉一旦您进行相关操作我们将进行账户预注销与您的账户相关的数据也将同步删除并将久永不能恢复若您在180天内未进行登录您的账户有可能被冻结你需要重新激活方可使用对已经被冻结且长时间未激活的用户账号我们将进行不定期的排查你的账号有可能被注销法律法规另有规定的除外
</p>
<p class="mb-2 text-justify">在以下情形中您可以向我们提出删除个人信息的请求</p>
<ol class="mb-4 list-decimal space-y-2 pl-6">
<li>我们违反法规法规的规定收集使用处理您的个人信息</li>
<li>我们违反与您的约定或未在您同意范围内收集使用处理您的个人信息</li>
<li>您不再使用赤眉产品/服务或您主动注销您的赤眉账户</li>
<li>我们无法或不再为您提供任何产品或服务</li>
<li>法律法规规定的其他情形</li>
</ol>
<p class="mb-3 text-justify">
您知悉并同意一旦您删除或我们协助您注销相关信息该等注销指令可能无法撤回该等被注销的信息可能无法恢复您需谨慎操作并自行做好重要信息的备份当您注销或我们协助您注销相关信息时我们可能不会立即从备份系统中删除相应的信息但会在备份更新时删除这些信息更多关于账号注销的流程条件事项请详见账号注销协议
</p>
<p class="mb-6 text-justify">
注销指引下载赤眉APP登录赤眉账户点击我的设置图标注销账户阅读账号注销协议同意注销注销完成
</p>
<h4 class="mb-2 font-semibold text-gray-900">同意的撤回与变更</h4>
<p class="mb-6 text-justify">
若您需要更改相关权限的授权相机相册麦克风您可以通过您的硬件设备进行修改您也可以通过注销赤眉账户的方式永久撤回我们继续收集您个人信息的全部授权如您在此过程中遇到操作问题的可以通过本政策帮助中心方式联系我们
</p>
<h4 class="mb-2 font-semibold text-gray-900">帮助反馈权</h4>
<p class="mb-6 text-justify">我们为您提供了多种反馈渠道具体请见设置帮助中心</p>
<h4 class="mb-2 font-semibold text-gray-900">提前获知产品与/或服务停止运营权</h4>
<p class="mb-8 text-justify">
我们将持续为您提供优质服务若因特殊原因导致我们的部分或全部产品与/或服务被迫停止运营我们将提前在显著位置或通知您并将停止对您个人信息的收集同时在超出法律法规规定的必需且最短期限后我们将会对所持有的您的个人信息进行删除或匿名化处理
</p>
<h3 class="mb-3 text-[15px] font-semibold text-gray-900">本政策如何更新</h3>
<p class="mb-3 text-justify">我们的隐私政策可能变更</p>
<p class="mb-3 text-justify">
未经您明确同意我们不会限制您按照本隐私政策所应享有的权利我们会在赤眉各个平台包括客户端相关网页上以首页弹窗形式发布对本隐私政策所做的任何变更并以交互设计提醒您阅读并完整理解
对于重大变更我们还会提供更为显著的通知可能包括公告通知甚至向您提供弹窗提示
</p>
<p class="mb-2 text-justify">本政策所指的重大变更</p>
<ol class="mb-8 list-decimal space-y-2 pl-6">
<li>我们的服务模式发生重大变化如处理用户信息的目的用户信息的使用方式</li>
<li>我们在控制权组织架构方面发生重大变化如业务调整破产并购引起的所有者变更</li>
<li>用户信息共享转让或公开披露的主要对象发生变化</li>
<li>我们负责处理用户信息安全的责任部门联络方式及投诉渠道发生变化时</li>
<li>用户信息安全影响评估报告表明存在高风险时</li>
</ol>
<h3 class="mb-3 text-[15px] font-semibold text-gray-900">如何联系我们</h3>
<p class="mb-4 text-justify">
如果您对本政策或数据处理有任何疑问意见或建议可以通过赤眉产品内的帮助中心投诉与建议与我们联系我们将在收到您发送的响应请求或相关信息之日起十五15天内回复您
</p>
<p class="mb-2 text-justify">您理解并同意当涉及以下任一情形时我们无法响应您的请求</p>
<ol class="mb-4 list-decimal space-y-2 pl-6">
<li>与国家安全国防安全有关的</li>
<li>与公共安全公共卫生重大公共利益有关的</li>
<li>与犯罪侦查起诉和审判有关的</li>
<li>有充分证据表明您存在主观恶意或滥用权利的</li>
<li>响应您的请求将导致您或其他个人组织的合法权益受到严重损害的</li>
<li>涉及赤眉或任何第三方主体商业秘密的</li>
<li>法律法规规定的其他情形</li>
</ol>
<p class="mb-8 text-justify">
如果您对我们的回复不满意特别是您认为我们的个人信息处理行为损害了您的合法权益您还可以通过向有管辖权的法院提起诉讼来寻求解决方案
</p>
<h3 class="mb-3 text-[15px] font-semibold text-gray-900">十一其他</h3>
<p class="mb-4 text-justify">
隐私政策的解释及争议解决均应适用中华人民共和国大陆地区法律与本隐私政策相关的任何纠纷双方应协商友好解决协商不成的还可以向消费者权益保护部门投诉或将争议提交至双方的所在地有管辖权的人民法院解决
</p>
<p class="text-justify">
隐私政策的标题仅为方便及阅读而设并不影响正文其中任何规定的含义或解释
</p>
</article>
</template>
<style lang="scss" scoped></style>

271
src/views/Promote.vue Normal file
View File

@@ -0,0 +1,271 @@
<template>
<div class="min-h-screen p-4 promote">
<div class="mb-4 card !bg-gradient-to-b from-orange-200 to-orange-200/80">
<div class="">
<div class="text-lg font-bold text-orange-500">直推用户查询</div>
<div class="font-bold text-orange-400 mt-1">
自定义价格赚取差价
</div>
</div>
<div class="mt-6">
<div class="mt-2 text-gray-600 bg-orange-100 rounded-xl px-4 py-2">
在下方 自定义价格 处选择报告类型设置客户查询价即可立即推广
</div>
</div>
</div>
<VipBanner />
<!-- 判断是否是代理 -->
<div>
<div class="card mb-4">
<div class="">
<h2 class="text-xl font-semibold mb-2">生成推广码</h2>
<van-cell-group inset>
<!-- 报告类型 -->
<van-field v-model="pickerFieldText" is-link readonly label="报告类型" placeholder="请选择报告类型"
@click="showTypePicker = true" />
<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>
<!-- 定价 -->
<van-field v-model="clientPrice" is-link readonly label="客户查询价" placeholder="请输入价格"
@click="showPricePicker = true" />
<PriceInputPopup v-model:show="showPricePicker" :default-price="clientPrice"
:product-config="pickerProductConfig" @change="onPriceChange" />
<div class="flex items-center justify-between my-2">
<div class="text-sm text-gray-500">推广收益为 <span class="text-orange-500">{{ promotionRevenue
}}</span> </div>
<div class="text-sm text-gray-500">我的成本为 <span class="text-orange-500">{{ costPrice
}}</span> </div>
</div>
</van-cell-group>
</div>
<div class="mt-6">
<van-button type="primary" class="w-full" @click="generatePromotionCode">点击立即推广</van-button>
</div>
</div>
</div>
<!-- 如果不是代理展示根据status显示不同内容 -->
<!-- <div>
<div v-if="status === 0" class="card mt-6">
<div class="font-semibold text-lg text-gray-700">申请审核中</div>
<div class="text-sm text-gray-500 mt-4 mb-8">
您的申请正在审核中请耐心等待
</div>
</div>
<div v-else-if="status === 2" class="card mt-6">
<div class="font-semibold text-lg text-gray-700">申请未通过</div>
<div class="text-sm text-gray-500 mt-4 mb-8">
很抱歉您的代理申请未通过请检查您的信息或重新申请
</div>
<van-button type="primary" round class="w-full" @click="showApplyPopup = true">
申请成为代理
</van-button>
</div>
<div v-else-if="status === 3" class="card mt-6">
<div class="font-semibold text-lg text-gray-700">未申请成为代理</div>
<div class="text-sm text-gray-500 mt-4 mb-8">
您还没有申请成为代理立即申请即可开始推广
</div>
<van-button type="primary" round class="w-full" @click="showApplyPopup = true">
申请成为代理
</van-button>
</div>
</div>
<AgentApplicationForm v-model:show="showApplyPopup" @submit="submitApplication"
@close="showApplyPopup = false" /> -->
<QRcode v-model:show="showQRcode" :linkIdentifier="linkIdentifier" />
</div>
</template>
<script setup>
import PriceInputPopup from '@/components/PriceInputPopup.vue';
import VipBanner from '@/components/VipBanner.vue';
const reportTypes = [
{ text: "小微企业", value: "companyinfo", id: 2 },
{ text: "贷前风险", value: "preloanbackgroundcheck", id: 5 },
{ text: "个人大数据", value: "personaldata", id: 27 },
{ text: '入职风险', value: 'backgroundcheck', id: 1 },
{ text: '家政风险', value: 'homeservice', id: 3 },
{ text: '婚恋风险', value: 'marriage', id: 4 },
{ text: '租赁风险', value: 'rentalrisk', id: 6 },
];
const showTypePicker = ref(false);
const showApplyPopup = ref(false); // 用来控制申请代理弹窗的显示
const showPricePicker = ref(false);
const pickerFieldText = ref('')
const pickerFieldVal = ref(null)
const pickerProductConfig = ref(null)
const selectedReportType = ref([]);
const clientPrice = ref(null);
const productConfig = ref(null);
const linkIdentifier = ref("")
// const costPrice = computed(() => {
// if (!pickerProductConfig.value) return 0.00
// // 平台定价成本
// let platformPricing = 0
// if (clientPrice.value > pickerProductConfig.value.p_pricing_standard) {
// platformPricing = (clientPrice.value - pickerProductConfig.value.p_pricing_standard) * pickerProductConfig.value.p_overpricing_ratio
// }
// return (pickerProductConfig.value.cost_price + platformPricing).toFixed(2)
// })
const costPrice = computed(() => {
if (!pickerProductConfig.value) return 0.00
// 平台定价成本
let platformPricing = 0
platformPricing += pickerProductConfig.value.cost_price
if (clientPrice.value > pickerProductConfig.value.p_pricing_standard) {
platformPricing += (clientPrice.value - pickerProductConfig.value.p_pricing_standard) * pickerProductConfig.value.p_overpricing_ratio
}
if (pickerProductConfig.value.a_pricing_standard > platformPricing && pickerProductConfig.value.a_pricing_end > platformPricing && pickerProductConfig.value.a_overpricing_ratio > 0) {
if (clientPrice.value > pickerProductConfig.value.a_pricing_standard) {
if (clientPrice.value > pickerProductConfig.value.a_pricing_end) {
platformPricing += (pickerProductConfig.value.a_pricing_end - pickerProductConfig.value.a_pricing_standard) * pickerProductConfig.value.a_overpricing_ratio
} else {
platformPricing += (clientPrice.value - pickerProductConfig.value.a_pricing_standard) * pickerProductConfig.value.a_overpricing_ratio
}
}
}
return safeTruncate(platformPricing)
})
const promotionRevenue = computed(() => {
return safeTruncate(clientPrice.value - costPrice.value)
});
const showQRcode = ref(false);
function safeTruncate(num, decimals = 2) {
if (isNaN(num) || !isFinite(num)) return "0.00";
const factor = 10 ** decimals;
const scaled = Math.trunc(num * factor);
const truncated = scaled / factor;
return truncated.toFixed(decimals);
}
const generatePromotionCode = async () => {
if (selectedReportType.value.length === 0) {
showToast({ message: '请选择报告类型' });
return;
}
if (!clientPrice.value) {
showToast({ message: '请输入查询价格' });
return;
}
const { data, error } = await useApiFetch("/agent/generating_link")
.post({ product: pickerFieldVal.value, price: clientPrice.value })
.json()
if (data.value && !error.value) {
if (data.value.code === 200) {
linkIdentifier.value = data.value.data.link_identifier
} else {
console.log("Error fetching agent info", data.value);
}
}
if (!linkIdentifier.value) return
showQRcode.value = true;
};
onMounted(() => {
getPromoteConfig();
// getAgentInfo();
});
const SelectTypePicker = (reportType) => {
selectedReportType.value = [reportType];
pickerFieldText.value = reportType.text;
pickerFieldVal.value = reportType.value;
for (let i of productConfig.value) {
if (i.product_id === reportType.id) {
pickerProductConfig.value = i
clientPrice.value = i.cost_price
}
}
};
const getPromoteConfig = async () => {
const { data, error } = await useApiFetch("/agent/product_config")
.get()
.json()
if (data.value && !error.value) {
if (data.value.code === 200) {
productConfig.value = data.value.data.AgentProductConfig;
SelectTypePicker(reportTypes[0])
} else {
console.log("Error fetching agent info", data.value);
}
}
}
const onPriceChange = (price) => {
clientPrice.value = price
}
// const getAgentInfo = async () => {
// const { data, error } = await useApiFetch("/agent/info")
// .get()
// .json()
// if (data.value && !error.value) {
// if (data.value.code === 200) {
// isAgent.value = data.value.data.is_agent; // 判断是否是代理
// status.value = data.value.data.status; // 获取代理状态
// agentID.value = data.value.data.agent_id
// } else {
// console.log("Error fetching agent info", data.value);
// }
// }
// };
const onConfirmType = ({ selectedValues, selectedOptions }) => {
SelectTypePicker(selectedOptions[0])
showTypePicker.value = false;
};
const submitApplication = async (formData) => {
// 提交代理申请的数据
const { region, mobile, wechat_id, code } = formData;
const { data, error } = await useApiFetch("/agent/apply")
.post({ region, mobile, wechat_id, code })
.json();
if (data.value && !error.value) {
if (data.value.code === 200) {
showApplyPopup.value = false;
// 这里可以提示成功,或者刷新代理状态
showToast({ message: "已提交申请" });
} else {
console.log('申请失败', data.value);
}
}
};
</script>
<style scoped>
/* .promote {
background-color: #ffeee0;
background-image: linear-gradient(45deg,
rgba(255, 235, 205, 0.3) 25%,
transparent 25%,
transparent 50%,
rgba(255, 235, 205, 0.3) 50%,
rgba(255, 235, 205, 0.3) 75%,
transparent 75%,
transparent);
background-size: 40px 40px;
min-height: 100vh;
}
*/
</style>

View 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 { useDialogStore } from '@/stores/dialogStore';
import InquireForm from "@/components/InquireForm.vue";
import LoginDialog from "@/components/LoginDialog.vue";
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
const dialogStore = useDialogStore();
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) {
router.push({ path: "/report", query: { orderNo } });
}
}
async function getProduct() {
linkIdentifier.value = route.params.linkIdentifier;
const { data: agentLinkData, error: agentLinkError } = await useApiFetch(
`/agent/link?link_identifier=${linkIdentifier.value}`
)
.get()
.json();
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" />
<LoginDialog />
</template>

147
src/views/Report.vue Normal file
View File

@@ -0,0 +1,147 @@
<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" />
<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";
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 (data.value && !error.value) {
if (data.value.code === 200) {
queryState.value = data.value.data.query_state;
if (queryState.value === "success") {
feature.value = data.value.data.product;
reportData.value = data.value.data.query_data.sort((a, b) => {
return a.feature.sort - b.feature.sort;
});
// console.log("reportData", reportData.value[1].data.data)
reportParams.value = data.value.data.query_params;
reportName.value = data.value.data.product_name;
reportDateTime.value = data.value.data.create_time;
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>

218
src/views/ReportShare.vue Normal file
View File

@@ -0,0 +1,218 @@
<template>
<div class="min-h-screen bg-gray-50">
<van-nav-bar title="赤眉" left-arrow @click-left="goHome" fixed placeholder safe-area-inset-topxc z>
<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";
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);
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 (data.value && !error.value) {
if (data.value.code === 200) {
// 检查分享链接状态
if (data.value.data.status === "expired") {
isExpired.value = true;
isDone.value = true;
// 如果过期,清除轮询
if (pollingInterval.value) {
clearInterval(pollingInterval.value);
pollingInterval.value = null;
}
return;
}
queryState.value = data.value.data.query_state;
if (queryState.value === "success") {
reportData.value = data.value.data.query_data.sort((a, b) => {
return a.feature.sort - b.feature.sort;
});
feature.value = data.value.data.product;
reportParams.value = data.value.data.query_params;
reportName.value = data.value.data.product_name;
reportDateTime.value = data.value.data.create_time;
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>

View File

@@ -0,0 +1,324 @@
<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 fetchRewardDetails = async () => {
if (loading.value || finished.value) return
loading.value = true
const { data, error } = await useApiFetch(
`/agent/subordinate/contribution/detail?subordinate_id=${route.params.id}&page=${page.value}&page_size=${pageSize}`
)
.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,
}
// 更新汇总数据
summary.value = {
totalReward: data.value.data.total_earnings,
totalContribution: data.value.data.total_contribution,
totalOrders: data.value.data.total_orders,
}
// 设置默认的统计类型
statistics.value = [
{
type: 'descendant_promotion',
amount: 0,
count: 0,
description: '推广奖励',
},
{
type: 'cost',
amount: 0,
count: 0,
description: '成本贡献',
},
{
type: 'pricing',
amount: 0,
count: 0,
description: '定价贡献',
},
{
type: 'descendant_withdraw',
amount: 0,
count: 0,
description: '提现收益',
},
{
type: 'descendant_upgrade_vip',
amount: 0,
count: 0,
description: '转化VIP奖励',
},
{
type: 'descendant_upgrade_svip',
amount: 0,
count: 0,
description: '转化SVIP奖励',
},
]
// 如果有统计数据,更新对应的值
if (data.value.data.stats) {
const stats = data.value.data.stats
// 更新推广奖励
const platformStat = statistics.value.find(s => s.type === 'descendant_promotion')
if (platformStat) {
platformStat.amount = stats.descendant_promotion_amount || 0
platformStat.count = stats.descendant_promotion_count || 0
}
// 更新成本贡献
const costStat = statistics.value.find(s => s.type === 'cost')
if (costStat) {
costStat.amount = stats.cost_amount || 0
costStat.count = stats.cost_count || 0
}
// 更新定价贡献
const pricingStat = statistics.value.find(s => s.type === 'pricing')
if (pricingStat) {
pricingStat.amount = stats.pricing_amount || 0
pricingStat.count = stats.pricing_count || 0
}
// 更新提现收益
const withdrawStat = statistics.value.find(s => s.type === 'descendant_withdraw')
if (withdrawStat) {
withdrawStat.amount = stats.descendant_withdraw_amount || 0
withdrawStat.count = stats.descendant_withdraw_count || 0
}
// 更新转化VIP奖励
const conversionVipStat = statistics.value.find(s => s.type === 'descendant_upgrade_vip')
if (conversionVipStat) {
conversionVipStat.amount = stats.descendant_upgrade_vip_amount || 0
conversionVipStat.count = stats.descendant_upgrade_vip_count || 0
}
// 更新转化SVIP奖励
const conversionSvipStat = statistics.value.find(s => s.type === 'descendant_upgrade_svip')
if (conversionSvipStat) {
conversionSvipStat.amount = stats.descendant_upgrade_svip_amount || 0
conversionSvipStat.count = stats.descendant_upgrade_svip_count || 0
}
}
rewardDetails.value = []
}
// 处理列表数据
if (data.value.data.list) {
const list = data.value.data.list.filter(
i => i.type !== 'descendant_new_active' && i.type !== 'descendant_stay_active',
)
if (page.value === 1) {
rewardDetails.value = list
} else {
rewardDetails.value.push(...list)
}
finished.value = list.length < pageSize
} else {
finished.value = true
}
}
}
loading.value = false
}
// 下拉刷新
const onRefresh = () => {
finished.value = false
page.value = 1
fetchRewardDetails().finally(() => {
refreshing.value = false
})
}
const rewardDetails = ref([])
const userInfo = ref({})
const summary = ref({})
const statistics = ref([])
onMounted(() => {
fetchRewardDetails()
})
// 获取收益类型样式
const getRewardTypeClass = type => {
const typeMap = {
descendant_promotion: 'bg-blue-100 text-blue-600',
cost: 'bg-green-100 text-green-600',
pricing: 'bg-purple-100 text-purple-600',
descendant_withdraw: 'bg-yellow-100 text-yellow-600',
descendant_upgrade_vip: 'bg-red-100 text-red-600',
descendant_upgrade_svip: 'bg-orange-100 text-orange-600',
}
return typeMap[type] || 'bg-gray-100 text-gray-600'
}
// 获取收益类型图标
const getRewardTypeIcon = type => {
const iconMap = {
descendant_promotion: 'gift',
cost: 'gold-coin',
pricing: 'balance-pay',
descendant_withdraw: 'cash-back-record',
descendant_upgrade_vip: 'fire',
descendant_upgrade_svip: 'fire',
}
return iconMap[type] || 'balance-o'
}
// 获取收益类型描述
const getRewardTypeDescription = type => {
const descriptionMap = {
descendant_promotion: '推广奖励',
cost: '成本贡献',
pricing: '定价贡献',
descendant_withdraw: '提现收益',
descendant_upgrade_vip: '转化VIP奖励',
descendant_upgrade_svip: '转化SVIP奖励',
}
return descriptionMap[type] || '未知类型'
}
// 格式化时间
const formatTime = timeStr => {
if (!timeStr) return '-'
const date = new Date(timeStr)
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
}
// 格式化金额
const formatNumber = num => {
if (!num) return '0.00'
return Number(num).toFixed(2)
}
</script>
<template>
<div class="reward-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 class="grid grid-cols-3 gap-4">
<div class="text-center">
<div class="text-gray-500 text-sm mb-1">总推广单量</div>
<div class="text-xl font-semibold text-blue-600">{{ summary.totalOrders }}</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(summary.totalReward) }}</div>
</div>
<div class="text-center">
<div class="text-gray-500 text-sm mb-1">总贡献</div>
<div class="text-xl font-semibold text-purple-600">¥{{ formatNumber(summary.totalContribution) }}</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-2 gap-3">
<div
v-for="item in statistics"
:key="item.type"
class="flex items-center p-2 rounded-lg"
:class="getRewardTypeClass(item.type).split(' ')[0]"
>
<van-icon
:name="getRewardTypeIcon(item.type)"
class="text-lg mr-2"
:class="getRewardTypeClass(item.type).split(' ')[1]"
/>
<div class="flex-1">
<div class="text-sm font-medium" :class="getRewardTypeClass(item.type).split(' ')[1]">
{{ item.description }}
</div>
<div class="flex justify-between items-center mt-1">
<div class="text-xs text-gray-500">{{ item.count }} </div>
<div class="text-sm font-medium" :class="getRewardTypeClass(item.type).split(' ')[1]">
¥{{ formatNumber(item.amount) }}
</div>
</div>
</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">贡献记录</div>
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="fetchRewardDetails"
>
<div class="p-4">
<div v-if="rewardDetails.length === 0" class="text-center text-gray-500 py-8">暂无贡献记录</div>
<div v-else v-for="item in rewardDetails" :key="item.id" class="reward-item">
<div class="mb-3 border-b border-gray-200 pb-3">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<van-icon
:name="getRewardTypeIcon(item.type)"
class="text-lg"
:class="getRewardTypeClass(item.type).split(' ')[1]"
/>
<div>
<div class="font-medium text-gray-800">{{ getRewardTypeDescription(item.type) }}</div>
<div class="text-xs text-gray-500">{{ formatTime(item.create_time) }}</div>
</div>
</div>
<div class="text-right">
<div class="text-base font-semibold" :class="getRewardTypeClass(item.type).split(' ')[1]">
¥{{ formatNumber(item.amount) }}
</div>
</div>
</div>
</div>
</div>
</div>
</van-list>
</van-pull-refresh>
</div>
</div>
</div>
</template>
<style scoped>
.reward-detail {
min-height: 100vh;
background-color: #f5f5f5;
}
.reward-item {
transition: transform 0.2s;
}
.reward-item:active {
transform: scale(0.98);
}
</style>

View File

@@ -0,0 +1,185 @@
<script setup>
import { ref, onMounted } from 'vue'
import { useAgentStore } from '@/stores/agentStore'
import useApiFetch from '@/composables/useApiFetch'
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()
onBeforeMount(() => {
fetchSubordinates()
})
// 计算统计数据
const statistics = ref({
totalSubordinates: 0,
})
// 获取下级列表
const fetchSubordinates = async () => {
if (loading.value || finished.value) return
loading.value = true
const { data, error } = await useApiFetch(`/agent/subordinate/list?page=${page.value}&page_size=${pageSize}`)
.get()
.json()
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)
}
}
}
finished.value = true
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)
}
// 获取等级标签样式
const getLevelClass = level => {
switch (level) {
case 'SVIP':
return 'bg-purple-100 text-purple-600'
case 'VIP':
return 'bg-blue-100 text-blue-600'
default:
return 'bg-gray-100 text-gray-600'
}
}
// 查看详情
const viewDetail = item => {
router.push({
name: 'subordinateDetail',
params: { 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="fetchSubordinates">
<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 ? item.level : '普通' }}代理
</span>
</div>
</div>
<!-- 加入时间 -->
<div class="text-sm text-gray-500 mb-5">成为下级代理时间{{ item.create_time }}</div>
<!-- 数据统计 -->
<div class="grid grid-cols-3 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 }}</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_earnings) }}</div>
</div>
<div class="text-center">
<div class="text-gray-500 text-sm mb-1">总贡献</div>
<div class="text-xl font-semibold text-purple-600">¥{{ formatNumber(item.total_contribution) }}</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>

137
src/views/UserAgreement.vue Normal file
View File

@@ -0,0 +1,137 @@
<script setup>
import { ref, onMounted, nextTick } from "vue";
const rootRef = ref(null);
const COMPANY_NAME = import.meta.env.VITE_COMPANY_NAME;
if (!COMPANY_NAME) throw new Error("缺少环境变量: VITE_COMPANY_NAME");
const APP_NAME = import.meta.env.VITE_APP_NAME;
if (!APP_NAME) throw new Error("缺少环境变量: VITE_APP_NAME");
const CONTACT_EMAIL = import.meta.env.VITE_CONTACT_EMAIL;
if (!CONTACT_EMAIL) throw new Error("缺少环境变量: VITE_CONTACT_EMAIL");
function replaceTextInNode(node, from, to) {
if (!node) return;
if (node.nodeType === Node.TEXT_NODE) {
if (node.textContent.includes(from)) {
node.textContent = node.textContent.split(from).join(to);
}
return;
}
if (node.nodeType !== Node.ELEMENT_NODE) return;
for (const child of Array.from(node.childNodes)) {
replaceTextInNode(child, from, to);
}
}
onMounted(async () => {
await nextTick();
replaceTextInNode(rootRef.value, "戎行技术有限公司", COMPANY_NAME);
replaceTextInNode(rootRef.value, "赤眉", APP_NAME);
replaceTextInNode(rootRef.value, "admin@iieeii.com", CONTACT_EMAIL);
});
</script>
<template>
<article ref="rootRef" class="user-agreement p-4 text-sm leading-relaxed text-gray-800">
<h1 class="mb-4 text-center text-lg font-semibold text-gray-900">用户协议</h1>
<p class="mb-1 text-gray-600">更新日期2025年05月09日</p>
<p class="mb-4 text-gray-600">生效日期2025年05月09日</p>
<p class="mb-3 text-justify">
本协议是您以下又称用户在使用本服务时约定您和戎行技术有限公司之间权利义务关系的有效协议
</p>
<p class="mb-3 text-justify">
在您使用本服务前请您务必仔细阅读本协议特别是隐私权保护及授权条款免除或者限制戎行技术有限公司责任的条款争议解决和法律适用条款一旦您有对本服务的任何部分或全部的注册查看定制使用等任何使用行为即视为您已充分阅读理解并接受本协议的全部内容并与戎行技术有限公司达成本协议如您对本协议有任何疑问应向戎行技术有限公司客服咨询如果您不同意本协议的部分或全部约定您应立即停止使用本服务
</p>
<p class="mb-6 text-justify">
您与戎行技术有限公司达成本协议后您承诺接受并遵守本协议的约定并不得以未阅读本协议的内容或者未获得戎行技术有限公司对您问询的解答等理由主张本协议无效或要求撤销本协议在本协议履行过程中戎行技术有限公司有权根据自身业务需要修订本协议但修订内容会在首页显著位置公开征求意见采取合理措施确保有关各方能够及时充分表达意见且修改内容在实施前七日予以公示如果您不同意戎行技术有限公司对本协议所做的修改您应立即停止使用本服务或通过戎行技术有限公司客服与戎行技术有限公司联系如果您继续使用本服务则视为您接受戎行技术有限公司对本协议所做的修改并应遵照修改后的协议执行
</p>
<h2 class="mb-2 mt-6 font-semibold text-gray-900">服务内容</h2>
<p class="mb-3 text-justify">
本服务向您提供多项个人信息整理服务您知悉并认可如您需使用该类服务必须满足如下所述条件且您承诺您向戎行技术有限公司提请服务申请时已经满足如下所述条件
</p>
<ol class="mb-3 list-none space-y-2 pl-0 [&>li]:pl-0">
<li>A.您已注册成为本服务的会员</li>
<li>B.您已在服务页面对应框中填写被查询主体的姓名身份证号手机号银行卡号和被查询主体的手机号收到的动态验证码以下称被查询主体信息</li>
<li>C.您确保被查询主体信息是您本人的信息或者被查询主体已授权您本人使用被查询主体信息进行查询授权内容应包括本条D项所述内容并且被查询主体已知悉该授权的风险</li>
<li>D.被查询主体不可撤销地授权戎行技术有限公司为查询评估被查询主体的信息状况a.可以委托合法存续的第三方机构收集查询验证使用并提供您或被查询主体的个人信息b.可以向数据源机构采集您或被查询主体的个人信息c.可以整理保存加工使用您或被查询主体的个人信息并向您提供数据报告d.可以向为您提供服务的第三方商户提供脱敏后的个人信息或数据报告本条所述的个人信息包括但不限于身份信息联系方式职业和居住地址等个人基本信息个人社保公积金收入及在商业活动中形成的各类交易记录个人公共费用缴纳违法违规信息财产状况等</li>
<li>E.被查询主体已被明确告知提供被查询主体信息并作出D项授权可能给被查询主体带来的各类损失以及其他可能的不利后果包括采集上述个人信息对被查询主体信用方面可能产生不良影响以及上述信息被信息使用者依法提供给第三方后被他人不当利用的风险</li>
<li>F.您已全额支付相应的查询服务费用</li>
<li>G.验证码请不要轻易提供给他人一旦填入手机号对应验证码视为手机号机主本人操作</li>
</ol>
<h2 class="mb-2 mt-6 font-semibold text-gray-900">服务中断或故障</h2>
<p class="mb-3 text-justify">
戎行技术有限公司将尽力保障服务的稳定性与安全性对于系统维护升级等可预见的中断公司提前48小时通过平台公告尽可能缩短中断时间并协助用户减少损失但因以下原因导致服务中断或故障的戎行技术有限公司不承担责任
</p>
<ol class="mb-6 list-decimal space-y-2 pl-6">
<li>您的电脑手机软硬件和通信线路供电线路出现故障的</li>
<li>您操作不当或通过非戎行技术有限公司授权或认可的方式使用本服务的</li>
<li>因台风地震海啸洪水停电战争恐怖袭击等不可抗力之因素造成本服务系统障碍不能执行业务的</li>
</ol>
<h2 class="mb-2 mt-6 font-semibold text-gray-900">信息的使用和保护</h2>
<p class="mb-3 text-justify">
戎行技术有限公司深知您注重个人信息安全和保护并理解保护被查询主体个人信息的重要性戎行技术有限公司会严格遵守中国关于收集使用保存用户个人信息的相关法律法规尽最大努力采用相应安全技术和管理手段保护您或被查询主体的个人信息防止您或被查询主体个人信息遭受未经授权的访问适用或泄露毁损篡改或者丢失未经您或被查询主体的授权不会向任何第三方提供
</p>
<p class="mb-2">在为您服务的过程中我们使用SDK服务使用详情如下</p>
<dl class="mb-4 rounded border border-gray-200 bg-gray-50 px-3 py-2 text-xs sm:text-sm">
<dt class="mt-1 font-medium text-gray-700 first:mt-0">使用SDK名称</dt>
<dd class="mb-2 pl-0">友盟SDK</dd>
<dt class="font-medium text-gray-700">服务类型</dt>
<dd class="mb-2 pl-0">统计分析</dd>
<dt class="font-medium text-gray-700">收集个人信息类型</dt>
<dd class="mb-2 pl-0">设备信息IMEI/Mac/Android ID/IDFA/OPENUDID/GUID/地理位置等</dd>
<dt class="font-medium text-gray-700">隐私权政策链接</dt>
<dd class="pl-0">
<a
href="https://www.umeng.com/page/policy"
class="break-all text-blue-600 underline"
target="_blank"
rel="noopener noreferrer"
>https://www.umeng.com/page/policy</a>
</dd>
</dl>
<p class="mb-3 text-justify">
但您同意为核实戎行技术有限公司的记录了解用户群体偏好提高本服务质量完善本服务的个性化内容及种类为您提供更优质的服务和用户体验之目的戎行技术有限公司可以电话短信电子邮件或其他方式向您推送符合您需求的相关资讯您使用本服务即表示您已授权戎行技术有限公司将您相关信息披露给戎行技术有限公司关联公司关联公司是指直接或间接控制于本协议一方的任何法律实体或者与本协议一方共同于另一法律实体的任何法律实体使用且戎行技术有限公司关联公司仅为了向您提供服务而使用您的相关信息如戎行技术有限公司关联公司使用您的相关信息则受本协议约束且会按照与戎行技术有限公司同等谨慎程度保护您的相关信息
</p>
<p class="mb-2">戎行技术有限公司就下列原因导致的您或被查询主体个人信息的泄露不承担任何法律责任</p>
<ol class="mb-6 list-decimal space-y-2 pl-6">
<li>由于您个人原因将本服务的会员账号和密码告知他人或与他人共享戎行技术有限公司服务账户由此导致的与您相关的信息的泄露</li>
<li>您使用第三方提供的服务包括您向第三方提供的任何个人信息须受第三方自己的服务条款及个人信息保护协议而非本协议约束您需要仔细阅读其条款本协议仅适用于戎行技术有限公司所提供的服务并不适用于任何第三方提供的服务或第三方的信息使用规则戎行技术有限公司对任何第三方使用由您提供的信息不承担任何责任</li>
<li>根据相关的法律法规相关政府主管部门或相关证券交易所的要求提供公布与您相关的信息</li>
<li>或其他非因戎行技术有限公司原因导致的与您相关的信息的泄露</li>
</ol>
<h2 class="mb-2 mt-6 font-semibold text-gray-900">用户声明与保证</h2>
<ol class="mb-6 list-decimal space-y-2 pl-6">
<li>您使用本服务的前提是您依照适用的法律是具有完全民事权利和民事行为能力能够独立承担民事责任的自然人</li>
<li>您如违反本协议第一条款中的承诺您可能会对他人造成侵权如由此给戎行技术有限公司或他人造成损失的您需依照法律法规规定承担相应的法律责任</li>
</ol>
<h2 class="mb-2 mt-6 font-semibold text-gray-900">知识产权保护</h2>
<p class="mb-6 text-justify">
本服务涉及的文档资料软件商标图案排版设计等以下简称戎行技术有限公司产品的著作权商标以及其他知识产权或权益均为戎行技术有限公司享有或戎行技术有限公司获得授权使用用户不得出租出借拷贝仿冒复制或修改戎行技术有限公司产品任何部分或用于其他任何商业目的也不得将戎行技术有限公司产品做反向工程反编译或反汇编或以其他方式或工具取得戎行技术有限公司产品之目标程序或源代码如果用户违反此约定造成戎行技术有限公司及其他任何第三方任何损失的甲方应予以全额赔偿
</p>
<h2 class="mb-2 mt-6 font-semibold text-gray-900">违约</h2>
<p class="mb-6 text-justify">
用户不得利用本服务进行任何损害戎行技术有限公司及其他第三方权益的行为否则戎行技术有限公司有权立即终止为该用户提供本服务并要求用户赔偿损失由此产生的任何后果由用户自行承担与戎行技术有限公司无关
</p>
<h2 class="mb-2 mt-6 font-semibold text-gray-900">适用法律</h2>
<p class="mb-6 text-justify">
本协议条款的解释效力及纠纷的解决适用中华人民共和国大陆地区法律法规如用户和戎行技术有限公司之间发生任何争议首先应友好协商解决协商不成的还可以向消费者权益保护部门投诉或将争议提交至双方的所在地有管辖权的人民法院解决
</p>
<h2 class="mb-2 mt-6 font-semibold text-gray-900">问题咨询</h2>
<p class="mb-2 text-justify">
如您对本协议及本服务有任何问题请通过赤眉公众号联系戎行技术有限公司进行咨询
</p>
<p class="text-justify">戎行技术有限公司会尽最大努力解决您的问题</p>
</article>
</template>

839
src/views/Withdraw.vue Normal file
View File

@@ -0,0 +1,839 @@
<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>
<!-- 提现方式切换标签 -->
<van-tabs v-if="showTabs" v-model:active="activeTab" class="mb-4" @change="onTabChange">
<van-tab v-if="showAlipay" title="支付宝提现" name="alipay">
<div class="rounded-xl shadow-lg p-6 mb-4 mt-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="alipayRealName" 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>
</van-tab>
<van-tab v-if="showBankcard" title="银行卡提现" name="bankcard">
<div class="rounded-xl shadow-lg p-6 mb-4 mt-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="credit-pay" class="text-xl mr-2" style="color: #1677FF;" />
<h1 class="text-xl font-bold" style="color: var(--van-text-color);">银行卡提现</h1>
</div>
<!-- 实名信息展示 -->
<div v-if="bankCardInfo.payee_name" class="mb-4 p-4 rounded-lg"
style="background-color: rgba(22, 119, 255, 0.1);">
<div class="text-xs mb-2" style="color: var(--van-text-color-2);">您的实名信息</div>
<div class="text-sm" style="color: var(--van-text-color);">
<div class="mb-1">姓名<span class="font-semibold">{{ bankCardInfo.payee_name }}</span>
</div>
<div>身份证号<span class="font-semibold">{{ formatIdCard(bankCardInfo.id_card) }}</span>
</div>
</div>
<div class="text-xs mt-2" style="color: #f59e0b;">
<van-icon name="warning-o" class="mr-1" />提示银行卡信息需与实名认证信息一致
</div>
</div>
<!-- 银行卡号 -->
<div class="mb-6">
<label class="text-sm mb-2 block" style="color: var(--van-text-color);">银行卡号</label>
<van-field v-model="bankCardNo" type="number" placeholder="请输入银行卡号16-19位"
class="flex items-center rounded-lg bg-white/90 backdrop-blur-sm shadow-sm" :rules="[
{ required: true, message: ' ' },
{ validator: validateBankCardNo, message: ' ' },
]">
<template #left-icon>
<van-icon name="credit-pay" style="color: var(--van-text-color-2);" />
</template>
</van-field>
<small class="text-xs mt-1 block"
style="color: var(--van-text-color-2);">请输入16-19位银行卡号</small>
</div>
<!-- 开户支行 -->
<div class="mb-6">
<label class="text-sm mb-2 block" style="color: var(--van-text-color);">开户支行</label>
<van-field v-model="bankName" 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="location-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);">例如中国工商银行XX支行</small>
</div>
</div>
</van-tab>
</van-tabs>
<!-- 如果只显示一种方式不显示 tabs直接显示内容 -->
<div v-if="!showTabs">
<!-- 只显示支付宝提现 -->
<div v-if="showAlipay && !showBankcard" class="rounded-xl shadow-lg p-6 mb-4 mt-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="alipayRealName" 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>
<!-- 只显示银行卡提现 -->
<div v-if="showBankcard && !showAlipay" class="rounded-xl shadow-lg p-6 mb-4 mt-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="credit-pay" class="text-xl mr-2" style="color: #1677FF;" />
<h1 class="text-xl font-bold" style="color: var(--van-text-color);">银行卡提现</h1>
</div>
<!-- 实名信息展示 -->
<div v-if="bankCardInfo.payee_name" class="mb-4 p-4 rounded-lg"
style="background-color: rgba(22, 119, 255, 0.1);">
<div class="text-xs mb-2" style="color: var(--van-text-color-2);">您的实名信息</div>
<div class="text-sm" style="color: var(--van-text-color);">
<div class="mb-1">姓名<span class="font-semibold">{{ bankCardInfo.payee_name }}</span>
</div>
<div>身份证号<span class="font-semibold">{{ formatIdCard(bankCardInfo.id_card) }}</span>
</div>
</div>
<div class="text-xs mt-2" style="color: #f59e0b;">
<van-icon name="warning-o" class="mr-1" />提示银行卡信息需与实名认证信息一致
</div>
</div>
<!-- 银行卡号 -->
<div class="mb-6">
<label class="text-sm mb-2 block" style="color: var(--van-text-color);">银行卡号</label>
<van-field v-model="bankCardNo" type="number" placeholder="请输入银行卡号16-19位"
class="flex items-center rounded-lg bg-white/90 backdrop-blur-sm shadow-sm" :rules="[
{ required: true, message: ' ' },
{ validator: validateBankCardNo, message: ' ' },
]">
<template #left-icon>
<van-icon name="credit-pay" style="color: var(--van-text-color-2);" />
</template>
</van-field>
<small class="text-xs mt-1 block" style="color: var(--van-text-color-2);">请输入16-19位银行卡号</small>
</div>
<!-- 开户支行 -->
<div class="mb-6">
<label class="text-sm mb-2 block" style="color: var(--van-text-color);">开户支行</label>
<van-field v-model="bankName" 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="location-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);">例如中国工商银行XX支行</small>
</div>
</div>
</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 mb-4" 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 v-if="activeTab === 'alipay'">· 到账时间24小时内</li>
<li v-else>· 到账时间管理员审核后手动转账</li>
</ul>
</div>
<!-- 税收说明 -->
<div class="p-4 rounded-xl backdrop-blur-sm mb-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>
<!-- 提交按钮 -->
<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">
-¥{{ taxAmount.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);">¥{{ actualAmount.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 v-if="activeTab === 'alipay'">
已向
<span style="color: var(--van-theme-primary);">{{ alipayAccount }}</span>
转账
</span>
<span v-else>
提现申请已通过管理员将手动转账
</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 v-if="activeTab === 'alipay'">预计24小时内到账</p>
<p v-else>请等待管理员审核并转账</p>
</template>
<template v-if="status === 1">
<p v-if="activeTab === 'alipay'">您的申请已进入处理队列</p>
<p v-else>您的申请已提交等待管理员审核</p>
<p>可在提现记录中查看结果</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, onBeforeMount } from "vue";
import { showToast } from "vant";
import RealNameAuthDialog from "@/components/RealNameAuthDialog.vue";
const agentStore = useAgentStore();
const dialogStore = useDialogStore();
// ========== 提现方式配置开关 ==========
// 可以通过环境变量控制
// 配置选项(建议其中一种):
// - 'alipay'
// - 'bankcard'
// - 'both'
// - 'alipay,bankcard'
// - JSON 数组字符串: ["alipay","bankcard"]
const WITHDRAW_CONFIG = import.meta.env.VITE_WITHDRAW_METHODS;
if (!WITHDRAW_CONFIG) {
throw new Error("缺少环境变量: VITE_WITHDRAW_METHODS");
}
// 解析配置:支持字符串和数组格式
const getWithdrawMethods = () => {
if (Array.isArray(WITHDRAW_CONFIG)) return WITHDRAW_CONFIG;
if (typeof WITHDRAW_CONFIG === 'string') {
// 支持 JSON 数组字符串
if (WITHDRAW_CONFIG.trim().startsWith('[')) {
const parsed = JSON.parse(WITHDRAW_CONFIG);
if (Array.isArray(parsed)) return parsed.map(String);
}
if (WITHDRAW_CONFIG === 'alipay') return ['alipay'];
if (WITHDRAW_CONFIG === 'bankcard') return ['bankcard'];
if (WITHDRAW_CONFIG === 'both') return ['alipay', 'bankcard'];
// 支持逗号分隔的字符串: "alipay,bankcard" 或 "alipay" 或 "bankcard"
return WITHDRAW_CONFIG.split(',').map(s => s.trim()).filter(Boolean);
}
throw new Error(`VITE_WITHDRAW_METHODS 无法解析: ${WITHDRAW_CONFIG}`);
};
const enabledMethods = getWithdrawMethods();
const showAlipay = enabledMethods.includes('alipay');
const showBankcard = enabledMethods.includes('bankcard');
const showTabs = showAlipay && showBankcard; // 只有两种都显示时才显示 tabs
// 标签页管理
const getInitialTab = () => {
// 如果只显示一种方式,直接设置为该方式
if (showAlipay && !showBankcard) return 'alipay';
if (showBankcard && !showAlipay) return 'bankcard';
// 如果两种都显示,默认支付宝
return 'alipay';
};
const activeTab = ref(getInitialTab());
// 状态管理
const status = ref(null);
const failMsg = ref("");
const isSubmitting = ref(false);
const showStatusPopup = ref(false);
const showTaxConfirmPopup = ref(false);
// 税务
const taxFreeAmount = ref(0);
const usedExemptionAmount = ref(0);
const remainingExemptionAmount = ref(0);
const taxRate = ref(0);
// 计算扣税金额和实际到账金额
const taxAmount = computed(() => {
if (!amount.value) return 0;
const withdrawAmount = Number(amount.value);
return withdrawAmount * 0.06;
});
const actualAmount = computed(() => {
if (!amount.value) return 0;
return Number(amount.value) - taxAmount.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: activeTab.value === 'alipay' ? "提现申请处理中,请稍后再查询结果" : "提现申请已提交,等待管理员审核",
2: "提现成功",
3: "提现失败",
};
// 表单数据 - 支付宝
const alipayAccount = ref("");
const alipayRealName = ref("");
// 表单数据 - 银行卡
const bankCardNo = ref("");
const bankName = ref("");
const bankCardInfo = ref({
bank_card_no: "",
bank_name: "",
payee_name: "",
id_card: "",
});
// 共用数据
const amount = ref(0);
const availableAmount = ref(null);
// 标签页切换
const onTabChange = (name) => {
if (name === 'bankcard') {
loadBankCardInfo();
}
resetForm();
};
// 加载银行卡信息
const loadBankCardInfo = async () => {
try {
const { data, error } = await useApiFetch("/agent/withdrawal/bank-card/info")
.get()
.json();
if (data.value?.code === 200 && !error.value) {
bankCardInfo.value = data.value.data;
// 自动填充历史银行卡信息
if (data.value.data.bank_card_no) {
bankCardNo.value = data.value.data.bank_card_no;
}
if (data.value.data.bank_name) {
bankName.value = data.value.data.bank_name;
}
}
} catch (err) {
console.error("加载银行卡信息失败", err);
}
};
// 格式化身份证号显示前6位和后4位
const formatIdCard = (idCard) => {
if (!idCard) return "";
if (idCard.length <= 10) return idCard;
return idCard.substring(0, 6) + "****" + idCard.substring(idCard.length - 4);
};
// 验证银行卡号
const validateBankCardNo = (val) => {
if (!val) return false;
const cardNo = String(val).replace(/\s/g, "");
return /^\d{16,19}$/.test(cardNo);
};
const getData = async () => {
const { data: res, error } = await useApiFetch("/agent/revenue")
.get()
.json();
if (res.value?.code === 200 && !error.value) {
availableAmount.value = res.value.data.balance;
}
};
onBeforeMount(() => {
getData();
getTax();
// 根据配置加载对应的数据
if (showBankcard && (activeTab.value === 'bankcard' || !showTabs)) {
loadBankCardInfo();
}
});
// 表单验证
const validateAmount = (val) => {
const num = Number(val);
return num >= 50 && num <= availableAmount.value;
};
const validateForm = () => {
if (activeTab.value === 'alipay') {
// 支付宝提现验证
if (!alipayRealName.value.trim()) {
showToast("请输入账户实名姓名");
return false;
}
if (!/^[\u4e00-\u9fa5]{2,4}$/.test(alipayRealName.value)) {
showToast("请输入2-4位中文姓名");
return false;
}
if (!alipayAccount.value.trim()) {
showToast("请输入支付宝账号");
return false;
}
} else {
// 银行卡提现验证
if (!bankCardNo.value.trim()) {
showToast("请输入银行卡号");
return false;
}
const cardNo = String(bankCardNo.value).replace(/\s/g, "");
if (!/^\d{16,19}$/.test(cardNo)) {
showToast("银行卡号格式不正确请输入16-19位数字");
return false;
}
if (!bankName.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 getTax = async () => {
const { data, error } = await useApiFetch("/agent/withdrawal/tax/exemption")
.get()
.json();
if (data.value?.code === 200 && !error.value) {
taxFreeAmount.value = data.value.data.total_exemption_amount;
usedExemptionAmount.value = data.value.data.used_exemption_amount;
remainingExemptionAmount.value = data.value.data.remaining_exemption_amount;
taxRate.value = data.value.data.tax_rate;
}
};
const handleSubmit = async () => {
// 检查实名认证状态
if (!agentStore.isRealName) {
showToast("请先完成实名认证");
openRealNameAuth();
return;
}
// 先进行表单验证
if (!validateForm()) return;
// 显示税务确认弹窗
showTaxConfirmPopup.value = true;
};
// 确认提现
const confirmWithdraw = async () => {
isSubmitting.value = true;
try {
let apiUrl, requestData;
if (activeTab.value === 'alipay') {
// 支付宝提现
apiUrl = "/agent/withdrawal";
requestData = {
payee_account: alipayAccount.value,
amount: amount.value,
payee_name: alipayRealName.value,
};
} else {
// 银行卡提现
apiUrl = "/agent/withdrawal/bank-card";
requestData = {
bank_card_no: String(bankCardNo.value).replace(/\s/g, ""),
bank_name: bankName.value,
amount: amount.value,
};
}
const { data, error } = await useApiFetch(apiUrl)
.post(requestData)
.json();
if (data.value?.code === 200) {
status.value = data.value.data.status;
showTaxConfirmPopup.value = false;
showStatusPopup.value = true;
if (status.value === 3) {
failMsg.value = data.value.data.fail_msg || "提现失败";
}
getData();
} else {
showToast(data.value?.msg || "提现失败");
}
} catch (err) {
showToast("提现失败,请重试");
console.error("提现失败", err);
} 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;
if (activeTab.value === 'alipay') {
alipayAccount.value = "";
alipayRealName.value = "";
} else {
// 银行卡提现不清空,保留历史信息
// bankCardNo.value = "";
// bankName.value = "";
}
amount.value = "";
};
const resetPage = () => {
resetForm();
// 可以跳转到提现记录页面
};
</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>

View File

@@ -0,0 +1,197 @@
<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">
<div v-if="item.withdraw_type === 1 && item.payee_account">
收款账户{{ maskName(item.payee_account) }}
</div>
<div v-if="item.withdraw_type === 2">
<div v-if="item.bank_card_no">银行卡号{{ maskBankCard(item.bank_card_no) }}</div>
<div v-if="item.bank_name">开户支行{{ item.bank_name }}</div>
<div v-if="item.payee_name">收款人{{ item.payee_name }}</div>
</div>
<div v-if="item.remark">备注{{ item.remark }}</div>
</div>
</div>
</van-list>
</div>
</template>
<script setup>
// 状态映射配置
const statusConfig = {
1: {
chinese: "处理中",
color: {
bg: "bg-yellow-100",
text: "text-yellow-800",
dot: "bg-yellow-500",
amount: "text-yellow-500",
},
},
2: {
chinese: "提现成功",
color: {
bg: "bg-green-100",
text: "text-green-800",
dot: "bg-green-500",
amount: "text-green-500",
},
},
3: {
chinese: "提现失败",
color: {
bg: "bg-red-100",
text: "text-red-800",
dot: "bg-red-500",
amount: "text-red-500",
},
},
};
const page = ref(1);
const pageSize = ref(10);
const 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 maskBankCard = (cardNo) => {
if (!cardNo || typeof cardNo !== "string") return "";
const card = cardNo.replace(/\s/g, "");
if (card.length <= 8) return card;
return card.substring(0, 4) + " **** **** " + card.substring(card.length - 4);
};
// 状态转中文
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 () => {
try {
loading.value = true;
const { data: res, error } = await useApiFetch(
`/agent/withdrawal?page=${page.value}&page_size=${pageSize.value}`
)
.get()
.json();
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;
}
}
} 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>

226
src/views/index.vue Normal file
View File

@@ -0,0 +1,226 @@
<script setup>
const router = useRouter();
import { storeToRefs } from "pinia";
import { showConfirmDialog } from "vant";
import SectionTitle from "@/components/SectionTitle.vue";
const agentStore = useAgentStore();
const { isAgent } = storeToRefs(agentStore);
import personalDataIcon from "@/assets/images/index/personal_data_bg.png";
import companyIcon from "@/assets/images/index/company_bg.png";
import loanCheckIcon from "@/assets/images/index/loan_check_bg.png";
import marriageRiskIcon from "@/assets/images/index/marriage_risk_bg.png";
import housekeepingRiskIcon from "@/assets/images/index/housekeeping_risk_bg.png";
import preLoanRiskIcon from "@/assets/images/index/preloan_risk_bg.png";
import rentalinfo from "@/assets/images/index/rentalinfo_bg.png";
import bannerImg from "@/assets/images/index/banner_1.png";
import bannerImg2 from "@/assets/images/index/banner_2.png";
import bannerImg3 from "@/assets/images/index/banner_3.png";
function toInquire(name) {
router.push(`/inquire/${name}`);
}
function toInvitation() {
router.push({ name: "invitation" });
}
const toPromote = () => {
router.push({ name: "promote" });
};
const toHelp = () => {
router.push("/help");
};
function toHistory() {
router.push(`/historyQuery`);
}
const services = ref([
{
title: "个人大数据",
name: "personalData",
subtitle: "数据洞察,规避风险",
bg: personalDataIcon,
goColor: "#6699ff",
},
{
title: "婚恋风险",
name: "marriage",
subtitle: "深度了解,坦诚相爱",
bg: marriageRiskIcon,
goColor: "#ff99cc",
},
{
title: "入职背调",
name: "backgroundcheck",
subtitle: "人才甄选,用人无忧",
bg: preLoanRiskIcon,
goColor: "#7db3ff",
},
]);
const riskServices = ref([
{
title: "小微",
name: "companyinfo",
subtitle: "风险监控,稳健经营",
bg: companyIcon,
goColor: "#ffaa66",
},
{
title: "家政",
name: "homeservice",
subtitle: "身份核验,守护家庭",
bg: housekeepingRiskIcon,
goColor: "#66cccc",
},
{
title: "贷前",
name: "preloanbackgroundcheck",
subtitle: "信用评估,放款无忧",
bg: loanCheckIcon,
goColor: "var(--color-service-loan)",
},
{
title: "诚信租赁",
name: "rentalinfo",
subtitle: "信用识别,安心出租",
bg: rentalinfo,
goColor: "#8b8cff",
},
]);
const banners = ref([
bannerImg,
bannerImg2,
bannerImg3,
]);
</script>
<template>
<div class="box-border min-h-screen">
<div class="relative p-4">
<!-- <BannerCarousel :images="banners" /> -->
<van-swipe class="rounded-xl overflow-hidden" :autoplay="3000" indicator-color="white">
<van-swipe-item v-for="(item, index) in banners" :key="index">
<img :src="item" alt="首页轮播图" class="w-full h-full block" />
</van-swipe-item>
</van-swipe>
</div>
<div class="px-6">
<div class="grid grid-cols-3 gap-3">
<div class="text-center flex flex-col justify-center items-center" @click="toPromote">
<div
class="h-16 w-16 p-1 box-content bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
<img src="@/assets/images/index/tgbg.png" alt="推广报告" class="h-12 w-12" />
</div>
<div class="text-center mt-1 font-bold ">推广报告</div>
</div>
<div class="text-center flex flex-col justify-center items-center" @click="toInvitation">
<div
class="h-16 w-16 p-1 box-content bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
<img src="@/assets/images/index/yqhy.png" alt="邀请下级" class="h-12 w-12" />
</div>
<div class="text-center mt-1 font-bold ">邀请下级</div>
</div>
<!-- <div class="text-center flex flex-col justify-center items-center" @click="toHelp">
<div
class="h-16 w-16 p-1 box-content bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
<img src="@/assets/images/index/bzzx.png" alt="帮助中心" class="h-12 w-12" />
</div>
<div class="text-center mt-1 font-bold ">帮助中心</div>
</div> -->
<div class="text-center flex flex-col justify-center items-center" @click="toHistory">
<div
class="h-16 w-16 p-1 box-content bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
<img src="@/assets/images/index/wdbg.png" alt="我的报告" class="h-12 w-12" />
</div>
<div class="text-center mt-1 font-bold ">我的报告</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-28 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="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="flex gap-2 my-4 px-1 pt-2 pb-4 -mx-1 overflow-x-auto no-scrollbar">
<template v-for="(service, index) in riskServices" :key="index">
<div class="relative flex-shrink-0 w-[107px] h-24 rounded-xl shadow-lg"
:style="`background: url(${service.bg}) no-repeat; background-size: cover; background-position: center;`"
@click="toInquire(service.name)">
<div class="h-full flex flex-col items-start px-2 py-2">
<!-- <div class="mt-1 max-w-max text-left text-gray-600 font-bold">
{{ service.title }}
</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>
<SectionTitle title="诚信专栏" class="mt-8" />
<div class="mt-4 rounded-xl overflow-hidden bg-white shadow-xl">
<img src="@/assets/images/index/banner_B.png" class="w-full h-full" alt="诚信专栏横幅" mode="widthFix" />
</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/bg_icon.png" mode="widthFix" />
</div>
<div class="flex-1">
<div class="text-gray-800">我的历史查询记录</div>
<div class="text-xs text-gray-500">查询记录有效期为30天</div>
</div>
<img src="@/assets/images/index/right.png" alt="右箭头" class="h-6 w-6" />
</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);
}
.no-scrollbar {
scrollbar-width: none;
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
</style>
<route type="home" lang="json">{
"layout": "home"
}</route>