f
@@ -39,9 +39,8 @@
|
||||
|
||||
### TabBar 页面
|
||||
1. ✅ `src/pages/index.vue` - 首页
|
||||
2. ✅ `src/pages/promote/index.vue` - 推广页
|
||||
3. ✅ `src/pages/agent/index.vue` - 数据页
|
||||
4. ✅ `src/pages/me/index.vue` - 我的页
|
||||
2. ✅ `src/pages/agent/index.vue` - 数据页
|
||||
3. ✅ `src/pages/me/index.vue` - 我的页
|
||||
|
||||
### 其他页面
|
||||
- ✅ `src/pages/help/index.vue` - 帮助中心
|
||||
|
||||
@@ -3,13 +3,11 @@ import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages'
|
||||
export default defineUniPages({
|
||||
pages: [
|
||||
{ path: 'pages/index', type: 'home', layout: 'HomeLayout', style: { navigationBarTitleText: '一查查' } },
|
||||
{ path: 'pages/promote/index', layout: 'HomeLayout', style: { navigationBarTitleText: '推广' } },
|
||||
{ path: 'pages/agent/index', layout: 'HomeLayout', style: { navigationBarTitleText: '数据' } },
|
||||
{ path: 'pages/me/index', layout: 'HomeLayout', style: { navigationBarTitleText: '我的' } },
|
||||
{ path: 'pages/help/index', layout: 'PageLayout', style: { navigationBarTitleText: '帮助中心' } },
|
||||
{ path: 'pages/help/detail', layout: 'PageLayout', style: { navigationBarTitleText: '帮助详情' } },
|
||||
{ path: 'pages/help/guide', layout: 'PageLayout', style: { navigationBarTitleText: '引导指南' } },
|
||||
{ path: 'pages/register/index', layout: 'PageLayout', style: { navigationBarTitleText: '注册成为代理' } },
|
||||
{ path: 'pages/historyQuery/index', layout: 'PageLayout', style: { navigationBarTitleText: '历史查询' } },
|
||||
{ path: 'pages/report/index', layout: 'PageLayout', style: { navigationBarTitleText: '报告详情' } },
|
||||
{ path: 'pages/service/index', layout: 'PageLayout', style: { navigationBarTitleText: '客服' } },
|
||||
@@ -18,10 +16,12 @@ export default defineUniPages({
|
||||
{ path: 'pages/privacyPolicy/index', layout: 'PageLayout', style: { navigationBarTitleText: '隐私政策' } },
|
||||
{ path: 'pages/agentManageAgreement/index', layout: 'PageLayout', style: { navigationBarTitleText: '代理管理协议' } },
|
||||
{ path: 'pages/teamList/index', layout: 'PageLayout', style: { navigationBarTitleText: '我的团队' } },
|
||||
{ path: 'pages/teamList/detail', layout: 'PageLayout', style: { navigationBarTitleText: '下级详情' } },
|
||||
{ path: 'pages/agentUpgrade/index', layout: 'PageLayout', style: { navigationBarTitleText: '升级代理' } },
|
||||
{ path: 'pages/promoteDetails/index', layout: 'PageLayout', style: { navigationBarTitleText: '我的推广收益' } },
|
||||
{ path: 'pages/rewardsDetails/index', layout: 'PageLayout', style: { navigationBarTitleText: '下级推广收益' } },
|
||||
{ path: 'pages/invitation/index', layout: 'PageLayout', style: { navigationBarTitleText: '邀请下级代理' } },
|
||||
{ path: 'pages/promote/reportList', layout: 'PageLayout', style: { navigationBarTitleText: '推广报告' } },
|
||||
{ path: 'pages/promote/report', layout: 'PageLayout', style: { navigationBarTitleText: '推广报告' } },
|
||||
{ path: 'pages/withdrawDetails/index', layout: 'PageLayout', style: { navigationBarTitleText: '提现记录' } },
|
||||
// #ifdef MP-WEIXIN
|
||||
@@ -49,12 +49,6 @@ export default defineUniPages({
|
||||
iconPath: '/static/homelayout/index.png',
|
||||
selectedIconPath: '/static/homelayout/index_active.png',
|
||||
},
|
||||
{
|
||||
pagePath: 'pages/promote/index',
|
||||
text: '推广',
|
||||
iconPath: '/static/homelayout/promote.png',
|
||||
selectedIconPath: '/static/homelayout/promote_active.png',
|
||||
},
|
||||
{
|
||||
pagePath: 'pages/agent/index',
|
||||
text: '数据',
|
||||
|
||||
17
src/App.vue
@@ -58,7 +58,7 @@ const login = () => {
|
||||
|
||||
console.log(`[登录] 获取code成功,第${loginRetryCount}次尝试`)
|
||||
|
||||
wxminiLogin({ code }).then((result) => {
|
||||
wxminiLogin({ code }).then(async (result) => {
|
||||
// 检查网络请求是否失败
|
||||
if (result.error.value) {
|
||||
console.error('[登录] 网络请求失败:', result.error.value)
|
||||
@@ -91,8 +91,19 @@ const login = () => {
|
||||
console.log('[登录] 登录成功')
|
||||
isLoggingIn = false
|
||||
loginRetryCount = 0
|
||||
getUser()
|
||||
getAgentInformation()
|
||||
try {
|
||||
await getUser()
|
||||
// 微信环境下未绑定手机号时,跳转绑定手机号页(与 BindPhoneDialog 逻辑一致)
|
||||
// #ifdef MP-WEIXIN
|
||||
if (!userStore.mobile) {
|
||||
uni.reLaunch({ url: '/pages/auth/index' })
|
||||
return
|
||||
}
|
||||
// #endif
|
||||
getAgentInformation()
|
||||
} catch (e) {
|
||||
console.error('[登录] 获取用户信息失败', e)
|
||||
}
|
||||
} else {
|
||||
// 检查是否是 code 无效错误
|
||||
const errorMsg = result.data.value.msg || ''
|
||||
|
||||
@@ -74,6 +74,14 @@ export function getTeamList(params) {
|
||||
return useApiFetch(`/agent/team/list${buildQueryString(params)}`).get().json()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下级贡献详情(订单/邀请列表)
|
||||
* @param {object} params - { subordinate_id, page, page_size, tab_type: 'order'|'invite' }
|
||||
*/
|
||||
export function getSubordinateContributionDetail(params) {
|
||||
return useApiFetch(`/agent/subordinate/contribution/detail${buildQueryString(params)}`).get().json()
|
||||
}
|
||||
|
||||
export function getSubordinateList(params) {
|
||||
return useApiFetch(`/agent/subordinate/list${buildQueryString(params)}`).get().json()
|
||||
}
|
||||
|
||||
@@ -11,20 +11,20 @@
|
||||
:placeholder="pricePlaceholder"
|
||||
@blur="onBlurPrice"
|
||||
/>
|
||||
<view class="flex justify-between mt-3 text-sm">
|
||||
<text>推广收益为</text>
|
||||
<text class="text-orange-500 font-medium">¥ {{ promotionRevenue }}</text>
|
||||
<view class="flex justify-between mt-2 text-sm">
|
||||
<text>推广收益为<text class="text-orange-500 font-medium"> {{ promotionRevenue }} </text>元</text>
|
||||
</view>
|
||||
<view class="flex justify-between mt-1 text-sm">
|
||||
<text>底价成本 ¥ {{ baseCost }}</text>
|
||||
<text>提价成本 ¥ {{ raiseCost }}</text>
|
||||
<view class="flex justify-between mt-2 text-sm">
|
||||
<text>底价成本为<text class="text-orange-500 font-medium"> {{ baseCost }} </text>元</text>
|
||||
<text>提价成本为<text class="text-orange-500 font-medium"> {{ raiseCost }} </text>元</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rounded-xl bg-white p-4 mt-3 shadow">
|
||||
<view class="text-base mb-2 text-gray-800">收益与成本说明</view>
|
||||
<text class="text-sm">推广收益 = 客户查询价 - 我的成本</text>
|
||||
<text class="text-sm block mt-1">我的成本 = 实际底价 + 提价成本</text>
|
||||
<text class="text-sm block mt-1">设定范围:¥{{ productConfig?.price_range_min ?? 0 }} - ¥{{ productConfig?.price_range_max ?? 9999 }}</text>
|
||||
<text class="text-sm block mt-1">提价成本:设置{{ productConfig?.price_threshold ?? 0 }}元以上的部分收取{{ rateFormat(productConfig?.price_fee_rate ?? 0) }}的提价成本</text>
|
||||
<text class="text-sm block mt-1">设定范围:<text class="text-orange-500">{{ productConfig?.price_range_min ?? 0 }}</text>元 - <text class="text-orange-500">{{ productConfig?.price_range_max ?? 9999 }}</text>元</text>
|
||||
</view>
|
||||
<wd-button type="primary" block class="mt-4" @click="onConfirm">确认</wd-button>
|
||||
</view>
|
||||
@@ -63,6 +63,10 @@ watch(
|
||||
},
|
||||
)
|
||||
|
||||
function rateFormat(rate) {
|
||||
return (Number(rate) || 0) * 100 + '%'
|
||||
}
|
||||
|
||||
function safeTruncate(num, decimals = 2) {
|
||||
if (isNaN(num) || !isFinite(num)) return '0.00'
|
||||
const factor = 10 ** decimals
|
||||
|
||||
82
src/composables/useBindPhoneLogic.js
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 绑定手机号登录逻辑,与 ycc-proxy-webview BindPhoneDialog.vue「已有平台账号」模式保持一致:
|
||||
* - 发送验证码:auth/sendSms actionType='bindMobile'
|
||||
* - 绑定:/user/bindMobile -> 存 token -> fetchUserInfo + fetchAgentStatus -> 按 isAgent 跳转
|
||||
*/
|
||||
import useApiFetch from '@/composables/useApiFetch'
|
||||
import { bindMobile } from '@/api/user'
|
||||
import { useUserStore } from '@/stores/userStore'
|
||||
import { useAgentStore } from '@/stores/agentStore'
|
||||
|
||||
export function useBindPhoneLogic() {
|
||||
const userStore = useUserStore()
|
||||
const agentStore = useAgentStore()
|
||||
|
||||
/**
|
||||
* 发送绑定手机号验证码(与 BindPhoneDialog sendVerificationCode 一致)
|
||||
* @param {string} mobile - 手机号
|
||||
* @returns {Promise<{ ok: boolean, msg?: string }>}
|
||||
*/
|
||||
async function sendBindSms(mobile) {
|
||||
const { data, error } = await useApiFetch('/auth/sendSms')
|
||||
.post({ mobile, actionType: 'bindMobile' })
|
||||
.json()
|
||||
if (data.value && !error.value && data.value.code === 200) {
|
||||
return { ok: true }
|
||||
}
|
||||
const msg = data.value?.msg || error.value || '发送失败'
|
||||
return { ok: false, msg }
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交绑定手机号并完成登录后流程(与 BindPhoneDialog handleRegister 已有账号分支一致)
|
||||
* - 调用 /user/bindMobile
|
||||
* - 成功:存 token、refreshAfter、accessExpire,拉取用户与代理信息,返回 isAgent 供跳转
|
||||
* @param {string} mobile - 手机号
|
||||
* @param {string} code - 6 位验证码
|
||||
* @returns {Promise<{ success: boolean, isAgent?: boolean, msg?: string }>}
|
||||
*/
|
||||
async function submitBindMobile(mobile, code) {
|
||||
const { data, error } = await bindMobile({ mobile, code })
|
||||
if (!data.value || error.value) {
|
||||
return { success: false, msg: error.value || '绑定失败,请重试' }
|
||||
}
|
||||
if (data.value.code !== 200) {
|
||||
const msg = data.value.msg || '绑定失败'
|
||||
if (msg.includes('已绑定其他微信号')) {
|
||||
return { success: false, msg: '该手机号已绑定其他微信号,一个微信只能绑定一个手机号' }
|
||||
}
|
||||
return { success: false, msg }
|
||||
}
|
||||
|
||||
const d = data.value.data
|
||||
uni.setStorageSync('token', d.accessToken)
|
||||
uni.setStorageSync('refreshAfter', d.refreshAfter)
|
||||
uni.setStorageSync('accessExpire', d.accessExpire)
|
||||
|
||||
await Promise.all([
|
||||
agentStore.fetchAgentStatus(),
|
||||
userStore.fetchUserInfo(),
|
||||
])
|
||||
|
||||
const isAgent = agentStore.isAgent
|
||||
return { success: true, isAgent }
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定成功后按 isAgent 跳转(与 BindPhoneDialog 一致:代理 -> 数据页,否则 -> 首页)
|
||||
*/
|
||||
function redirectAfterBind(isAgent) {
|
||||
if (isAgent) {
|
||||
uni.switchTab({ url: '/pages/agent/index' })
|
||||
} else {
|
||||
uni.switchTab({ url: '/pages/index' })
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sendBindSms,
|
||||
submitBindMobile,
|
||||
redirectAfterBind,
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ const APP_NAME = import.meta.env?.VITE_APP_NAME || '一查查'
|
||||
export const defaultShareTitle = `${APP_NAME} - 专业大数据风控与查询服务`
|
||||
|
||||
/** 默认分享图(5:4 比例,建议 500*400),不传则使用当前页截图 */
|
||||
export const defaultShareImageUrl = '/static/index/banner.png'
|
||||
export const defaultShareImageUrl = '/static/index/01.jpg'
|
||||
|
||||
/**
|
||||
* 获取「分享给好友」的配置
|
||||
|
||||
@@ -59,6 +59,15 @@
|
||||
"navigationBarTitleText": "升级代理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/auth/index",
|
||||
"type": "page",
|
||||
"layout": "PageLayout",
|
||||
"style": {
|
||||
"navigationBarTitleText": "绑定手机号",
|
||||
"navigationStyle": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/help/detail",
|
||||
"type": "page",
|
||||
@@ -116,15 +125,15 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/promote/index",
|
||||
"path": "pages/promote/report",
|
||||
"type": "page",
|
||||
"layout": "HomeLayout",
|
||||
"layout": "PageLayout",
|
||||
"style": {
|
||||
"navigationBarTitleText": "推广"
|
||||
"navigationBarTitleText": "推广报告"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/promote/report",
|
||||
"path": "pages/promote/reportList",
|
||||
"type": "page",
|
||||
"layout": "PageLayout",
|
||||
"style": {
|
||||
@@ -139,14 +148,6 @@
|
||||
"navigationBarTitleText": "我的推广收益"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/register/index",
|
||||
"type": "page",
|
||||
"layout": "PageLayout",
|
||||
"style": {
|
||||
"navigationBarTitleText": "注册成为代理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/report/index",
|
||||
"type": "page",
|
||||
@@ -171,6 +172,14 @@
|
||||
"navigationBarTitleText": "客服"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/teamList/detail",
|
||||
"type": "page",
|
||||
"layout": "PageLayout",
|
||||
"style": {
|
||||
"navigationBarTitleText": "下级详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/teamList/index",
|
||||
"type": "page",
|
||||
@@ -218,12 +227,6 @@
|
||||
"iconPath": "/static/homelayout/index.png",
|
||||
"selectedIconPath": "/static/homelayout/index_active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/promote/index",
|
||||
"text": "推广",
|
||||
"iconPath": "/static/homelayout/promote.png",
|
||||
"selectedIconPath": "/static/homelayout/promote_active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/agent/index",
|
||||
"text": "数据",
|
||||
|
||||
@@ -60,8 +60,8 @@ function toTeamList() {
|
||||
|
||||
function toRegister() {
|
||||
const url = mobile.value
|
||||
? `/pages/register/index?mobile=${encodeURIComponent(mobile.value)}`
|
||||
: '/pages/register/index'
|
||||
? `/pages/auth/index?mobile=${encodeURIComponent(mobile.value)}`
|
||||
: '/pages/auth/index'
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
|
||||
@@ -136,19 +136,19 @@ function toRegister() {
|
||||
</view>
|
||||
<view class="absolute top-3 right-3 flex p-1 rounded-2xl bg-blue-500">
|
||||
<button
|
||||
:class="['px-6 py-2 text-sm font-medium rounded-lg mx-1', myConversionActiveTab === 'daily' ? 'bg-white text-gray-800' : 'text-white']"
|
||||
:class="['px-6 py-2 text-sm font-medium rounded-lg mx-1', myConversionActiveTab === 'daily' ? 'bg-white text-gray-900' : 'text-gray-900']"
|
||||
@click="myConversionActiveTab = 'daily'"
|
||||
>
|
||||
日
|
||||
</button>
|
||||
<button
|
||||
:class="['px-6 py-2 text-sm font-medium rounded-lg mx-1', myConversionActiveTab === 'weekly' ? 'bg-white text-gray-800' : 'text-white']"
|
||||
:class="['px-6 py-2 text-sm font-medium rounded-lg mx-1', myConversionActiveTab === 'weekly' ? 'bg-white text-gray-900' : 'text-gray-900']"
|
||||
@click="myConversionActiveTab = 'weekly'"
|
||||
>
|
||||
周
|
||||
</button>
|
||||
<button
|
||||
:class="['px-6 py-2 text-sm font-medium rounded-lg mx-1', myConversionActiveTab === 'monthly' ? 'bg-white text-gray-800' : 'text-white']"
|
||||
:class="['px-6 py-2 text-sm font-medium rounded-lg mx-1', myConversionActiveTab === 'monthly' ? 'bg-white text-gray-900' : 'text-gray-900']"
|
||||
@click="myConversionActiveTab = 'monthly'"
|
||||
>
|
||||
月
|
||||
@@ -228,19 +228,19 @@ function toRegister() {
|
||||
</view>
|
||||
<view class="absolute top-3 right-3 flex p-1 rounded-2xl bg-blue-500">
|
||||
<button
|
||||
:class="['px-6 py-2 text-sm font-medium rounded-lg mx-1', subordinateConversionActiveTab === 'daily' ? 'bg-white text-gray-800' : 'text-white']"
|
||||
:class="['px-6 py-2 text-sm font-medium rounded-lg mx-1', subordinateConversionActiveTab === 'daily' ? 'bg-white text-gray-900' : 'text-gray-900']"
|
||||
@click="subordinateConversionActiveTab = 'daily'"
|
||||
>
|
||||
日
|
||||
</button>
|
||||
<button
|
||||
:class="['px-6 py-2 text-sm font-medium rounded-lg mx-1', subordinateConversionActiveTab === 'weekly' ? 'bg-white text-gray-800' : 'text-white']"
|
||||
:class="['px-6 py-2 text-sm font-medium rounded-lg mx-1', subordinateConversionActiveTab === 'weekly' ? 'bg-white text-gray-900' : 'text-gray-900']"
|
||||
@click="subordinateConversionActiveTab = 'weekly'"
|
||||
>
|
||||
周
|
||||
</button>
|
||||
<button
|
||||
:class="['px-6 py-2 text-sm font-medium rounded-lg mx-1', subordinateConversionActiveTab === 'monthly' ? 'bg-white text-gray-800' : 'text-white']"
|
||||
:class="['px-6 py-2 text-sm font-medium rounded-lg mx-1', subordinateConversionActiveTab === 'monthly' ? 'bg-white text-gray-900' : 'text-gray-900']"
|
||||
@click="subordinateConversionActiveTab = 'monthly'"
|
||||
>
|
||||
月
|
||||
|
||||
@@ -17,7 +17,7 @@ const { isAgent, level } = storeToRefs(agentStore)
|
||||
const { isLoggedIn } = storeToRefs(userStore)
|
||||
|
||||
function toRegister() {
|
||||
uni.navigateTo({ url: '/pages/register/index' })
|
||||
uni.navigateTo({ url: '/pages/auth/index' })
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -359,7 +359,7 @@ function toService() {
|
||||
|
||||
|
||||
function toRegister() {
|
||||
uni.navigateTo({ url: '/pages/register/index' })
|
||||
uni.navigateTo({ url: '/pages/auth/index' })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
326
src/pages/auth/index.vue
Normal file
@@ -0,0 +1,326 @@
|
||||
<script setup>
|
||||
definePage({
|
||||
layout: 'PageLayout',
|
||||
style: {
|
||||
navigationBarTitleText: '绑定手机号',
|
||||
navigationStyle: 'default',
|
||||
},
|
||||
})
|
||||
|
||||
import { ref, computed, onUnmounted, onMounted } from 'vue'
|
||||
import useApiFetch from '@/composables/useApiFetch'
|
||||
import { useBindPhoneLogic } from '@/composables/useBindPhoneLogic'
|
||||
import { useUserStore } from '@/stores/userStore'
|
||||
import { useAgentStore } from '@/stores/agentStore'
|
||||
import { registerByInviteCode } from '@/api/agent'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const agentStore = useAgentStore()
|
||||
const { sendBindSms, submitBindMobile, redirectAfterBind } = useBindPhoneLogic()
|
||||
|
||||
// 模式:'bind' 已有平台账号 | 'register' 注册成为代理
|
||||
const mode = ref('bind')
|
||||
const phoneNumber = ref('')
|
||||
const verificationCode = ref('')
|
||||
const inviteCode = ref('')
|
||||
const isAgreed = ref(false)
|
||||
const isCountingDown = ref(false)
|
||||
const countdown = ref(60)
|
||||
const submitting = ref(false)
|
||||
let timer = null
|
||||
|
||||
onMounted(() => {
|
||||
const pages = getCurrentPages()
|
||||
const page = pages[pages.length - 1]
|
||||
const options = page?.options || {}
|
||||
|
||||
if (options.invite_code) {
|
||||
inviteCode.value = decodeURIComponent(options.invite_code)
|
||||
mode.value = 'register'
|
||||
}
|
||||
if (options.mobile) {
|
||||
phoneNumber.value = decodeURIComponent(options.mobile)
|
||||
}
|
||||
const token = uni.getStorageSync('token')
|
||||
if (token && userStore.mobile) {
|
||||
phoneNumber.value = userStore.mobile
|
||||
// 已有手机号则默认进入注册模式
|
||||
if (!options.invite_code) {
|
||||
mode.value = 'register'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const isPhoneNumberValid = computed(() => /^1[3-9]\d{9}$/.test(phoneNumber.value))
|
||||
const isInviteCodeValid = computed(() => inviteCode.value.trim().length > 0)
|
||||
|
||||
const canSubmitBind = computed(() =>
|
||||
isPhoneNumberValid.value &&
|
||||
verificationCode.value.length === 6
|
||||
)
|
||||
|
||||
const canSubmitRegister = computed(() =>
|
||||
isPhoneNumberValid.value &&
|
||||
verificationCode.value.length === 6 &&
|
||||
isInviteCodeValid.value &&
|
||||
isAgreed.value
|
||||
)
|
||||
|
||||
async function sendVerificationCode() {
|
||||
if (isCountingDown.value || !isPhoneNumberValid.value) return
|
||||
if (mode.value === 'register' && !isInviteCodeValid.value) {
|
||||
uni.showToast({ title: '请先输入邀请码', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const actionType = mode.value === 'bind' ? 'bindMobile' : 'agentApply'
|
||||
const { data, error } = await useApiFetch('/auth/sendSms')
|
||||
.post({ mobile: phoneNumber.value, actionType })
|
||||
.json()
|
||||
if (data.value && !error.value && data.value.code === 200) {
|
||||
uni.showToast({ title: '验证码已发送', icon: 'success' })
|
||||
startCountdown()
|
||||
} else {
|
||||
uni.showToast({ title: data.value?.msg || error.value || '发送失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
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 handleBind() {
|
||||
if (!canSubmitBind.value) return
|
||||
submitting.value = true
|
||||
try {
|
||||
const { success, isAgent, msg } = await submitBindMobile(phoneNumber.value, verificationCode.value)
|
||||
if (success) {
|
||||
uni.showToast({ title: '绑定成功', icon: 'success' })
|
||||
redirectAfterBind(isAgent)
|
||||
} else {
|
||||
uni.showToast({ title: msg || '绑定失败,请重试', icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
uni.showToast({ title: '绑定失败,请重试', icon: 'none' })
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRegister() {
|
||||
if (!isInviteCodeValid.value) {
|
||||
uni.showToast({ title: '请输入邀请码', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!isPhoneNumberValid.value) {
|
||||
uni.showToast({ title: '请输入有效的手机号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (verificationCode.value.length !== 6) {
|
||||
uni.showToast({ title: '请输入6位验证码', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!isAgreed.value) {
|
||||
uni.showToast({ title: '请先同意用户协议、隐私政策和代理管理协议', icon: 'none' })
|
||||
return
|
||||
}
|
||||
submitting.value = true
|
||||
try {
|
||||
const { data, error } = await registerByInviteCode({
|
||||
mobile: phoneNumber.value,
|
||||
code: verificationCode.value,
|
||||
referrer: inviteCode.value.trim(),
|
||||
})
|
||||
|
||||
if (data.value && !error.value && data.value.code === 200) {
|
||||
uni.setStorageSync('token', data.value.data.accessToken)
|
||||
uni.setStorageSync('refreshAfter', data.value.data.refreshAfter)
|
||||
uni.setStorageSync('accessExpire', data.value.data.accessExpire)
|
||||
if (data.value.data.agent_id) {
|
||||
agentStore.updateAgentInfo({
|
||||
isAgent: true,
|
||||
agentID: data.value.data.agent_id,
|
||||
level: data.value.data.level || 1,
|
||||
levelName: data.value.data.level_name || '普通代理',
|
||||
})
|
||||
}
|
||||
uni.showToast({ title: '注册成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({ url: '/pages/index' })
|
||||
}, 500)
|
||||
} else {
|
||||
uni.showToast({ title: data.value?.msg || error.value || '注册失败,请重试', icon: 'none' })
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('注册失败:', err)
|
||||
uni.showToast({ title: '注册失败,请重试', icon: 'none' })
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function toUserAgreement() {
|
||||
uni.navigateTo({ url: '/pages/userAgreement/index' })
|
||||
}
|
||||
|
||||
function toPrivacyPolicy() {
|
||||
uni.navigateTo({ url: '/pages/privacyPolicy/index' })
|
||||
}
|
||||
|
||||
function toAgentManageAgreement() {
|
||||
uni.navigateTo({ url: '/pages/agentManageAgreement/index' })
|
||||
}
|
||||
|
||||
function toIndex() {
|
||||
uni.switchTab({ url: '/pages/index' })
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
timer = null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="min-h-screen bg-gray-50 px-6 pt-8 pb-12">
|
||||
<view class="flex flex-col items-center mb-6">
|
||||
<image src="/static/logo.png" class="w-16 h-16 mb-4" mode="aspectFit" />
|
||||
<view class="text-center">
|
||||
<view class="text-2xl font-bold text-gray-800 mb-1">绑定 / 注册</view>
|
||||
<view class="text-sm text-gray-500">已有平台账号选择绑定,否则注册成为代理</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 模式切换 -->
|
||||
<view class="flex rounded-xl overflow-hidden bg-white shadow-sm mb-6">
|
||||
<view
|
||||
class="flex-1 py-3 text-center text-sm font-medium"
|
||||
:class="mode === 'bind' ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-600'"
|
||||
@click="mode = 'bind'"
|
||||
>
|
||||
已有平台账号
|
||||
</view>
|
||||
<view
|
||||
class="flex-1 py-3 text-center text-sm font-medium"
|
||||
:class="mode === 'register' ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-600'"
|
||||
@click="mode = 'register'"
|
||||
>
|
||||
注册成为代理
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 绑定模式:重要提示 -->
|
||||
<view v-if="mode === 'bind'" class="bg-amber-50 border border-amber-200 rounded-xl p-4 mb-6">
|
||||
<view class="flex items-start">
|
||||
<text class="text-amber-600 mr-2">⚠</text>
|
||||
<view class="text-xs text-amber-800 leading-relaxed">
|
||||
<text class="font-semibold">重要提示:</text>
|
||||
<text>一个微信只能绑定一个手机号;若该手机号已绑定其他微信号,将无法在此微信登录;请确保输入的是您已注册的手机号。</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="space-y-4 mb-8">
|
||||
<!-- 注册模式:邀请码 -->
|
||||
<view v-if="mode === 'register'">
|
||||
<view class="text-sm text-gray-600 mb-2">邀请码</view>
|
||||
<input
|
||||
v-model="inviteCode"
|
||||
type="text"
|
||||
placeholder="请输入邀请码"
|
||||
class="w-full px-4 py-3 bg-white border border-gray-200 rounded-xl text-base"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view>
|
||||
<view class="text-sm text-gray-600 mb-2">手机号</view>
|
||||
<input
|
||||
v-model="phoneNumber"
|
||||
type="number"
|
||||
placeholder="请输入手机号"
|
||||
maxlength="11"
|
||||
class="w-full px-4 py-3 bg-white border border-gray-200 rounded-xl text-base"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view>
|
||||
<view class="text-sm text-gray-600 mb-2">验证码</view>
|
||||
<view class="flex gap-2">
|
||||
<input
|
||||
v-model="verificationCode"
|
||||
type="number"
|
||||
placeholder="请输入验证码"
|
||||
maxlength="6"
|
||||
class="flex-1 px-4 py-3 bg-white border border-gray-200 rounded-xl text-base"
|
||||
/>
|
||||
<button
|
||||
:disabled="isCountingDown || !isPhoneNumberValid || (mode === 'register' && !isInviteCodeValid)"
|
||||
:class="[
|
||||
'px-4 py-3 rounded-xl text-sm font-medium whitespace-nowrap',
|
||||
(isCountingDown || !isPhoneNumberValid || (mode === 'register' && !isInviteCodeValid))
|
||||
? 'bg-gray-200 text-gray-400'
|
||||
: 'bg-blue-500 text-white'
|
||||
]"
|
||||
@click="sendVerificationCode"
|
||||
>
|
||||
{{ isCountingDown ? `${countdown}s` : '获取验证码' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 注册模式:协议 -->
|
||||
<view v-if="mode === 'register'" class="flex items-start gap-2 pt-2">
|
||||
<checkbox :checked="isAgreed" @click="isAgreed = !isAgreed" />
|
||||
<view class="text-xs text-gray-500 leading-tight flex-1">
|
||||
<text>我已阅读并同意</text>
|
||||
<text class="text-blue-500" @click.stop="toUserAgreement">《用户协议》</text>
|
||||
<text>、</text>
|
||||
<text class="text-blue-500" @click.stop="toPrivacyPolicy">《隐私政策》</text>
|
||||
<text>和</text>
|
||||
<text class="text-blue-500" @click.stop="toAgentManageAgreement">《代理管理协议》</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 绑定模式:提交 -->
|
||||
<button
|
||||
v-if="mode === 'bind'"
|
||||
:disabled="!canSubmitBind || submitting"
|
||||
:class="[
|
||||
'w-full py-3 rounded-full text-base font-bold text-white',
|
||||
canSubmitBind && !submitting ? 'bg-blue-500' : 'bg-gray-300 text-gray-500'
|
||||
]"
|
||||
@click="handleBind"
|
||||
>
|
||||
{{ submitting ? '提交中…' : '绑定手机号' }}
|
||||
</button>
|
||||
|
||||
<!-- 注册模式:提交 -->
|
||||
<button
|
||||
v-if="mode === 'register'"
|
||||
:disabled="!canSubmitRegister || submitting"
|
||||
:class="[
|
||||
'w-full py-3 rounded-full text-base font-bold text-white',
|
||||
canSubmitRegister && !submitting ? 'bg-blue-500' : 'bg-gray-300 text-gray-500'
|
||||
]"
|
||||
@click="handleRegister"
|
||||
>
|
||||
{{ submitting ? '提交中…' : '注册成为代理' }}
|
||||
</button>
|
||||
|
||||
<view class="mt-6 text-center">
|
||||
<text class="text-sm text-gray-400" @click="toIndex">暂不操作,先逛逛</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -27,14 +27,14 @@ function toInvitation() {
|
||||
return
|
||||
} else {
|
||||
uni.showToast({ title: '请先注册成为代理', icon: 'none' })
|
||||
uni.navigateTo({ url: `/pages/register/index?mobile=${encodeURIComponent(mobile.value)}` })
|
||||
uni.navigateTo({ url: `/pages/auth/index?mobile=${encodeURIComponent(mobile.value)}` })
|
||||
}
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url: '/pages/invitation/index' })
|
||||
}
|
||||
|
||||
// 直推报告:进入推广报告页 pages/promote/report
|
||||
// 直推报告:进入推广报告列表页,选择报告后进入对应推广报告页
|
||||
function toDirectReport() {
|
||||
if (!isAgent.value) {
|
||||
if (!isLoggedIn.value) {
|
||||
@@ -42,25 +42,11 @@ function toDirectReport() {
|
||||
return
|
||||
} else {
|
||||
uni.showToast({ title: '请先注册成为代理', icon: 'none' })
|
||||
uni.navigateTo({ url: `/pages/register/index?mobile=${encodeURIComponent(mobile.value)}` })
|
||||
uni.navigateTo({ url: `/pages/auth/index?mobile=${encodeURIComponent(mobile.value)}` })
|
||||
}
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url: '/pages/promote/report' })
|
||||
}
|
||||
|
||||
function toPromote() {
|
||||
if (!isAgent.value) {
|
||||
if (!isLoggedIn.value) {
|
||||
uni.showToast({ title: '正在登录中,请稍候', icon: 'none' })
|
||||
return
|
||||
} else {
|
||||
uni.showToast({ title: '请先注册成为代理', icon: 'none' })
|
||||
uni.navigateTo({ url: `/pages/register/index?mobile=${encodeURIComponent(mobile.value)}` })
|
||||
}
|
||||
return
|
||||
}
|
||||
uni.switchTab({ url: '/pages/promote/index' })
|
||||
uni.navigateTo({ url: '/pages/promote/reportList' })
|
||||
}
|
||||
|
||||
// 一查查公众号:点击弹出二维码,长按可保存或关注
|
||||
@@ -73,14 +59,14 @@ function closeQrcodePopup() {
|
||||
showQrcodePopup.value = false
|
||||
}
|
||||
|
||||
// 微信分享:分享给好友
|
||||
// 微信分享:分享给好友(使用首页头图作为分享图)
|
||||
onShareAppMessage(() => {
|
||||
return getShareAppMessageOptions({ path: 'pages/index' })
|
||||
return getShareAppMessageOptions({ path: 'pages/index', imageUrl: '/static/index/01.jpg' })
|
||||
})
|
||||
|
||||
// 微信分享:分享到朋友圈(基础库 2.11.3+)
|
||||
// 微信分享:分享到朋友圈(使用首页头图作为分享图)
|
||||
onShareTimeline(() => {
|
||||
return getShareTimelineOptions({ query: 'from=timeline' })
|
||||
return getShareTimelineOptions({ query: 'from=timeline', imageUrl: '/static/index/01.jpg' })
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -88,7 +74,7 @@ onShareTimeline(() => {
|
||||
<view class="box-border min-h-screen from-blue-100 to-white bg-gradient-to-b">
|
||||
<!-- 头图保留 -->
|
||||
<view class="relative">
|
||||
<image class="h-full w-full block" mode="widthFix" src="/static/index/n/01.jpg" />
|
||||
<image class="h-full w-full block" mode="widthFix" src="/static/index/01.jpg" />
|
||||
</view>
|
||||
<!-- 卡片一行一个,文字在左、图标在右 -->
|
||||
<view class="p-4 flex flex-col gap-4">
|
||||
@@ -136,7 +122,7 @@ onShareTimeline(() => {
|
||||
>
|
||||
<image
|
||||
class="absolute inset-0 w-full h-full"
|
||||
src="/static/index/n/ycc_search.jpg"
|
||||
src="/static/index/ycc_search.jpg"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="relative z-10 text-white text-center px-4 py-2 rounded-lg bg-black/20 text-sm font-medium">关注公众号 →</view>
|
||||
|
||||
@@ -13,7 +13,6 @@ import { useAgentStore } from '@/stores/agentStore'
|
||||
import { useUserStore } from '@/stores/userStore'
|
||||
import { useEnv } from '@/composables/useEnv'
|
||||
import { getRevenueInfo, realNameAuth } from '@/api/agent'
|
||||
import { bindMobile } from '@/api/user'
|
||||
import useApiFetch from '@/composables/useApiFetch'
|
||||
|
||||
const agentStore = useAgentStore()
|
||||
@@ -23,13 +22,7 @@ const { userName, userAvatar, isLoggedIn, mobile } = storeToRefs(userStore)
|
||||
const { isWeChat } = useEnv()
|
||||
const revenueData = ref(null)
|
||||
const showWithdrawQrPopup = ref(false)
|
||||
const showBindMobilePopup = ref(false)
|
||||
const showRealNameAuthPopup = ref(false)
|
||||
const phoneNumber = ref('')
|
||||
const verificationCode = ref('')
|
||||
const isCountingDown = ref(false)
|
||||
const countdown = ref(60)
|
||||
let countdownTimer = null
|
||||
|
||||
// 实名认证相关
|
||||
const realName = ref('')
|
||||
@@ -152,30 +145,10 @@ function goToRebateDetail() {
|
||||
uni.navigateTo({ url: '/pages/rewardsDetails/index' })
|
||||
}
|
||||
|
||||
function toRegister() {
|
||||
uni.navigateTo({ url: '/pages/register/index' })
|
||||
function goToAuth() {
|
||||
uni.navigateTo({ url: '/pages/auth/index' })
|
||||
}
|
||||
|
||||
function openBindMobilePopup() {
|
||||
showBindMobilePopup.value = true
|
||||
phoneNumber.value = ''
|
||||
verificationCode.value = ''
|
||||
}
|
||||
|
||||
function closeBindMobilePopup() {
|
||||
showBindMobilePopup.value = false
|
||||
phoneNumber.value = ''
|
||||
verificationCode.value = ''
|
||||
if (countdownTimer) {
|
||||
clearInterval(countdownTimer)
|
||||
countdownTimer = null
|
||||
}
|
||||
isCountingDown.value = false
|
||||
countdown.value = 60
|
||||
}
|
||||
|
||||
const isPhoneNumberValid = computed(() => /^1[3-9]\d{9}$/.test(phoneNumber.value))
|
||||
|
||||
// 实名认证表单验证
|
||||
const isRealNamePhoneValid = computed(() => /^1[3-9]\d{9}$/.test(realNamePhoneNumber.value))
|
||||
const isIdCardValid = computed(() => /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(idCard.value))
|
||||
@@ -188,88 +161,6 @@ const canSubmitRealName = computed(() =>
|
||||
isAgreed.value
|
||||
)
|
||||
|
||||
async function sendVerificationCode() {
|
||||
if (isCountingDown.value || !isPhoneNumberValid.value) return
|
||||
if (!isPhoneNumberValid.value) {
|
||||
uni.showToast({ title: '请输入有效的手机号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const { data, error } = await useApiFetch('/auth/sendSms')
|
||||
.post({ mobile: phoneNumber.value, actionType: 'bindMobile' })
|
||||
.json()
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
uni.showToast({ title: '验证码已发送', icon: 'success' })
|
||||
startCountdown()
|
||||
} else {
|
||||
uni.showToast({ title: data.value.msg || '发送失败', icon: 'none' })
|
||||
}
|
||||
} else {
|
||||
uni.showToast({ title: error.value || '发送失败', icon: 'none' })
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('发送验证码失败:', err)
|
||||
uni.showToast({ title: '发送失败,请重试', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
function startCountdown() {
|
||||
isCountingDown.value = true
|
||||
countdown.value = 60
|
||||
countdownTimer = setInterval(() => {
|
||||
if (countdown.value > 0) {
|
||||
countdown.value--
|
||||
} else {
|
||||
clearInterval(countdownTimer)
|
||||
countdownTimer = null
|
||||
isCountingDown.value = false
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
async function handleBindMobile() {
|
||||
if (!isPhoneNumberValid.value) {
|
||||
uni.showToast({ title: '请输入有效的手机号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (verificationCode.value.length !== 6) {
|
||||
uni.showToast({ title: '请输入6位验证码', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const { data, error } = await bindMobile({
|
||||
mobile: phoneNumber.value,
|
||||
code: verificationCode.value,
|
||||
})
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
// 更新 token
|
||||
uni.setStorageSync('token', data.value.data.accessToken)
|
||||
uni.setStorageSync('refreshAfter', data.value.data.refreshAfter)
|
||||
uni.setStorageSync('accessExpire', data.value.data.accessExpire)
|
||||
|
||||
// 刷新用户信息
|
||||
await userStore.fetchUserInfo()
|
||||
|
||||
uni.showToast({ title: '绑定成功', icon: 'success' })
|
||||
closeBindMobilePopup()
|
||||
} else {
|
||||
uni.showToast({ title: data.value.msg || '绑定失败', icon: 'none' })
|
||||
}
|
||||
} else {
|
||||
uni.showToast({ title: error.value || '绑定失败,请重试', icon: 'none' })
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('绑定手机号失败:', err)
|
||||
uni.showToast({ title: '绑定失败,请重试', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
// 实名认证 - 发送验证码
|
||||
async function sendRealNameVerificationCode() {
|
||||
if (isRealNameCountingDown.value || !isRealNamePhoneValid.value) return
|
||||
@@ -390,10 +281,6 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (countdownTimer) {
|
||||
clearInterval(countdownTimer)
|
||||
countdownTimer = null
|
||||
}
|
||||
if (realNameCountdownTimer) {
|
||||
clearInterval(realNameCountdownTimer)
|
||||
realNameCountdownTimer = null
|
||||
@@ -427,21 +314,18 @@ onUnmounted(() => {
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex-1 space-y-1">
|
||||
<view class="text-2xl font-bold text-gray-800">
|
||||
<view class="flex-1 min-w-0 space-y-1 overflow-hidden">
|
||||
<view class="text-2xl font-bold text-gray-800 truncate">
|
||||
{{ !isLoggedIn ? '点击登录' : mobile ? mobile : isWeChat ? '微信用户' : '未绑定手机号' }}
|
||||
</view>
|
||||
<view v-if="isLoggedIn && !mobile && isWeChat" class="text-sm text-blue-500" @click.stop="openBindMobilePopup">
|
||||
<view v-if="isLoggedIn && (!mobile || !isAgent) && isWeChat" class="text-sm text-blue-500" @click.stop="goToAuth">
|
||||
绑定手机号
|
||||
</view>
|
||||
<view v-if="isLoggedIn && mobile && !isAgent" class="text-sm text-blue-500" @click.stop="toRegister">
|
||||
点击申请成为代理
|
||||
</view>
|
||||
<view v-if="isAgent" class="font-bold" :class="levelGradient.text">ID: {{ agentCode }}</view>
|
||||
</view>
|
||||
<view v-if="isAgent" class="text-right">
|
||||
<view v-if="isAgent" class="shrink-0 text-right ml-2">
|
||||
<view class="text-sm mb-1 text-gray-500">余额</view>
|
||||
<view class="text-2xl font-bold text-blue-500">¥ {{ (revenueData?.balance || 0).toFixed(2) }}</view>
|
||||
<view class="text-2xl font-bold text-blue-500 whitespace-nowrap">¥ {{ (revenueData?.balance || 0).toFixed(2) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<template v-if="isAgent">
|
||||
@@ -606,64 +490,6 @@ onUnmounted(() => {
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 绑定手机号弹窗 -->
|
||||
<view v-if="showBindMobilePopup" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50" @click.self="closeBindMobilePopup">
|
||||
<view class="mx-4 w-full max-w-sm rounded-2xl bg-white p-6 shadow-xl" @click.stop>
|
||||
<view class="text-center text-lg font-bold text-gray-800 mb-4">绑定手机号</view>
|
||||
|
||||
<view class="mb-4">
|
||||
<view class="text-sm text-gray-600 mb-2">手机号</view>
|
||||
<input
|
||||
v-model="phoneNumber"
|
||||
type="number"
|
||||
placeholder="请输入手机号"
|
||||
maxlength="11"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg text-base focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="mb-4">
|
||||
<view class="text-sm text-gray-600 mb-2">验证码</view>
|
||||
<view class="flex gap-2">
|
||||
<input
|
||||
v-model="verificationCode"
|
||||
type="number"
|
||||
placeholder="请输入验证码"
|
||||
maxlength="6"
|
||||
class="flex-1 px-4 py-3 border border-gray-300 rounded-lg text-base focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
<button
|
||||
:disabled="isCountingDown || !isPhoneNumberValid"
|
||||
:class="[
|
||||
'px-4 py-3 rounded-lg text-sm font-medium whitespace-nowrap',
|
||||
isCountingDown || !isPhoneNumberValid
|
||||
? 'bg-gray-200 text-gray-400'
|
||||
: 'bg-blue-500 text-white'
|
||||
]"
|
||||
@click="sendVerificationCode"
|
||||
>
|
||||
{{ isCountingDown ? `${countdown}秒` : '获取验证码' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="flex gap-3">
|
||||
<button
|
||||
class="flex-1 rounded-lg py-3 text-base font-medium text-gray-700 bg-gray-100"
|
||||
@click="closeBindMobilePopup"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
class="flex-1 rounded-lg py-3 text-base font-medium text-white bg-blue-500"
|
||||
@click="handleBindMobile"
|
||||
>
|
||||
确定
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 实名认证弹窗 -->
|
||||
<view v-if="showRealNameAuthPopup" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50" @click.self="closeRealNameAuthPopup">
|
||||
<view class="mx-4 w-full max-w-sm rounded-2xl bg-white p-6 shadow-xl max-h-[90vh] overflow-y-auto" @click.stop>
|
||||
|
||||
@@ -1,292 +0,0 @@
|
||||
<script setup>
|
||||
definePage({
|
||||
layout: 'HomeLayout',
|
||||
style: {
|
||||
navigationBarTitleText: '推广',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
import SectionTitle from '@/components/SectionTitle.vue'
|
||||
import { useAgentStore } from '@/stores/agentStore'
|
||||
import { useUserStore } from '@/stores/userStore'
|
||||
import { getProductConfig } from '@/api/agent'
|
||||
import { getShareAppMessageOptions, getShareTimelineOptions } from '@/composables/useWechatShare'
|
||||
|
||||
const agentStore = useAgentStore()
|
||||
const userStore = useUserStore()
|
||||
const { isAgent } = storeToRefs(agentStore)
|
||||
const { isLoggedIn, mobile } = storeToRefs(userStore)
|
||||
|
||||
function toInquire(name) {
|
||||
if (!isAgent.value) {
|
||||
if (!isLoggedIn.value) {
|
||||
uni.showToast({ title: '正在登录中,请稍候', icon: 'none' })
|
||||
return
|
||||
} else {
|
||||
uni.showToast({ title: '请先注册成为代理', icon: 'none' })
|
||||
uni.navigateTo({ url: `/pages/register/index?mobile=${encodeURIComponent(mobile.value)}` })
|
||||
}
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url: `/pages/promote/report?feature=${encodeURIComponent(name)}` })
|
||||
}
|
||||
|
||||
function toInvitation() {
|
||||
if (!isAgent.value) {
|
||||
if (!isLoggedIn.value) {
|
||||
uni.showToast({ title: '正在登录中,请稍候', icon: 'none' })
|
||||
return
|
||||
} else {
|
||||
uni.showToast({ title: '请先注册成为代理', icon: 'none' })
|
||||
uni.navigateTo({ url: `/pages/register/index?mobile=${encodeURIComponent(mobile.value)}` })
|
||||
}
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url: '/pages/invitation/index' })
|
||||
}
|
||||
|
||||
function toPromote() {
|
||||
if (!isAgent.value) {
|
||||
if (!isLoggedIn.value) {
|
||||
uni.showToast({ title: '正在登录中,请稍候', icon: 'none' })
|
||||
return
|
||||
} else {
|
||||
uni.showToast({ title: '请先注册成为代理', icon: 'none' })
|
||||
uni.navigateTo({ url: `/pages/register/index?mobile=${encodeURIComponent(mobile.value)}` })
|
||||
}
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url: '/pages/promote/report' })
|
||||
}
|
||||
|
||||
function toHelp() {
|
||||
uni.navigateTo({ url: '/pages/agentSystemGuide/index' })
|
||||
}
|
||||
|
||||
function toHistory() {
|
||||
uni.navigateTo({ url: '/pages/historyQuery/index' })
|
||||
}
|
||||
|
||||
function toAgent() {
|
||||
if (!isAgent.value) {
|
||||
if (!isLoggedIn.value) {
|
||||
uni.showToast({ title: '正在登录中,请稍候', icon: 'none' })
|
||||
return
|
||||
} else {
|
||||
uni.showToast({ title: '请先注册成为代理', icon: 'none' })
|
||||
uni.navigateTo({ url: `/pages/register/index?mobile=${encodeURIComponent(mobile.value)}` })
|
||||
}
|
||||
return
|
||||
}
|
||||
uni.switchTab({ url: '/pages/agent/index' })
|
||||
}
|
||||
|
||||
function toWithdraw() {
|
||||
if (!isAgent.value) {
|
||||
if (!isLoggedIn.value) {
|
||||
uni.showToast({ title: '正在登录中,请稍候', icon: 'none' })
|
||||
return
|
||||
} else {
|
||||
uni.showToast({ title: '请先注册成为代理', icon: 'none' })
|
||||
uni.navigateTo({ url: `/pages/register/index?mobile=${encodeURIComponent(mobile.value)}` })
|
||||
}
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url: '/pages/withdraw/index' })
|
||||
}
|
||||
|
||||
function toTeamList() {
|
||||
if (!isAgent.value) {
|
||||
if (!isLoggedIn.value) {
|
||||
uni.showToast({ title: '正在登录中,请稍候', icon: 'none' })
|
||||
return
|
||||
} else {
|
||||
uni.showToast({ title: '请先注册成为代理', icon: 'none' })
|
||||
uni.navigateTo({ url: `/pages/register/index?mobile=${encodeURIComponent(mobile.value)}` })
|
||||
}
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url: '/pages/teamList/index' })
|
||||
}
|
||||
|
||||
function toService() {
|
||||
uni.navigateTo({ url: '/pages/service/index' })
|
||||
}
|
||||
|
||||
const services = ref([
|
||||
{ name: 'riskassessment', title: '个人大数据', subtitle: '个人信用 精准查询', bg: '/static/promote/personal_data_bg.png', goColor: '#6699ff', costPrice: null },
|
||||
{ name: 'marriage', title: '情侣报告', subtitle: '相信才能相依 相爱才能永久', bg: '/static/promote/marriage_risk_bg.png', goColor: '#ff99cc', costPrice: null },
|
||||
{ name: 'backgroundcheck', title: '入职背调', subtitle: '查询便可 慧眼识英雄', bg: '/static/promote/backgroundcheck_bg.png', goColor: '#7db3ff', costPrice: null },
|
||||
{ name: 'companyinfo', title: '企业大数据', subtitle: '信任是合作 永恒的基石', bg: '/static/promote/company_bg.png', goColor: '#ffaa66', costPrice: null },
|
||||
{ name: 'homeservice', title: '家政报告', subtitle: '口碑与能力 一查便知', bg: '/static/promote/housekeeping_risk_bg.png', goColor: '#66cccc', costPrice: null },
|
||||
{ name: 'consumerFinanceReport', title: '消金报告', subtitle: '', bg: '/static/promote/consumer_finance_report_bg.png', goColor: '#a259ff', costPrice: null },
|
||||
])
|
||||
|
||||
const allServices = computed(() => services.value)
|
||||
|
||||
function getCostPriceText(service) {
|
||||
if (isLoggedIn.value && isAgent.value && service.costPrice) return `成本价 ${service.costPrice}¥`
|
||||
if (!isLoggedIn.value) return '登录查看'
|
||||
if (!isAgent.value) return '成为代理查看'
|
||||
return '成本价 --'
|
||||
}
|
||||
|
||||
async function fetchProductConfig() {
|
||||
const token = typeof uni !== 'undefined' && uni.getStorageSync ? uni.getStorageSync('token') : ''
|
||||
if (!token) return
|
||||
try {
|
||||
const { data, error } = await getProductConfig()
|
||||
if (data.value && !error.value && data.value.code === 200) {
|
||||
const list = data.value.data?.list || []
|
||||
services.value.forEach((s) => {
|
||||
const config = list.find((item) => item.product_en === s.name)
|
||||
if (config?.actual_base_price != null) s.costPrice = parseFloat(config.actual_base_price).toFixed(2)
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {})
|
||||
watch([isLoggedIn, isAgent], ([loggedIn, agent]) => {
|
||||
if (loggedIn && agent) fetchProductConfig()
|
||||
}, { immediate: true })
|
||||
|
||||
// 推广页头图(单图,已去掉轮播)
|
||||
const bannerImage = '/static/promote/banner_1.png'
|
||||
|
||||
// 微信分享:分享给好友
|
||||
onShareAppMessage(() => {
|
||||
return getShareAppMessageOptions({ path: 'pages/promote/index' })
|
||||
})
|
||||
|
||||
// 微信分享:分享到朋友圈
|
||||
onShareTimeline(() => {
|
||||
return getShareTimelineOptions({ query: 'from=timeline' })
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="box-border from-blue-100 to-white bg-gradient-to-b">
|
||||
<view class="relative" @click="toPromote">
|
||||
<image :src="bannerImage" class="w-full block" mode="widthFix" />
|
||||
</view>
|
||||
<view class="px-6 mt-4">
|
||||
<view class="grid grid-cols-4 gap-3">
|
||||
<view class="text-center flex flex-col justify-center items-center" @click="toPromote">
|
||||
<view class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
|
||||
<image src="/static/promote/tgbg.png" class="h-14 w-14" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="text-center mt-1 font-bold text-sm">推广报告</view>
|
||||
</view>
|
||||
<view class="text-center flex flex-col justify-center items-center" @click="toInvitation">
|
||||
<view class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
|
||||
<image src="/static/promote/yqxj.png" class="h-14 w-14" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="text-center mt-1 font-bold text-sm">邀请下级</view>
|
||||
</view>
|
||||
<view class="text-center flex flex-col justify-center items-center" @click="toHelp">
|
||||
<view class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
|
||||
<image src="/static/promote/bzzx.png" class="h-14 w-14" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="text-center mt-1 font-bold text-sm">帮助中心</view>
|
||||
</view>
|
||||
<view class="text-center flex flex-col justify-center items-center" @click="toHistory">
|
||||
<view class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
|
||||
<image src="/static/promote/wdbg.png" class="h-14 w-14" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="text-center mt-1 font-bold text-sm">我的报告</view>
|
||||
</view>
|
||||
<view class="text-center flex flex-col justify-center items-center" @click="toAgent">
|
||||
<view class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
|
||||
<image src="/static/promote/zc.png" class="h-14 w-14" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="text-center mt-1 font-bold text-sm">资产</view>
|
||||
</view>
|
||||
<view class="text-center flex flex-col justify-center items-center" @click="toWithdraw">
|
||||
<view class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
|
||||
<image src="/static/promote/wytx.png" class="h-14 w-14" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="text-center mt-1 font-bold text-sm">我要提现</view>
|
||||
</view>
|
||||
<view class="text-center flex flex-col justify-center items-center" @click="toTeamList">
|
||||
<view class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
|
||||
<image src="/static/promote/wdxj.png" class="h-14 w-14" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="text-center mt-1 font-bold text-sm">我的团队</view>
|
||||
</view>
|
||||
<view class="text-center flex flex-col justify-center items-center" @click="toService">
|
||||
<view class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
|
||||
<image src="/static/promote/zxkf.png" class="h-14 w-14" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="text-center mt-1 font-bold text-sm">在线客服</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="my-2 mx-4 rounded-xl overflow-hidden shadow-xl" @click="toInvitation">
|
||||
<image src="/static/promote/tghb.png" class="w-full block" mode="widthFix" />
|
||||
</view>
|
||||
<view class="flex items-center justify-between mx-4 mb-2">
|
||||
<SectionTitle title="推广服务" />
|
||||
<view class="text-xs text-gray-500 flex items-center gap-1.5">
|
||||
<text>‹</text>
|
||||
<text>滑动查看更多</text>
|
||||
<text>›</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="relative p-4 pt-0">
|
||||
<scroll-view scroll-x class="services-scroll-container" :show-scrollbar="false">
|
||||
<view class="services-scroll-wrapper">
|
||||
<view
|
||||
v-for="(service, index) in allServices"
|
||||
:key="index"
|
||||
class="relative flex flex-col px-4 py-2 rounded-xl shadow service-card"
|
||||
@click="toInquire(service.name)"
|
||||
>
|
||||
<image
|
||||
:src="service.bg"
|
||||
class="absolute inset-0 w-full h-full rounded-xl"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="relative z-10 flex flex-col items-start flex-1">
|
||||
<view class="mt-1 text-left text-gray-600 font-bold">{{ service.title }}</view>
|
||||
<view
|
||||
class="mt-2 rounded-lg px-2 py-1 text-xs text-white w-max flex items-center"
|
||||
:style="`background-color: ${service.goColor}`"
|
||||
>
|
||||
立即推广
|
||||
<image src="/static/index/go_icon.png" class="ml-0.5 h-3 w-3 inline-block" mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="absolute bottom-0 left-0 right-0 z-10 rounded-b-xl px-2 py-1 text-xs text-white text-center">
|
||||
{{ getCostPriceText(service) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.services-scroll-container {
|
||||
white-space: nowrap;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
.services-scroll-wrapper {
|
||||
display: inline-flex;
|
||||
gap: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
.service-card {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
flex-shrink: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
198
src/pages/promote/reportList.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<script setup>
|
||||
definePage({
|
||||
layout: 'PageLayout',
|
||||
style: {
|
||||
navigationBarTitleText: '推广报告',
|
||||
navigationStyle: 'default',
|
||||
},
|
||||
})
|
||||
|
||||
import { storeToRefs } from 'pinia'
|
||||
import SectionTitle from '@/components/SectionTitle.vue'
|
||||
import { useAgentStore } from '@/stores/agentStore'
|
||||
import { useUserStore } from '@/stores/userStore'
|
||||
|
||||
const agentStore = useAgentStore()
|
||||
const userStore = useUserStore()
|
||||
const { isAgent } = storeToRefs(agentStore)
|
||||
const { isLoggedIn, mobile } = storeToRefs(userStore)
|
||||
|
||||
// 参考 PromotePage 布局:左列个人大数据(2.5)+情侣报告(1.5),右列四张小卡片
|
||||
const personalDataService = {
|
||||
name: 'riskassessment',
|
||||
title: '个人大数据',
|
||||
subtitle: '个人风险 精准查询',
|
||||
bg: '/static/index/05.png',
|
||||
goColor: '#6699ff',
|
||||
}
|
||||
const marriageService = {
|
||||
name: 'marriage',
|
||||
title: '情侣报告',
|
||||
subtitle: '相信才能相依 相爱才能永久',
|
||||
bg: '/static/index/06.png',
|
||||
goColor: '#ff99cc',
|
||||
}
|
||||
// 右列四张:企业大数据、消金报告、家政报告、入职背调
|
||||
const group2 = [
|
||||
{ name: 'companyinfo', title: '企业大数据', subtitle: '信任是合作 永恒的基石', bg: '/static/index/07.png', goColor: '#ffaa66' },
|
||||
{ name: 'consumerFinanceReport', title: '消金报告', subtitle: '', bg: '/static/index/08.png', goColor: '#a259ff', showGo: true },
|
||||
{ name: 'homeservice', title: '家政报告', subtitle: '口碑与能力 一查便知', bg: '/static/index/09.png', goColor: '#66cccc' },
|
||||
{ name: 'backgroundcheck', title: '入职背调', subtitle: '查询便可 慧眼识英雄', bg: '/static/index/10.png', goColor: '#7db3ff' },
|
||||
]
|
||||
|
||||
const formatSubtitle = (subtitle) => {
|
||||
if (!subtitle) return ''
|
||||
const parts = subtitle.split(/\s+/)
|
||||
if (parts.length >= 2) {
|
||||
const mid = Math.ceil(parts.length / 2)
|
||||
const firstLine = parts.slice(0, mid).join(' ')
|
||||
const secondLine = parts.slice(mid).join(' ')
|
||||
return `${firstLine}\n${secondLine}`
|
||||
}
|
||||
return subtitle
|
||||
}
|
||||
|
||||
function toReport(feature) {
|
||||
if (!isAgent.value) {
|
||||
if (!isLoggedIn.value) {
|
||||
uni.showToast({ title: '正在登录中,请稍候', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: '请先注册成为代理', icon: 'none' })
|
||||
uni.navigateTo({ url: `/pages/auth/index?mobile=${encodeURIComponent(mobile.value)}` })
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url: `/pages/promote/report?feature=${encodeURIComponent(feature)}` })
|
||||
}
|
||||
|
||||
function toAgent() {
|
||||
if (!isAgent.value && isLoggedIn.value) {
|
||||
uni.showToast({ title: '请先注册成为代理', icon: 'none' })
|
||||
uni.navigateTo({ url: `/pages/auth/index?mobile=${encodeURIComponent(mobile.value)}` })
|
||||
return
|
||||
}
|
||||
uni.switchTab({ url: '/pages/index' })
|
||||
}
|
||||
|
||||
function toAgentSystemGuide() {
|
||||
uni.navigateTo({ url: '/pages/agentSystemGuide/index' })
|
||||
}
|
||||
|
||||
function toHistory() {
|
||||
uni.navigateTo({ url: '/pages/historyQuery/index' })
|
||||
}
|
||||
|
||||
function toCooperation() {
|
||||
// #ifdef H5
|
||||
window.location.href = 'https://www.tianyuandata.com'
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
uni.navigateTo({ url: '/pages/service/index' })
|
||||
// #endif
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="box-border min-h-screen from-blue-100 to-white bg-gradient-to-b">
|
||||
<view class="relative">
|
||||
<image class="h-full w-full block" mode="widthFix" src="/static/index/banner_1.png" />
|
||||
</view>
|
||||
<view class="px-4 mt-2">
|
||||
<SectionTitle title="推广报告" />
|
||||
</view>
|
||||
<view class="relative p-4 pt-0">
|
||||
<!-- 参考 PromotePage:左列大卡片(个人大数据+情侣报告),右列四张小卡片 -->
|
||||
<view class="flex flex-row gap-3 my-4">
|
||||
<!-- 左侧:个人大数据(2.5) + 情侣报告(1.5) -->
|
||||
<view class="flex-1 flex flex-col gap-3">
|
||||
<view
|
||||
class="relative flex-[2.5] rounded-lg overflow-hidden shadow-md"
|
||||
@click="toReport(personalDataService.name)"
|
||||
>
|
||||
<image :src="personalDataService.bg" class="absolute inset-0 w-full h-full" mode="aspectFill" />
|
||||
<view class="relative z-10 flex flex-col items-start flex-1 px-4 py-2 min-h-40">
|
||||
<view class="mt-1 text-gray-600 font-bold text-lg">{{ personalDataService.title }}</view>
|
||||
<view
|
||||
class="text-left text-sm text-gray-500 leading-relaxed whitespace-pre-line mt-1"
|
||||
style="max-width: calc(100% - 1rem)"
|
||||
>
|
||||
{{ formatSubtitle(personalDataService.subtitle) }}
|
||||
</view>
|
||||
<view
|
||||
class="mt-2 rounded-lg px-2 py-1 text-sm text-white shadow-xl flex items-center"
|
||||
:style="`background-color: ${personalDataService.goColor}`"
|
||||
>
|
||||
GO
|
||||
<text class="ml-1">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="relative flex-[1.5] rounded-lg overflow-hidden shadow-md"
|
||||
@click="toReport(marriageService.name)"
|
||||
>
|
||||
<image :src="marriageService.bg" class="absolute inset-0 w-full h-full" mode="aspectFill" />
|
||||
<view class="relative z-10 flex flex-col items-start flex-1 px-3 py-2 min-h-24">
|
||||
<view class="mt-1 text-gray-600 font-bold text-lg">{{ marriageService.title }}</view>
|
||||
<view
|
||||
class="text-left text-sm text-gray-500 leading-relaxed whitespace-pre-line mt-1"
|
||||
style="max-width: calc(100% - 1rem)"
|
||||
>
|
||||
{{ formatSubtitle(marriageService.subtitle) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧:四张小卡片 -->
|
||||
<view class="flex-1 flex flex-col gap-3">
|
||||
<view
|
||||
v-for="item in group2"
|
||||
:key="item.name"
|
||||
class="relative rounded-lg overflow-hidden shadow-md min-h-28 flex flex-col px-3 py-2"
|
||||
@click="toReport(item.name)"
|
||||
>
|
||||
<image :src="item.bg" class="absolute inset-0 w-full h-full" mode="aspectFill" />
|
||||
<view class="relative z-10 flex flex-col items-start flex-1">
|
||||
<view class="mt-1 text-gray-600 font-bold text-lg">{{ item.title }}</view>
|
||||
<view
|
||||
v-if="item.subtitle"
|
||||
class="text-left text-sm text-gray-500 leading-relaxed whitespace-pre-line mt-1"
|
||||
style="max-width: calc(100% - 1rem)"
|
||||
>
|
||||
{{ formatSubtitle(item.subtitle) }}
|
||||
</view>
|
||||
<view
|
||||
v-if="item.showGo"
|
||||
class="mt-1 rounded-lg px-1.5 py-1 text-xs text-white shadow-xl flex items-center"
|
||||
:style="`background-color: ${item.goColor}`"
|
||||
>
|
||||
GO
|
||||
<text class="ml-0.5">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 我的历史查询记录 -->
|
||||
<view
|
||||
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"
|
||||
>
|
||||
<image class="w-10 h-10 mr-4" src="/static/promote/wdbg.png" mode="aspectFit" />
|
||||
<view class="flex-1">
|
||||
<view class="text-gray-800">我的历史查询记录</view>
|
||||
<view class="text-xs text-gray-500">查询记录有效期为30天</view>
|
||||
</view>
|
||||
<text class="text-gray-400 text-xl">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 卡片点击效果 */
|
||||
view[class*="rounded-lg"]:active {
|
||||
opacity: 0.9;
|
||||
}
|
||||
</style>
|
||||
@@ -1,424 +0,0 @@
|
||||
<script setup>
|
||||
definePage({
|
||||
layout: 'PageLayout',
|
||||
style: {
|
||||
navigationBarTitleText: '注册成为代理',
|
||||
navigationStyle: 'default',
|
||||
},
|
||||
})
|
||||
|
||||
import { ref, computed, onUnmounted, onMounted } from 'vue'
|
||||
import { registerByInviteCode, applyForAgent } from '@/api/agent'
|
||||
import { useAgentStore } from '@/stores/agentStore'
|
||||
import { useUserStore } from '@/stores/userStore'
|
||||
import useApiFetch from '@/composables/useApiFetch'
|
||||
|
||||
const agentStore = useAgentStore()
|
||||
const userStore = useUserStore()
|
||||
const appName = import.meta.env?.VITE_APP_NAME || '全能查'
|
||||
|
||||
const phoneNumber = ref('')
|
||||
const verificationCode = ref('')
|
||||
const inviteCode = ref('')
|
||||
const isAgreed = ref(false)
|
||||
const isCountingDown = ref(false)
|
||||
const countdown = ref(60)
|
||||
const isPhoneDisabled = ref(false)
|
||||
let timer = null
|
||||
|
||||
function fillInviteCode() {
|
||||
inviteCode.value = '16800'
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const pages = getCurrentPages()
|
||||
const page = pages[pages.length - 1]
|
||||
const options = page?.options || {}
|
||||
|
||||
if (options.invite_code) {
|
||||
inviteCode.value = decodeURIComponent(options.invite_code)
|
||||
}
|
||||
if (options.mobile) {
|
||||
phoneNumber.value = decodeURIComponent(options.mobile)
|
||||
isPhoneDisabled.value = true
|
||||
} else {
|
||||
const token = uni.getStorageSync('token')
|
||||
if (token && userStore.mobile) {
|
||||
phoneNumber.value = userStore.mobile
|
||||
isPhoneDisabled.value = true
|
||||
} else if (token) {
|
||||
try {
|
||||
await userStore.fetchUserInfo()
|
||||
if (userStore.mobile) {
|
||||
phoneNumber.value = userStore.mobile
|
||||
isPhoneDisabled.value = true
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const isPhoneNumberValid = computed(() => /^1[3-9]\d{9}$/.test(phoneNumber.value))
|
||||
|
||||
const isInviteCodeValid = computed(() => inviteCode.value.trim().length > 0)
|
||||
|
||||
const canRegister = computed(() =>
|
||||
isPhoneNumberValid.value &&
|
||||
verificationCode.value.length === 6 &&
|
||||
isInviteCodeValid.value &&
|
||||
isAgreed.value
|
||||
)
|
||||
|
||||
async function sendVerificationCode() {
|
||||
if (isCountingDown.value || !isPhoneNumberValid.value) return
|
||||
if (!isPhoneNumberValid.value) {
|
||||
uni.showToast({ title: '请输入有效的手机号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!isInviteCodeValid.value) {
|
||||
uni.showToast({ title: '请先输入邀请码', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const { data, error } = await useApiFetch('auth/sendSms')
|
||||
.post({ mobile: phoneNumber.value, actionType: 'agentApply' })
|
||||
.json()
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
uni.showToast({ title: '获取成功', icon: 'success' })
|
||||
startCountdown()
|
||||
} else {
|
||||
uni.showToast({ title: data.value.msg || '获取失败', icon: 'none' })
|
||||
}
|
||||
} else {
|
||||
uni.showToast({ title: error.value || '获取失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
function startCountdown() {
|
||||
isCountingDown.value = true
|
||||
countdown.value = 60
|
||||
timer = setInterval(() => {
|
||||
if (countdown.value > 0) {
|
||||
countdown.value--
|
||||
} else {
|
||||
clearInterval(timer)
|
||||
isCountingDown.value = false
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
async function handleRegister() {
|
||||
if (!isPhoneNumberValid.value) {
|
||||
uni.showToast({ title: '请输入有效的手机号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!isInviteCodeValid.value) {
|
||||
uni.showToast({ title: '请输入邀请码', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (verificationCode.value.length !== 6) {
|
||||
uni.showToast({ title: '请输入6位验证码', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!isAgreed.value) {
|
||||
uni.showToast({ title: '请先同意用户协议、隐私政策和代理管理协议', icon: 'none' })
|
||||
return
|
||||
}
|
||||
await performRegister()
|
||||
}
|
||||
|
||||
async function performRegister() {
|
||||
try {
|
||||
const { data, error } = await registerByInviteCode({
|
||||
mobile: phoneNumber.value,
|
||||
code: verificationCode.value,
|
||||
referrer: inviteCode.value.trim(),
|
||||
})
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
uni.setStorageSync('token', data.value.data.accessToken)
|
||||
uni.setStorageSync('refreshAfter', data.value.data.refreshAfter)
|
||||
uni.setStorageSync('accessExpire', data.value.data.accessExpire)
|
||||
|
||||
if (data.value.data.agent_id) {
|
||||
agentStore.updateAgentInfo({
|
||||
isAgent: true,
|
||||
agentID: data.value.data.agent_id,
|
||||
level: data.value.data.level || 1,
|
||||
levelName: data.value.data.level_name || '普通代理',
|
||||
})
|
||||
}
|
||||
|
||||
uni.showToast({ title: '注册成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({ url: '/pages/index' })
|
||||
}, 500)
|
||||
} else {
|
||||
uni.showToast({ title: data.value.msg || '注册失败', icon: 'none' })
|
||||
}
|
||||
} else {
|
||||
uni.showToast({ title: error.value || '注册失败,请重试', icon: 'none' })
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('注册失败:', err)
|
||||
uni.showToast({ title: '注册失败,请重试', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
function toUserAgreement() {
|
||||
uni.navigateTo({ url: '/pages/userAgreement/index' })
|
||||
}
|
||||
|
||||
function toPrivacyPolicy() {
|
||||
uni.navigateTo({ url: '/pages/privacyPolicy/index' })
|
||||
}
|
||||
|
||||
function toAgentManageAgreement() {
|
||||
uni.navigateTo({ url: '/pages/agentManageAgreement/index' })
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) clearInterval(timer)
|
||||
})
|
||||
|
||||
function onClickLeft() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="login-layout">
|
||||
<image
|
||||
class="login-bg-image"
|
||||
src="/static/login/login_bg.png"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="login px-4 relative z-10" style="padding-top: 44px;">
|
||||
<view class="mb-8 pt-20 text-left">
|
||||
<view class="flex flex-col items-center">
|
||||
<image class="h-16 w-16 rounded-full shadow block" src="/static/login/logo.png" mode="aspectFit" />
|
||||
<image class="h-12 mt-4 block" src="/static/login/logo_title.png" mode="widthFix" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="login-form">
|
||||
<view class="form-item">
|
||||
<view class="form-label">邀请码</view>
|
||||
<view class="input-with-btn flex-1">
|
||||
<input
|
||||
v-model="inviteCode"
|
||||
class="form-input"
|
||||
type="text"
|
||||
placeholder="请输入邀请码"
|
||||
/>
|
||||
<view class="get-invite-code-btn" @click="fillInviteCode">获取邀请码</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">手机号</view>
|
||||
<input
|
||||
v-model="phoneNumber"
|
||||
class="form-input flex-1"
|
||||
type="number"
|
||||
placeholder="请输入手机号"
|
||||
maxlength="11"
|
||||
:disabled="isPhoneDisabled"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">验证码</view>
|
||||
<view class="verification-input-wrapper flex-1">
|
||||
<input
|
||||
v-model="verificationCode"
|
||||
class="form-input verification-input"
|
||||
type="number"
|
||||
placeholder="请输入验证码"
|
||||
maxlength="6"
|
||||
/>
|
||||
<view
|
||||
class="get-code-btn"
|
||||
:class="{ disabled: isCountingDown || !isPhoneNumberValid || !isInviteCodeValid }"
|
||||
@click="sendVerificationCode"
|
||||
>
|
||||
{{ isCountingDown ? `${countdown}s` : '获取验证码' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="agreement-wrapper">
|
||||
<wd-checkbox v-model="isAgreed" />
|
||||
<view class="agreement-text">
|
||||
我已阅读并同意
|
||||
<text class="agreement-link" @click.stop="toUserAgreement">《用户协议》</text>
|
||||
<text class="agreement-link" @click.stop="toPrivacyPolicy">《隐私政策》</text>
|
||||
和
|
||||
<text class="agreement-link" @click.stop="toAgentManageAgreement">《代理管理协议》</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="notice-text">
|
||||
未注册手机号注册后将自动生成账号并成为代理,并且代表您已阅读并同意
|
||||
</view>
|
||||
|
||||
<button
|
||||
class="login-btn"
|
||||
:class="{ disabled: !canRegister }"
|
||||
:disabled="!canRegister"
|
||||
@click="handleRegister"
|
||||
>
|
||||
注册成为代理
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.login-layout {
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-bg-image {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
background-color: #fff;
|
||||
padding: 2rem;
|
||||
margin-top: 0.5rem;
|
||||
box-shadow: 0 0 24px rgba(63, 63, 63, 0.06);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: none;
|
||||
border-bottom: 1px solid #ebedf0;
|
||||
}
|
||||
|
||||
.input-with-btn {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.input-with-btn .form-input {
|
||||
padding-right: 5rem;
|
||||
}
|
||||
|
||||
.get-invite-code-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
color: #1989fa;
|
||||
font-size: 14px;
|
||||
padding: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 15px;
|
||||
color: #323233;
|
||||
margin-right: 1rem;
|
||||
font-weight: 500;
|
||||
min-width: 4rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 14px 0;
|
||||
font-size: 15px;
|
||||
color: #323233;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.form-input:disabled {
|
||||
color: #969799;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.verification-input-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.verification-input {
|
||||
flex: 1;
|
||||
padding-right: 5rem;
|
||||
}
|
||||
|
||||
.get-code-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
color: #1989fa;
|
||||
font-size: 14px;
|
||||
padding: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.get-code-btn.disabled {
|
||||
color: #c8c9cc;
|
||||
}
|
||||
|
||||
.agreement-wrapper {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.agreement-wrapper :deep(.wd-checkbox) {
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.agreement-text {
|
||||
font-size: 12px;
|
||||
color: #646566;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.agreement-link {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
.notice-text {
|
||||
font-size: 11px;
|
||||
color: #969799;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
background-color: #1989fa;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.25em;
|
||||
}
|
||||
|
||||
.login-btn.disabled {
|
||||
background-color: #c8c9cc;
|
||||
}
|
||||
</style>
|
||||
297
src/pages/teamList/detail.vue
Normal file
@@ -0,0 +1,297 @@
|
||||
<script setup>
|
||||
definePage({
|
||||
layout: 'PageLayout',
|
||||
style: {
|
||||
navigationBarTitleText: '下级详情',
|
||||
navigationStyle: 'default',
|
||||
},
|
||||
})
|
||||
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getSubordinateContributionDetail } from '@/api/agent'
|
||||
|
||||
const subordinateId = ref('')
|
||||
const loading = ref(false)
|
||||
const refreshing = ref(false)
|
||||
const finished = ref(false)
|
||||
const page = ref(1)
|
||||
const pageSize = 8
|
||||
const activeTab = ref(0) // 0=order, 1=invite
|
||||
|
||||
const userInfo = ref({})
|
||||
const orderStats = ref({})
|
||||
const rebateStats = ref({})
|
||||
const inviteStats = ref({})
|
||||
const orderList = ref([])
|
||||
const inviteList = ref([])
|
||||
const initialized = ref(false)
|
||||
|
||||
const tabType = () => (activeTab.value === 0 ? 'order' : 'invite')
|
||||
|
||||
const fetchDetail = async () => {
|
||||
if (!subordinateId.value) return
|
||||
if (loading.value) return
|
||||
if (finished.value && page.value > 1) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const { data, error } = await getSubordinateContributionDetail({
|
||||
subordinate_id: subordinateId.value,
|
||||
page: page.value,
|
||||
page_size: pageSize,
|
||||
tab_type: tabType(),
|
||||
})
|
||||
|
||||
if (data.value && !error.value && data.value.code === 200) {
|
||||
const res = data.value.data
|
||||
if (page.value === 1) {
|
||||
userInfo.value = {
|
||||
createTime: res.create_time,
|
||||
level: res.level_name || '普通',
|
||||
mobile: res.mobile,
|
||||
}
|
||||
orderStats.value = res.order_stats || {}
|
||||
rebateStats.value = res.rebate_stats || {}
|
||||
inviteStats.value = res.invite_stats || {}
|
||||
}
|
||||
|
||||
if (tabType() === 'order') {
|
||||
const items = res.order_list || []
|
||||
if (page.value === 1) {
|
||||
orderList.value = items
|
||||
} else {
|
||||
orderList.value = orderList.value.concat(items)
|
||||
}
|
||||
finished.value = items.length < pageSize
|
||||
} else {
|
||||
const items = res.invite_list || []
|
||||
if (page.value === 1) {
|
||||
inviteList.value = items
|
||||
} else {
|
||||
inviteList.value = inviteList.value.concat(items)
|
||||
}
|
||||
finished.value = items.length < pageSize
|
||||
}
|
||||
if (!finished.value) page.value++
|
||||
} else {
|
||||
finished.value = true
|
||||
}
|
||||
initialized.value = true
|
||||
} finally {
|
||||
loading.value = false
|
||||
refreshing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const switchTab = () => {
|
||||
if (!initialized.value) return
|
||||
page.value = 1
|
||||
finished.value = false
|
||||
fetchDetail()
|
||||
}
|
||||
|
||||
const onRefresh = () => {
|
||||
refreshing.value = true
|
||||
page.value = 1
|
||||
finished.value = false
|
||||
fetchDetail()
|
||||
}
|
||||
|
||||
const onLoadMore = () => {
|
||||
if (!finished.value && !loading.value) {
|
||||
fetchDetail()
|
||||
}
|
||||
}
|
||||
|
||||
const getLevelClass = (level) => {
|
||||
const n = typeof level === 'number' ? level : parseInt(level)
|
||||
switch (n) {
|
||||
case 3:
|
||||
return 'bg-purple-100 text-purple-600'
|
||||
case 2:
|
||||
return 'bg-yellow-100 text-yellow-600'
|
||||
default:
|
||||
return 'bg-gray-100 text-gray-600'
|
||||
}
|
||||
}
|
||||
|
||||
const getLevelName = (level) => {
|
||||
const n = typeof level === 'number' ? level : parseInt(level)
|
||||
const map = { 1: '普通', 2: '黄金', 3: '钻石' }
|
||||
return map[n] || '普通'
|
||||
}
|
||||
|
||||
const formatTime = (str) => {
|
||||
if (!str) return '-'
|
||||
return String(str).split(' ')[0]
|
||||
}
|
||||
|
||||
const formatNumber = (num) => {
|
||||
if (num == null || num === '') return '0.00'
|
||||
return Number(num).toFixed(2)
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
subordinateId.value = options?.id || ''
|
||||
if (subordinateId.value) fetchDetail()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="min-h-screen bg-gray-50">
|
||||
<view v-if="!subordinateId" class="p-8 text-center text-gray-500">参数错误</view>
|
||||
<template v-else>
|
||||
<!-- 用户信息 -->
|
||||
<view class="p-4">
|
||||
<view class="rounded-xl bg-white shadow-sm p-5 mb-4">
|
||||
<view class="flex items-center flex-wrap gap-2 mb-2">
|
||||
<text class="text-lg font-semibold text-gray-800">{{ userInfo.mobile }}</text>
|
||||
<text class="px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-600">
|
||||
{{ userInfo.level }}代理
|
||||
</text>
|
||||
</view>
|
||||
<view class="text-sm text-gray-500">成为下级代理时间:{{ formatTime(userInfo.createTime) }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单统计 -->
|
||||
<view class="rounded-xl bg-white shadow-sm p-4 mb-4">
|
||||
<view class="text-base font-medium text-gray-800 mb-3">订单统计(仅统计有返佣的订单)</view>
|
||||
<view class="grid grid-cols-3 gap-3">
|
||||
<view class="text-center">
|
||||
<view class="text-gray-500 text-sm mb-1">总订单量</view>
|
||||
<view class="text-xl font-semibold text-blue-600">{{ orderStats.total_orders || 0 }}</view>
|
||||
</view>
|
||||
<view class="text-center">
|
||||
<view class="text-gray-500 text-sm mb-1">月订单</view>
|
||||
<view class="text-xl font-semibold text-green-600">{{ orderStats.month_orders || 0 }}</view>
|
||||
</view>
|
||||
<view class="text-center">
|
||||
<view class="text-gray-500 text-sm mb-1">今日订单</view>
|
||||
<view class="text-xl font-semibold text-orange-600">{{ orderStats.today_orders || 0 }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 返佣统计 -->
|
||||
<view class="rounded-xl bg-white shadow-sm p-4 mb-4">
|
||||
<view class="text-base font-medium text-gray-800 mb-3">返佣统计</view>
|
||||
<view class="grid grid-cols-3 gap-3">
|
||||
<view class="text-center">
|
||||
<view class="text-gray-500 text-sm mb-1">总返佣金额</view>
|
||||
<view class="text-xl font-semibold text-blue-600">¥{{ formatNumber(rebateStats.total_rebate_amount) }}</view>
|
||||
</view>
|
||||
<view class="text-center">
|
||||
<view class="text-gray-500 text-sm mb-1">月返佣金额</view>
|
||||
<view class="text-xl font-semibold text-green-600">¥{{ formatNumber(rebateStats.month_rebate_amount) }}</view>
|
||||
</view>
|
||||
<view class="text-center">
|
||||
<view class="text-gray-500 text-sm mb-1">今日返佣金额</view>
|
||||
<view class="text-xl font-semibold text-orange-600">¥{{ formatNumber(rebateStats.today_rebate_amount) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 邀请统计 -->
|
||||
<view class="rounded-xl bg-white shadow-sm p-4 mb-4">
|
||||
<view class="text-base font-medium text-gray-800 mb-3">邀请统计</view>
|
||||
<view class="grid grid-cols-3 gap-3">
|
||||
<view class="text-center">
|
||||
<view class="text-gray-500 text-sm mb-1">总邀请</view>
|
||||
<view class="text-xl font-semibold text-blue-600">{{ inviteStats.total_invites || 0 }}</view>
|
||||
</view>
|
||||
<view class="text-center">
|
||||
<view class="text-gray-500 text-sm mb-1">月邀请</view>
|
||||
<view class="text-xl font-semibold text-green-600">{{ inviteStats.month_invites || 0 }}</view>
|
||||
</view>
|
||||
<view class="text-center">
|
||||
<view class="text-gray-500 text-sm mb-1">今日邀请</view>
|
||||
<view class="text-xl font-semibold text-orange-600">{{ inviteStats.today_invites || 0 }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Tab -->
|
||||
<view class="rounded-xl bg-white shadow-sm overflow-hidden mb-4">
|
||||
<wd-tabs v-model="activeTab" sticky @change="switchTab">
|
||||
<wd-tab title="订单列表">
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="tab-scroll"
|
||||
:refresher-enabled="true"
|
||||
:refresher-triggered="refreshing"
|
||||
@refresherrefresh="onRefresh"
|
||||
@scrolltolower="onLoadMore"
|
||||
>
|
||||
<view class="p-4">
|
||||
<view v-if="orderList.length === 0" class="text-center text-gray-500 py-8">暂无订单记录</view>
|
||||
<view
|
||||
v-else
|
||||
v-for="item in orderList"
|
||||
:key="item.order_no"
|
||||
class="border-b border-gray-200 pb-3 mb-3"
|
||||
>
|
||||
<view class="flex justify-between">
|
||||
<view class="flex-1">
|
||||
<view class="font-medium text-gray-800 mb-1">{{ item.product_name || '未知产品' }}</view>
|
||||
<view class="text-xs text-gray-500 mb-1">订单号:{{ item.order_no }}</view>
|
||||
<view class="text-xs text-gray-500">{{ formatTime(item.create_time) }}</view>
|
||||
</view>
|
||||
<view class="text-right ml-4">
|
||||
<view class="text-sm text-gray-500 mb-1">订单金额</view>
|
||||
<view class="text-base font-semibold text-blue-600 mb-2">¥{{ formatNumber(item.order_amount) }}</view>
|
||||
<view class="text-sm text-gray-500 mb-1">返佣金额</view>
|
||||
<view class="text-base font-semibold text-green-600">¥{{ formatNumber(item.rebate_amount) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="loading && orderList.length > 0" class="py-3 text-center text-gray-400 text-sm">加载中…</view>
|
||||
<view v-else-if="finished && orderList.length > 0" class="py-3 text-center text-gray-400 text-sm">没有更多了</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</wd-tab>
|
||||
<wd-tab title="邀请列表">
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="tab-scroll"
|
||||
:refresher-enabled="true"
|
||||
:refresher-triggered="refreshing"
|
||||
@refresherrefresh="onRefresh"
|
||||
@scrolltolower="onLoadMore"
|
||||
>
|
||||
<view class="p-4">
|
||||
<view v-if="inviteList.length === 0" class="text-center text-gray-500 py-8">暂无邀请记录</view>
|
||||
<view
|
||||
v-else
|
||||
v-for="item in inviteList"
|
||||
:key="item.agent_id"
|
||||
class="border-b border-gray-200 pb-3 mb-3"
|
||||
>
|
||||
<view class="flex justify-between items-center">
|
||||
<view class="flex items-center flex-wrap gap-2 flex-1">
|
||||
<text class="text-base font-semibold text-gray-800">{{ item.mobile }}</text>
|
||||
<text :class="['px-3 py-1 rounded-full text-sm font-medium', getLevelClass(item.level)]">
|
||||
{{ item.level_name || getLevelName(item.level) }}代理
|
||||
</text>
|
||||
</view>
|
||||
<view class="text-xs text-gray-500">{{ formatTime(item.create_time) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="loading && inviteList.length > 0" class="py-3 text-center text-gray-400 text-sm">加载中…</view>
|
||||
<view v-else-if="finished && inviteList.length > 0" class="py-3 text-center text-gray-400 text-sm">没有更多了</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</wd-tab>
|
||||
</wd-tabs>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tab-scroll {
|
||||
height: 400px;
|
||||
}
|
||||
</style>
|
||||
@@ -69,14 +69,38 @@ function loadMore() {
|
||||
loadList(false)
|
||||
}
|
||||
|
||||
|
||||
function toRegister() {
|
||||
uni.navigateTo({ url: '/pages/register/index' })
|
||||
uni.navigateTo({ url: '/pages/auth/index' })
|
||||
}
|
||||
|
||||
function levelName(level) {
|
||||
const map = { 1: '普通代理', 2: '黄金代理', 3: '钻石代理' }
|
||||
return map[level] || '代理'
|
||||
function getLevelName(level) {
|
||||
const levelNum = typeof level === 'number' ? level : parseInt(level)
|
||||
const map = { 1: '普通', 2: '黄金', 3: '钻石' }
|
||||
return map[levelNum] || '普通'
|
||||
}
|
||||
|
||||
function getLevelClass(level) {
|
||||
const levelNum = typeof level === 'number' ? level : parseInt(level)
|
||||
switch (levelNum) {
|
||||
case 3:
|
||||
return 'bg-purple-100 text-purple-600'
|
||||
case 2:
|
||||
return 'bg-yellow-100 text-yellow-600'
|
||||
default:
|
||||
return 'bg-gray-100 text-gray-600'
|
||||
}
|
||||
}
|
||||
|
||||
function formatCount(num) {
|
||||
if (num == null || num === '') return '0'
|
||||
return Number(num).toLocaleString()
|
||||
}
|
||||
|
||||
function viewDetail(item) {
|
||||
const id = item.agent_id || item.id
|
||||
if (id) {
|
||||
uni.navigateTo({ url: `/pages/teamList/detail?id=${id}` })
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -89,99 +113,139 @@ watch(isAgent, (val) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="p-4 min-h-screen bg-gray-50">
|
||||
<view v-if="!isLoggedIn" class="rounded-xl bg-white shadow p-6 text-center">
|
||||
<view class="min-h-screen bg-gray-50">
|
||||
<view v-if="!isLoggedIn" class="rounded-xl bg-white shadow p-6 m-4 text-center">
|
||||
<view class="text-gray-600">正在登录中,请稍候...</view>
|
||||
</view>
|
||||
<view v-else-if="!isAgent" class="rounded-xl bg-white shadow p-6 text-center">
|
||||
<view v-else-if="!isAgent" class="rounded-xl bg-white shadow p-6 m-4 text-center">
|
||||
<view class="text-gray-600 mb-4">注册成为代理后即可查看团队列表</view>
|
||||
<wd-button type="primary" block @click="toRegister">注册成为代理</wd-button>
|
||||
</view>
|
||||
<template v-else>
|
||||
<!-- 统计 -->
|
||||
<view v-if="statistics" class="rounded-xl bg-white shadow p-4 mb-4">
|
||||
<view class="text-base font-semibold text-gray-800 mb-3">团队概览</view>
|
||||
<view class="grid grid-cols-3 gap-3">
|
||||
<view class="text-center p-2 rounded-lg bg-gray-50">
|
||||
<view class="text-lg font-bold text-primary">{{ statistics.total_members || 0 }}</view>
|
||||
<view class="text-xs text-gray-500 mt-1">团队人数</view>
|
||||
</view>
|
||||
<view class="text-center p-2 rounded-lg bg-gray-50">
|
||||
<view class="text-lg font-bold text-primary">{{ statistics.today_new_members || 0 }}</view>
|
||||
<view class="text-xs text-gray-500 mt-1">今日新增</view>
|
||||
</view>
|
||||
<view class="text-center p-2 rounded-lg bg-gray-50">
|
||||
<view class="text-lg font-bold text-primary">{{ statistics.month_new_members || 0 }}</view>
|
||||
<view class="text-xs text-gray-500 mt-1">本月新增</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="mt-3 flex gap-3">
|
||||
<view class="flex-1 text-center p-2 rounded-lg bg-gray-50">
|
||||
<view class="text-sm font-semibold text-gray-700">总收益</view>
|
||||
<view class="text-sm text-primary">¥{{ (statistics.total_earnings ?? 0).toFixed(2) }}</view>
|
||||
</view>
|
||||
<view class="flex-1 text-center p-2 rounded-lg bg-gray-50">
|
||||
<view class="text-sm font-semibold text-gray-700">今日收益</view>
|
||||
<view class="text-sm text-primary">¥{{ (statistics.today_earnings ?? 0).toFixed(2) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索 -->
|
||||
<view class="mb-3">
|
||||
<view class="px-4 pt-4 pb-3">
|
||||
<wd-search
|
||||
v-model="keyword"
|
||||
placeholder="手机号搜索"
|
||||
placeholder="请输入手机号搜索"
|
||||
shape="round"
|
||||
@search="onRefresh"
|
||||
@clear="onRefresh"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 列表 -->
|
||||
<view class="rounded-xl bg-white shadow overflow-hidden">
|
||||
<view v-if="loading && list.length === 0" class="p-6">
|
||||
<wd-skeleton :row="5" />
|
||||
</view>
|
||||
<view v-else-if="list.length === 0" class="p-8 text-center text-gray-500 text-sm">
|
||||
暂无团队成员
|
||||
</view>
|
||||
<view v-else class="divide-y divide-gray-100">
|
||||
<view
|
||||
v-for="item in list"
|
||||
:key="item.agent_id"
|
||||
class="p-4 flex items-center justify-between"
|
||||
>
|
||||
<view class="flex-1 min-w-0">
|
||||
<view class="flex items-center gap-2">
|
||||
<text class="font-medium text-gray-800">{{ item.mobile || '—' }}</text>
|
||||
<text
|
||||
v-if="item.is_direct"
|
||||
class="text-xs px-1.5 py-0.5 rounded bg-primary/10 text-primary"
|
||||
>
|
||||
直推
|
||||
</text>
|
||||
<view class="p-4 pb-8">
|
||||
<!-- 统计 -->
|
||||
<view v-if="statistics" class="rounded-xl bg-white shadow-sm p-4 mb-4">
|
||||
<view class="text-base font-semibold text-gray-800 mb-3">团队概览</view>
|
||||
<view class="grid grid-cols-3 gap-3">
|
||||
<view class="text-center p-2 rounded-lg bg-gray-50">
|
||||
<view class="text-lg font-bold text-primary">{{ statistics.total_members || 0 }}</view>
|
||||
<view class="text-xs text-gray-500 mt-1">团队人数</view>
|
||||
</view>
|
||||
<view class="text-xs text-gray-500 mt-1">
|
||||
{{ levelName(item.level) }} · 加入 {{ item.create_time || '—' }}
|
||||
<view class="text-center p-2 rounded-lg bg-gray-50">
|
||||
<view class="text-lg font-bold text-primary">{{ statistics.today_new_members || 0 }}</view>
|
||||
<view class="text-xs text-gray-500 mt-1">今日新增</view>
|
||||
</view>
|
||||
<view class="text-xs text-gray-500 mt-0.5">
|
||||
返佣 ¥{{ (item.total_rebate_amount ?? 0).toFixed(2) }} · 查询 {{ item.total_queries ?? 0 }} 次
|
||||
<view class="text-center p-2 rounded-lg bg-gray-50">
|
||||
<view class="text-lg font-bold text-primary">{{ statistics.month_new_members || 0 }}</view>
|
||||
<view class="text-xs text-gray-500 mt-1">本月新增</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="mt-3 flex gap-3">
|
||||
<view class="flex-1 text-center p-2 rounded-lg bg-gray-50">
|
||||
<view class="text-sm font-semibold text-gray-700">总收益</view>
|
||||
<view class="text-sm text-primary">¥{{ (statistics.total_earnings ?? 0).toFixed(2) }}</view>
|
||||
</view>
|
||||
<view class="flex-1 text-center p-2 rounded-lg bg-gray-50">
|
||||
<view class="text-sm font-semibold text-gray-700">今日收益</view>
|
||||
<view class="text-sm text-primary">¥{{ (statistics.today_earnings ?? 0).toFixed(2) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 成员列表 -->
|
||||
<view v-if="loading && list.length === 0" class="rounded-xl bg-white shadow p-6">
|
||||
<wd-skeleton :row="5" />
|
||||
</view>
|
||||
<view v-else-if="list.length === 0" class="rounded-xl bg-white shadow p-8 text-center text-gray-500 text-sm">
|
||||
暂无团队成员
|
||||
</view>
|
||||
<view v-else>
|
||||
<view
|
||||
v-for="(item, index) in list"
|
||||
:key="item.agent_id || item.id"
|
||||
class="rounded-xl bg-white shadow-sm p-4 mb-3"
|
||||
>
|
||||
<!-- 顶部信息 -->
|
||||
<view class="flex items-center flex-wrap gap-2 mb-2">
|
||||
<view class="w-6 h-6 flex items-center justify-center bg-gray-200 text-gray-600 rounded text-xs font-medium">
|
||||
{{ index + 1 }}
|
||||
</view>
|
||||
<text class="text-base font-semibold text-gray-800">{{ item.mobile || '—' }}</text>
|
||||
<text
|
||||
:class="['px-2 py-0.5 rounded text-xs font-medium', getLevelClass(item.level)]"
|
||||
>
|
||||
{{ getLevelName(item.level) }}代理
|
||||
</text>
|
||||
<text
|
||||
:class="[
|
||||
'px-2 py-0.5 rounded text-xs font-medium',
|
||||
item.is_direct ? 'bg-green-100 text-green-700' : 'bg-blue-100 text-blue-700',
|
||||
]"
|
||||
>
|
||||
{{ item.is_direct ? '直接' : '间接' }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 加入时间 -->
|
||||
<view class="text-xs text-gray-500 mb-3">加入团队时间:{{ item.create_time || '—' }}</view>
|
||||
|
||||
<!-- 数据统计 -->
|
||||
<view class="grid grid-cols-3 gap-2 mb-3">
|
||||
<view class="text-center p-2 rounded-lg bg-gray-50">
|
||||
<view class="text-[10px] text-gray-500 mb-0.5">今日邀请</view>
|
||||
<view class="text-sm font-semibold text-gray-800">{{ formatCount(item.today_invites ?? 0) }}</view>
|
||||
</view>
|
||||
<view class="text-center p-2 rounded-lg bg-gray-50">
|
||||
<view class="text-[10px] text-gray-500 mb-0.5">本月邀请</view>
|
||||
<view class="text-sm font-semibold text-gray-800">{{ formatCount(item.month_invites ?? 0) }}</view>
|
||||
</view>
|
||||
<view class="text-center p-2 rounded-lg bg-gray-50">
|
||||
<view class="text-[10px] text-gray-500 mb-0.5">邀请总人数</view>
|
||||
<view class="text-sm font-semibold text-gray-800">{{ formatCount(item.total_invites ?? 0) }}</view>
|
||||
</view>
|
||||
<view class="text-center p-2 rounded-lg bg-gray-50">
|
||||
<view class="text-[10px] text-gray-500 mb-0.5">今日查询</view>
|
||||
<view class="text-sm font-semibold text-gray-800">{{ formatCount(item.today_queries ?? 0) }}</view>
|
||||
</view>
|
||||
<view class="text-center p-2 rounded-lg bg-gray-50">
|
||||
<view class="text-[10px] text-gray-500 mb-0.5">本月查询</view>
|
||||
<view class="text-sm font-semibold text-gray-800">{{ formatCount(item.month_queries ?? 0) }}</view>
|
||||
</view>
|
||||
<view class="text-center p-2 rounded-lg bg-gray-50">
|
||||
<view class="text-[10px] text-gray-500 mb-0.5">查询总单量</view>
|
||||
<view class="text-sm font-semibold text-gray-800">{{ formatCount(item.total_queries ?? 0) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 查看详情 -->
|
||||
<view class="flex justify-end">
|
||||
<wd-button size="small" type="primary" @click="viewDetail(item)">
|
||||
查看详情
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="loading && list.length > 0" class="py-3 text-center text-gray-400 text-sm">加载中…</view>
|
||||
<view
|
||||
v-else-if="hasMore && list.length > 0"
|
||||
class="py-3 text-center text-primary text-sm"
|
||||
@click="loadMore"
|
||||
>
|
||||
加载更多
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="loading && list.length > 0" class="p-3 text-center text-gray-400 text-sm">
|
||||
加载中…
|
||||
</view>
|
||||
<view
|
||||
v-else-if="hasMore && list.length > 0"
|
||||
class="p-3 text-center text-primary text-sm"
|
||||
@click="loadMore"
|
||||
>
|
||||
加载更多
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
BIN
src/static/index/01.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
src/static/index/05.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
src/static/index/06.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
src/static/index/07.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/static/index/08.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src/static/index/09.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
src/static/index/10.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 38 KiB |
BIN
src/static/index/banner_1.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 272 B |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 86 KiB |
BIN
src/static/index/ycc_search.jpg
Normal file
|
After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 16 KiB |
BIN
src/static/logo.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
7
uni-pages.d.ts
vendored
@@ -10,6 +10,7 @@ type _LocationUrl =
|
||||
"/pages/agentPromotionQueryList/index" |
|
||||
"/pages/agentSystemGuide/index" |
|
||||
"/pages/agentUpgrade/index" |
|
||||
"/pages/auth/index" |
|
||||
"/pages/help/detail" |
|
||||
"/pages/help/guide" |
|
||||
"/pages/help/index" |
|
||||
@@ -17,13 +18,13 @@ type _LocationUrl =
|
||||
"/pages/invitation/index" |
|
||||
"/pages/me/index" |
|
||||
"/pages/privacyPolicy/index" |
|
||||
"/pages/promote/index" |
|
||||
"/pages/promote/report" |
|
||||
"/pages/promote/reportList" |
|
||||
"/pages/promoteDetails/index" |
|
||||
"/pages/register/index" |
|
||||
"/pages/report/index" |
|
||||
"/pages/rewardsDetails/index" |
|
||||
"/pages/service/index" |
|
||||
"/pages/teamList/detail" |
|
||||
"/pages/teamList/index" |
|
||||
"/pages/upgradeSubordinate/index" |
|
||||
"/pages/userAgreement/index" |
|
||||
@@ -35,7 +36,7 @@ interface NavigateToOptions {
|
||||
interface RedirectToOptions extends NavigateToOptions {}
|
||||
|
||||
interface SwitchTabOptions {
|
||||
url: "/pages/index" | "/pages/promote/index" | "/pages/agent/index" | "/pages/me/index"
|
||||
url: "/pages/index" | "/pages/agent/index" | "/pages/me/index"
|
||||
}
|
||||
|
||||
type ReLaunchOptions = NavigateToOptions | SwitchTabOptions;
|
||||
|
||||