This commit is contained in:
2026-06-29 14:09:46 +08:00
parent 4f041ffa37
commit 7e73312838
23 changed files with 365 additions and 1250 deletions

3
.env
View File

@@ -33,6 +33,9 @@ VITE_SITE_ORIGIN=https://chimei.ronsafe.cn
# 报告 H5 根地址(可选,优先与 VITE_SITE_ORIGIN 配合使用)
VITE_REPORT_BASE_URL=https://chimei.ronsafe.cn
# 与 bdrp-webview `.env` 中 VITE_TOKEN_VERSION 保持一致(外部浏览器带参登录)
VITE_TOKEN_VERSION=1.0
# `src/pages/not-found.vue` 必需
VITE_SEO_SITE_NAME=赤眉

View File

@@ -0,0 +1,32 @@
<script setup lang="ts">
import { buildAgentVipApplyH5Url } from '@/composables/useAgentVipH5'
import { ensureCurrentPageAccess } from '@/composables/useNavigationAuthGuard'
definePage({ layout: false, auth: true })
const src = ref('')
onLoad((query) => {
const params: Record<string, string> = {}
const type = query?.type
if (type) {
const normalized = String(type).toLowerCase()
if (normalized === 'vip' || normalized === 'svip')
params.type = normalized
}
src.value = buildAgentVipApplyH5Url(params)
if (!src.value)
uni.showToast({ title: '未配置 H5 站点地址', icon: 'none' })
})
onShow(() => {
ensureCurrentPageAccess('redirect')
})
</script>
<template>
<web-view v-if="src" :src="src" />
<view v-else class="flex min-h-screen items-center justify-center bg-gray-50 px-6 text-center text-sm text-gray-500">
无法打开会员升级页面请检查 VITE_SITE_ORIGIN 配置
</view>
</template>

View File

@@ -1,6 +1,6 @@
<script setup>
import { ref } from 'vue'
import InquireForm from '@/components/InquireForm.vue'
import InquireForm from './components/InquireForm.vue'
definePage({ layout: 'default' })

View File

@@ -1,7 +1,7 @@
import { defineManifestConfig } from '@uni-helper/vite-plugin-uni-manifest'
export default defineManifestConfig({
'name': '赤眉',
'name': '戎行赤眉',
'appid': 'H5A4E639E',
'description': '',
'versionName': '1.0.0',
@@ -36,19 +36,40 @@ export default defineManifestConfig({
},
"Payment": {},
"UIWebview": {},
"Camera": {}
},
/* 应用发布信息 */
distribute: {
/* android打包配置 */
android: {
permissions: [
'<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>',
'<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>',
'<uses-feature android:name="android.hardware.camera.autofocus"/>',
permissionExternalStorage: {
request: 'none',
},
permissionPhoneState: {
request: 'none',
},
// 云打包默认已含 INTERNET / 网络状态 / 读写存储等,此处只配置「强制移除」与启动申请策略,避免 permissions 重复合并失败
excludePermissions: [
'<uses-permission android:name="android.permission.CAMERA"/>',
'<uses-feature android:name="android.hardware.camera"/>',
'<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>',
'<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>',
'<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>',
'<uses-permission android:name="android.permission.READ_PHONE_STATE"/>',
'<uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>',
'<uses-permission android:name="android.permission.READ_PRECISE_PHONE_STATE"/>',
'<uses-permission android:name="android.permission.READ_BASIC_PHONE_STATE"/>',
'<uses-permission android:name="android.permission.CALL_PHONE"/>',
'<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS"/>',
'<uses-permission android:name="android.permission.READ_CALL_LOG"/>',
'<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>',
'<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>',
'<uses-permission android:name="android.permission.CALL_PRIVILEGED"/>',
'<uses-permission android:name="android.permission.ADD_VOICEMAIL"/>',
'<uses-permission android:name="android.permission.READ_VOICEMAIL"/>',
'<uses-permission android:name="android.permission.WRITE_VOICEMAIL"/>',
'<uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/>',
'<uses-permission android:name="android.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER"/>',
],
"targetSdkVersion": 30
},
/* ios打包配置 */
"ios": {

View File

@@ -12,14 +12,11 @@ export default defineUniPages({
{ path: 'pages/agent-rewards-details', auth: true, style: { navigationBarTitleText: '奖励明细' } },
{ path: 'pages/agent-service-agreement', style: { navigationBarTitleText: '信息技术服务合同', navigationStyle: 'default' } },
{ path: 'pages/agent-vip', auth: true, style: { navigationBarTitleText: '代理会员' } },
{ path: 'pages/agent-vip-apply', auth: true, style: { navigationBarTitleText: 'VIP代理申请' } },
{ path: 'pages/agent-vip-config', auth: true, style: { navigationBarTitleText: '代理报告配置' } },
{ path: 'pages/authorization', style: { navigationBarTitleText: '授权书', navigationStyle: 'default' } },
{ path: 'pages/help', style: { navigationBarTitleText: '帮助中心' } },
{ path: 'pages/help-detail', style: { navigationBarTitleText: '帮助详情' } },
{ path: 'pages/help-guide', style: { navigationBarTitleText: '引导指南' } },
{ path: 'pages/history-query', auth: true, style: { navigationBarTitleText: '历史报告' } },
{ path: 'pages/inquire', style: { navigationBarTitleText: '查询报告' } },
{ path: 'pages/invitation', auth: true, style: { navigationBarTitleText: '邀请下级' } },
{ path: 'pages/invitation-agent-apply', auth: true, style: { navigationBarTitleText: '代理申请' } },
{ path: 'pages/login', style: { navigationBarTitleText: '登录' } },

12
src/auto-imports.d.ts vendored
View File

@@ -10,8 +10,10 @@ declare global {
const ZoomAdapter: typeof import('./utils/zoomAdapter.js')['ZoomAdapter']
const aesDecrypt: typeof import('./utils/crypto.js')['aesDecrypt']
const aesEncrypt: typeof import('./utils/crypto.js')['aesEncrypt']
const appendAppAuthQueryParams: typeof import('./composables/useReportWebview')['appendAppAuthQueryParams']
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
const buildAgentVipApplyH5Url: typeof import('./composables/useAgentVipH5')['buildAgentVipApplyH5Url']
const calculatePromotionPricing: typeof import('./utils/promotionPricing.js')['calculatePromotionPricing']
const chatCrypto: typeof import('./utils/chatCrypto.js')['default']
const chatEncrypt: typeof import('./utils/chatEncrypt.js')['default']
@@ -78,6 +80,7 @@ declare global {
const markRaw: typeof import('vue')['markRaw']
const navigateLogin: typeof import('./utils/navigate')['navigateLogin']
const navigateTo: typeof import('./utils/navigate')['navigateTo']
const navigateToAgentVipApplyH5: typeof import('./composables/useAgentVipH5')['navigateToAgentVipApplyH5']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onAddToFavorites: typeof import('@dcloudio/uni-app')['onAddToFavorites']
@@ -122,7 +125,9 @@ declare global {
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
const openAgentVipApplyInExternalBrowser: typeof import('./composables/useAgentVipH5')['openAgentVipApplyInExternalBrowser']
const openCustomerService: typeof import('./composables/useCustomerService')['openCustomerService']
const openExternalUrl: typeof import('./composables/openExternalUrl')['openExternalUrl']
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
const provide: typeof import('vue')['provide']
const provideLocal: typeof import('@vueuse/core')['provideLocal']
@@ -156,6 +161,7 @@ declare global {
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const shouldSilencePrivacyBlockedRequest: typeof import('./composables/usePrivacyConsent')['shouldSilencePrivacyBlockedRequest']
const syncRef: typeof import('@vueuse/core')['syncRef']
const syncRefs: typeof import('@vueuse/core')['syncRefs']
const templateRef: typeof import('@vueuse/core')['templateRef']
@@ -408,8 +414,10 @@ declare module 'vue' {
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly aesDecrypt: UnwrapRef<typeof import('./utils/crypto.js')['aesDecrypt']>
readonly aesEncrypt: UnwrapRef<typeof import('./utils/crypto.js')['aesEncrypt']>
readonly appendAppAuthQueryParams: UnwrapRef<typeof import('./composables/useReportWebview')['appendAppAuthQueryParams']>
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
readonly buildAgentVipApplyH5Url: UnwrapRef<typeof import('./composables/useAgentVipH5')['buildAgentVipApplyH5Url']>
readonly calculatePromotionPricing: UnwrapRef<typeof import('./utils/promotionPricing.js')['calculatePromotionPricing']>
readonly clearAuthStorage: UnwrapRef<typeof import('./utils/storage')['clearAuthStorage']>
readonly clearToken: UnwrapRef<typeof import('./utils/storage')['clearToken']>
@@ -474,6 +482,7 @@ declare module 'vue' {
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly navigateLogin: UnwrapRef<typeof import('./utils/navigate')['navigateLogin']>
readonly navigateTo: UnwrapRef<typeof import('./utils/navigate')['navigateTo']>
readonly navigateToAgentVipApplyH5: UnwrapRef<typeof import('./composables/useAgentVipH5')['navigateToAgentVipApplyH5']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
readonly onAddToFavorites: UnwrapRef<typeof import('@dcloudio/uni-app')['onAddToFavorites']>
@@ -518,7 +527,9 @@ declare module 'vue' {
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
readonly onWatcherCleanup: UnwrapRef<typeof import('vue')['onWatcherCleanup']>
readonly openAgentVipApplyInExternalBrowser: UnwrapRef<typeof import('./composables/useAgentVipH5')['openAgentVipApplyInExternalBrowser']>
readonly openCustomerService: UnwrapRef<typeof import('./composables/useCustomerService')['openCustomerService']>
readonly openExternalUrl: UnwrapRef<typeof import('./composables/openExternalUrl')['openExternalUrl']>
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
@@ -552,6 +563,7 @@ declare module 'vue' {
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly shouldSilencePrivacyBlockedRequest: UnwrapRef<typeof import('./composables/usePrivacyConsent')['shouldSilencePrivacyBlockedRequest']>
readonly syncRef: UnwrapRef<typeof import('@vueuse/core')['syncRef']>
readonly syncRefs: UnwrapRef<typeof import('@vueuse/core')['syncRefs']>
readonly templateRef: UnwrapRef<typeof import('@vueuse/core')['templateRef']>

1
src/components.d.ts vendored
View File

@@ -12,7 +12,6 @@ declare module 'vue' {
BindPhoneDialog: typeof import('./components/BindPhoneDialog.vue')['default']
EmptyState: typeof import('./components/EmptyState.vue')['default']
ImageSaveGuide: typeof import('./components/ImageSaveGuide.vue')['default']
InquireForm: typeof import('./components/InquireForm.vue')['default']
LoginDialog: typeof import('./components/LoginDialog.vue')['default']
Payment: typeof import('./components/Payment.vue')['default']
PriceInputPopup: typeof import('./components/PriceInputPopup.vue')['default']

View File

@@ -1,6 +1,8 @@
<script setup>
import { openAgentVipApplyInExternalBrowser } from '@/composables/useAgentVipH5'
function toAgentVip() {
uni.navigateTo({ url: '/pages/agent-vip-apply' })
openAgentVipApplyInExternalBrowser()
}
</script>

View File

@@ -0,0 +1,46 @@
/** 在系统默认浏览器中打开链接App 用 plusH5 开发调试用 window.open */
function redactUrlForLog(url: string): string {
try {
const u = new URL(url)
for (const key of ['token', 'refreshAfter', 'accessExpire']) {
if (u.searchParams.has(key))
u.searchParams.set(key, '***')
}
return u.toString()
}
catch {
return '[invalid-url]'
}
}
export function openExternalUrl(url: string): boolean {
if (!url)
return false
// #ifdef APP-PLUS
try {
if (typeof plus !== 'undefined' && plus.runtime?.openURL) {
plus.runtime.openURL(url, (err: unknown) => {
console.error('[openExternalUrl] 打开失败', err, redactUrlForLog(url))
uni.showToast({ title: '无法打开浏览器', icon: 'none' })
})
return true
}
}
catch (e) {
console.error('[openExternalUrl] 异常', e)
}
uni.showToast({ title: '当前环境无法打开外部浏览器', icon: 'none' })
return false
// #endif
// #ifdef H5
window.open(url, '_blank')
return true
// #endif
// #ifndef APP-PLUS || H5
uni.showToast({ title: '请使用 App 打开', icon: 'none' })
return false
// #endif
}

View File

@@ -6,7 +6,7 @@ import { pages } from 'virtual:uni-pages'
const pathToPage: Record<string, string> = {
'/': '/pages/index',
'/login': '/pages/login',
'/historyQuery': '/pages/history-query',
'/historyQuery': '/pages/index',
'/help': '/pages/help',
'/help/detail': '/pages/help-detail',
'/help/guide': '/pages/help-guide',
@@ -35,7 +35,7 @@ const nameToPage: Record<string, string> = {
invitation: '/pages/invitation',
promote: '/pages/promote',
agent: '/pages/agent',
history: '/pages/history-query',
history: '/pages/index',
help: '/pages/help',
helpDetail: '/pages/help-detail',
helpGuide: '/pages/help-guide',
@@ -70,7 +70,7 @@ export function resolveWebToUni(to: string | { name?: string, path?: string, que
if (pathOnly.startsWith('/inquire/')) {
const feature = pathOnly.replace(/^\/inquire\//, '')
const base = `/pages/inquire?feature=${encodeURIComponent(feature)}`
const base = `/pages/promote?feature=${encodeURIComponent(feature)}`
return queryPart ? `${base}&${queryPart}` : base
}
if (pathOnly.startsWith('/agent/invitationAgentApply/')) {
@@ -142,12 +142,31 @@ const pageMetaByRoute = new Map(
(pages as UniPageMeta[]).map(page => [normalizeUniRoute(page.path), page]),
)
function resolvePageMetaByRoute(route: string): UniPageMeta | undefined {
const normalized = normalizeUniRoute(route)
const candidates = [
normalized,
normalized.startsWith('pages/') ? normalized.slice('pages/'.length) : `pages/${normalized}`,
]
for (const key of candidates) {
const meta = pageMetaByRoute.get(key)
if (meta)
return meta
}
return undefined
}
export function getCurrentPageMeta(): UniPageMeta | undefined {
return pageMetaByRoute.get(getCurrentUniRoute())
return resolvePageMetaByRoute(getCurrentUniRoute())
}
export function getPageTitleByRoute(route = getCurrentUniRoute()): string {
return pageMetaByRoute.get(normalizeUniRoute(route))?.style?.navigationBarTitleText || 'BDRP'
const meta = resolvePageMetaByRoute(route)
const raw = meta?.style?.navigationBarTitleText
if (typeof raw === 'string' && raw.trim())
return raw.trim()
const appName = import.meta.env.VITE_APP_NAME
return (typeof appName === 'string' && appName.trim()) ? appName.trim() : 'BDRP'
}
export function getLayoutPageTitle(): string {
@@ -163,7 +182,6 @@ const UNI_TO_WEB_NOTIFY_PATHS: Record<string, string[]> = {
'pages/agent': ['/agent'],
'pages/me': ['/me'],
'pages/promote': ['/agent/promote'],
'pages/history-query': ['/historyQuery'],
'pages/help': ['/help'],
'pages/help-detail': ['/help/detail'],
'pages/help-guide': ['/help/guide'],
@@ -176,7 +194,6 @@ const UNI_TO_WEB_NOTIFY_PATHS: Record<string, string[]> = {
'pages/agent-service-agreement': ['/agentSerivceAgreement'],
'pages/authorization': ['/authorization'],
'pages/payment-result': ['/payment/result'],
'pages/inquire': ['/inquire'],
'pages/login': ['/login'],
'pages/invitation': ['/agent/invitation'],
'pages/agent-promote-details': ['/agent/promoteDetails'],
@@ -202,8 +219,8 @@ export function getWebPathsForNotification(): string[] {
const q: Record<string, string> = { ...(page?.options || {}) }
// 动态路由:带参数的路径
if (r === 'pages/inquire' && q.feature)
return [`/inquire/${q.feature}`]
if (r === 'pages/promote' && q.feature)
return [`/inquire/${q.feature}`, '/agent/promote']
if (r === 'pages/subordinate-detail' && q.id)
return [`/agent/subordinateDetail/${q.id}`]
if (r === 'pages/invitation-agent-apply' && q.linkIdentifier)

View File

@@ -0,0 +1,58 @@
import { openExternalUrl } from '@/composables/openExternalUrl'
import { useReportWebview } from '@/composables/useReportWebview'
import { getToken } from '@/utils/storage'
const VIP_APPLY_H5_PATH = '/agent/vipApply'
const CONFIRM_TITLE = '前往浏览器完成升级'
const CONFIRM_CONTENT = '代理会员开通/续费将在系统浏览器中完成,与 App 支付完全分离。是否继续打开?'
export function buildAgentVipApplyH5Url(params: Record<string, string> = {}) {
const { buildSitePathUrl } = useReportWebview()
return buildSitePathUrl(VIP_APPLY_H5_PATH, params)
}
function buildTypeQuery(options?: { type?: 'vip' | 'svip' | 'VIP' | 'SVIP' }) {
const query: Record<string, string> = {}
const rawType = options?.type
if (rawType) {
const normalized = String(rawType).toUpperCase()
if (normalized === 'VIP' || normalized === 'SVIP')
query.type = normalized.toLowerCase()
}
return query
}
/**
* 在系统浏览器打开 H5 会员页(带 App 登录态),打开前需用户确认。
*/
export function openAgentVipApplyInExternalBrowser(options?: { type?: 'vip' | 'svip' | 'VIP' | 'SVIP' }) {
if (!getToken()) {
uni.showToast({ title: '请先登录', icon: 'none' })
uni.navigateTo({ url: '/pages/login' })
return false
}
const h5Url = buildAgentVipApplyH5Url(buildTypeQuery(options))
if (!h5Url) {
uni.showToast({ title: '未配置 H5 站点地址', icon: 'none' })
return false
}
uni.showModal({
title: CONFIRM_TITLE,
content: CONFIRM_CONTENT,
confirmText: '继续',
cancelText: '取消',
success: (res) => {
if (res.confirm)
openExternalUrl(h5Url)
},
})
return true
}
/** @deprecated 请使用 openAgentVipApplyInExternalBrowser */
export function navigateToAgentVipApplyH5(options?: { type?: 'vip' | 'svip' | 'VIP' | 'SVIP' }) {
return openAgentVipApplyInExternalBrowser(options)
}

View File

@@ -18,7 +18,6 @@ const HOME_URL = '/pages/index'
*/
const ROUTE_PERMISSION_MAP: Record<string, PagePermission> = {
'pages/promote': { requiresAuth: true, requiresAgent: true },
'pages/history-query': { requiresAuth: true },
'pages/withdraw': { requiresAuth: true, requiresAgent: true },
'pages/payment-result': { requiresAuth: true },
'pages/report-result-webview': { requiresAuth: true },

View File

@@ -3,6 +3,27 @@ import { getToken } from '@/utils/storage'
declare function getCurrentPages(): any[]
/** 与 bdrp-webview `VITE_TOKEN_VERSION` 保持一致,供外部浏览器 / WebView 带参登录 */
function getAppTokenVersionForH5(): string {
return String(import.meta.env.VITE_TOKEN_VERSION || '1.0')
}
/** 外部浏览器打开 H5 时携带 App 登录态(须与 webview `applyAppWebViewTokenFromRoute` 对齐) */
export function appendAppAuthQueryParams(params: Record<string, string> = {}): Record<string, string> {
const merged: Record<string, string> = { ...params, source: 'app' }
const token = getToken()
if (token)
merged.token = token
const refreshAfter = uni.getStorageSync('refreshAfter')
const accessExpire = uni.getStorageSync('accessExpire')
if (refreshAfter != null && refreshAfter !== '')
merged.refreshAfter = String(refreshAfter)
if (accessExpire != null && accessExpire !== '')
merged.accessExpire = String(accessExpire)
merged.tokenVersion = getAppTokenVersionForH5()
return merged
}
/** App 端部分 WebView 无 URLSearchParams用手动编码保证兼容 */
function buildQueryString(params: Record<string, string>): string {
const parts: string[] = []
@@ -20,46 +41,37 @@ export function useReportWebview() {
}
const buildReportUrl = (page: 'example' | 'report', params: Record<string, string> = {}) => {
const token = getToken() || ''
const base = resolveBase()
if (!base) {
console.warn('[report webview] 请配置 VITE_SITE_ORIGIN 或 VITE_REPORT_BASE_URL')
return ''
}
const merged: Record<string, string> = { source: 'app', ...params }
if (token)
merged.token = token
const merged = appendAppAuthQueryParams(params)
const search = buildQueryString(merged)
/** 使用 H5 无顶栏的 App 专用路由,避免 PageLayout 与 App 原生导航栏重复 */
return `${base}/app/${page === 'example' ? 'example' : 'report'}?${search}`
}
const buildReportShareUrl = (linkIdentifier: string, params: Record<string, string> = {}) => {
const token = getToken() || ''
const base = resolveBase()
if (!base) {
console.warn('[report webview] 请配置 VITE_SITE_ORIGIN 或 VITE_REPORT_BASE_URL')
return ''
}
const merged: Record<string, string> = { source: 'app', ...params }
if (token)
merged.token = token
const merged = appendAppAuthQueryParams(params)
const search = buildQueryString(merged)
const encodedLink = encodeURIComponent(linkIdentifier)
return `${base}/report/share/${encodedLink}${search ? `?${search}` : ''}`
}
const buildSitePathUrl = (path: string, params: Record<string, string> = {}) => {
const token = getToken() || ''
const base = resolveBase()
if (!base) {
console.warn('[site webview] 请配置 VITE_SITE_ORIGIN 或 VITE_REPORT_BASE_URL')
return ''
}
const normalizedPath = path.startsWith('/') ? path : `/${path}`
const merged: Record<string, string> = { source: 'app', ...params }
if (token)
merged.token = token
const merged = appendAppAuthQueryParams(params)
const search = buildQueryString(merged)
return `${base}${normalizedPath}${search ? `?${search}` : ''}`
}

View File

@@ -1,6 +1,6 @@
<script setup>
import { onShow } from '@dcloudio/uni-app'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { computed, nextTick, onMounted, ref } from 'vue'
import {
getCurrentUniRoute,
getLayoutPageTitle,
@@ -11,10 +11,13 @@ import { ensurePrivacyConsentIfNeeded } from '@/composables/usePrivacyConsent'
import { useGlobalNotification } from '@/composables/useGlobalNotification'
const router = useRouter()
const pageTitle = ref('赤眉')
const currentRoute = ref('')
const safeAreaTop = ref(0)
const immersiveNavbarSolid = ref(false)
/** 用于在 onShow 时触发标题 computed 重新读取页面栈 */
const routeRevision = ref(0)
const currentRoute = computed(() => {
routeRevision.value
return getCurrentUniRoute()
})
const pageTitle = computed(() => getLayoutPageTitle())
const {
showPopup,
@@ -25,24 +28,10 @@ const {
onClosePopup,
} = useGlobalNotification()
const immersiveRoutes = new Set([
'pages/inquire',
])
const isImmersivePage = computed(() => immersiveRoutes.has(currentRoute.value))
const immersiveNavbarClass = computed(() => (
immersiveNavbarSolid.value ? 'immersive-wd-navbar is-solid' : 'immersive-wd-navbar'
))
const immersiveNavbarStyle = computed(() => (
immersiveNavbarSolid.value
? 'background: rgba(255,255,255,0.96); box-shadow: 0 2px 12px rgba(15, 23, 42, 0.08);'
: 'background: transparent; box-shadow: none;'
))
function syncTitle() {
currentRoute.value = getCurrentUniRoute()
pageTitle.value = getLayoutPageTitle()
immersiveNavbarSolid.value = false
nextTick(() => {
routeRevision.value += 1
})
}
function buildCurrentPageUrl() {
@@ -60,15 +49,6 @@ function ensureAuthIfNeeded() {
ensurePageAccessByUrl(buildCurrentPageUrl(), 'redirect')
}
function getSafeAreaTop() {
uni.getSystemInfo({
success: (res) => {
if (res.safeArea)
safeAreaTop.value = res.safeArea.top
},
})
}
function onClickOverlay() {
onClosePopup()
}
@@ -77,17 +57,9 @@ function onClickLeft() {
router.back()
}
function onImmersiveNavbarChange(payload) {
if (!payload || payload.route !== currentRoute.value || !immersiveRoutes.has(payload.route))
return
immersiveNavbarSolid.value = Boolean(payload.solid)
}
onMounted(() => {
getSafeAreaTop()
syncTitle()
loadShownNotificationKeys()
console.log('[通知DEBUG] default layout onMounted, 即将请求通知')
getGlobalNotify()
})
@@ -95,35 +67,29 @@ onShow(() => {
ensurePrivacyConsentIfNeeded()
syncTitle()
ensureAuthIfNeeded()
console.log('[通知DEBUG] default layout onShow')
checkNotification()
})
onMounted(() => {
uni.$on('immersive-navbar-change', onImmersiveNavbarChange)
})
onUnmounted(() => {
uni.$off('immersive-navbar-change', onImmersiveNavbarChange)
onReady(() => {
syncTitle()
})
</script>
<template>
<view
class="page-layout min-h-screen from-sky-100/20 to-white bg-gradient-to-b"
:class="{ 'transparent-layout': isImmersivePage }"
>
<view class="page-layout min-h-screen from-sky-100/20 to-white bg-gradient-to-b">
<wd-navbar
v-if="isImmersivePage"
:class="immersiveNavbarClass"
:title="pageTitle"
:custom-style="immersiveNavbarStyle"
:key="currentRoute"
safe-area-inset-top
left-arrow
placeholder
fixed
bordered
@click-left="onClickLeft"
/>
<wd-navbar v-else :title="pageTitle" safe-area-inset-top left-arrow placeholder fixed @click-left="onClickLeft" />
>
<template #title>
<text class="layout-navbar-title">{{ pageTitle }}</text>
</template>
</wd-navbar>
<slot />
<wd-popup v-model="showPopup" round :close-on-click-modal="true" @close="onClickOverlay">
<view class="popup-content p-8 text-center" style="width: 85vw;">
@@ -142,28 +108,16 @@ onUnmounted(() => {
</template>
<style lang="scss" scoped>
.transparent-layout {
background: transparent;
}
:deep(.immersive-wd-navbar) {
z-index: 1000;
transition: background-color 0.2s ease, box-shadow 0.2s ease;
}
:deep(.immersive-wd-navbar .wd-navbar__title),
:deep(.immersive-wd-navbar .wd-navbar__left),
:deep(.immersive-wd-navbar .wd-navbar__left .wd-icon) {
color: #fff !important;
text-shadow: 0 1px 6px rgba(0, 0, 0, 0.22);
transition: color 0.2s ease, text-shadow 0.2s ease;
}
:deep(.immersive-wd-navbar.is-solid .wd-navbar__title),
:deep(.immersive-wd-navbar.is-solid .wd-navbar__left),
:deep(.immersive-wd-navbar.is-solid .wd-navbar__left .wd-icon) {
color: #111827 !important;
text-shadow: none;
:deep(.layout-navbar-title) {
display: block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #111827;
font-size: 18px;
font-weight: 600;
line-height: 1.2;
}
.popup-content {

View File

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

View File

@@ -1,9 +1,10 @@
<script setup>
import { openCustomerService } from '@/composables/useCustomerService'
import { openAgentVipApplyInExternalBrowser } from '@/composables/useAgentVipH5'
definePage({ layout: 'default', auth: true })
function toVipApply() {
uni.navigateTo({ url: '/pages/agent-vip-apply' })
openAgentVipApplyInExternalBrowser()
}
function toService() {

View File

@@ -1,22 +1,10 @@
<script setup>
import { computed } from 'vue'
import bgIcon from '/static/images/bg_icon.png'
import bannerImg from '/static/images/index/banner_1.png'
import bannerImg2 from '/static/images/index/banner_2.png'
import bannerImg3 from '/static/images/index/banner_3.png'
import honestyBanner from '/static/images/index/banner_B.png'
import companyIcon from '/static/images/index/company_bg.png'
import housekeepingRiskIcon from '/static/images/index/housekeeping_risk_bg.png'
import loanCheckIcon from '/static/images/index/loan_check_bg.png'
import marriageRiskIcon from '/static/images/index/marriage_risk_bg.png'
import personalDataIcon from '/static/images/index/personal_data_bg.png'
import preLoanRiskIcon from '/static/images/index/preloan_risk_bg.png'
import rentalInfoBg from '/static/images/index/rentalinfo_bg.png'
import rightIcon from '/static/images/index/right.png'
import indexPromoteIcon from '/static/images/index/tgbg.png'
import indexMyReportIcon from '/static/images/index/wdbg.png'
import indexInvitationIcon from '/static/images/index/yqhy.png'
import { useAppConfig } from '@/composables/useAppConfig'
import { ensurePrivacyConsentIfNeeded } from '@/composables/usePrivacyConsent'
definePage({ layout: 'home' })
@@ -27,28 +15,6 @@ onShow(() => {
const banners = [bannerImg, bannerImg2, bannerImg3]
const services = [
{ title: '个人大数据', name: 'personalData', bg: personalDataIcon },
{ title: '婚恋风险', name: 'marriage', bg: marriageRiskIcon },
{ title: '入职背调', name: 'backgroundcheck', bg: preLoanRiskIcon },
]
const riskServices = [
{ title: '小微', name: 'companyinfo', bg: companyIcon },
{ title: '家政', name: 'homeservice', bg: housekeepingRiskIcon },
{ title: '贷前', name: 'preloanbackgroundcheck', bg: loanCheckIcon },
{ title: '诚信租赁', name: 'rentalinfo', bg: rentalInfoBg },
]
const { appConfig, loadAppConfig } = useAppConfig()
const queryRetentionDaysText = computed(() => `${appConfig.value.query.retention_days}`)
void loadAppConfig()
function toInquire(name) {
uni.navigateTo({
url: `/pages/inquire?feature=${encodeURIComponent(name)}`,
})
}
function toInvitation() {
uni.navigateTo({ url: '/pages/invitation' })
}
@@ -56,112 +22,70 @@ function toInvitation() {
function toPromote() {
uni.navigateTo({ url: '/pages/promote' })
}
function toHistory() {
uni.navigateTo({ url: '/pages/history-query' })
}
</script>
<template>
<view class="box-border min-h-screen">
<view class="relative p-4">
<swiper class="banner-swiper overflow-hidden rounded-xl" circular autoplay :interval="3000" indicator-dots
indicator-color="rgba(255,255,255,0.5)" indicator-active-color="#ffffff">
<swiper-item v-for="(item, index) in banners" :key="index">
<view class="box-border min-h-screen bg-[#f5faff] pb-4">
<view class="m-4 overflow-hidden rounded-2xl">
<swiper
class="banner-swiper w-full"
circular
autoplay
:interval="3000"
indicator-dots
indicator-color="rgba(255,255,255,0.45)"
indicator-active-color="#ffffff"
>
<swiper-item v-for="(item, index) in banners" :key="index" class="h-full w-full">
<image :src="item" class="h-full w-full" mode="aspectFill" />
</swiper-item>
</swiper>
</view>
<view class="px-6">
<view class="grid grid-cols-3 gap-3">
<view class="flex flex-col items-center justify-center text-center" @click="toPromote">
<view class="mt-2 px-4">
<view class="mb-2 text-base text-gray-800 font-bold">
即刻赚佣金
</view>
<view
class="box-content h-16 w-16 flex items-center justify-center rounded-full from-white to-blue-100/10 bg-gradient-to-b p-1 shadow-lg">
<image :src="indexPromoteIcon" class="h-12 w-12" mode="aspectFit" />
class="card-gradient-1 mb-4 flex items-center rounded-2xl p-0 shadow-xl"
@click="toPromote"
>
<view class="flex flex-1 flex-col items-start justify-center py-4 pl-4">
<view class="mb-1 text-[20px] text-blue-700 font-bold">
直推报告
</view>
<text class="mt-1 text-center font-bold">
推广报告
</text>
<view class="mb-4 text-xs text-gray-700">
选择所需报告类型灵活定价一键分享客户客户下单即结算佣金实时到账
</view>
<wd-button plain>
立即推广
</wd-button>
</view>
<image class="mx-4 my-4 h-20 w-20" :src="indexPromoteIcon" mode="aspectFit" />
</view>
<view class="flex flex-col items-center justify-center text-center" @click="toInvitation">
<view
class="box-content h-16 w-16 flex items-center justify-center rounded-full from-white to-blue-100/10 bg-gradient-to-b p-1 shadow-lg">
<image :src="indexInvitationIcon" class="h-12 w-12" mode="aspectFit" />
class="card-gradient-2 flex items-center rounded-2xl p-0 shadow-xl"
@click="toInvitation"
>
<view class="flex flex-1 flex-col items-start justify-center py-4 pl-4">
<view class="mb-1 text-[20px] text-teal-700 font-bold">
邀请下级代理
</view>
<text class="mt-1 text-center font-bold">
邀请下级
</text>
<view class="mb-4 text-xs text-gray-700">
邀请好友成为代理好友推广获客客户支付即返佣团队收益轻松到手
</view>
<wd-button plain>
立即邀请
</wd-button>
</view>
<image class="mx-4 my-4 h-20 w-20" :src="indexInvitationIcon" mode="aspectFit" />
</view>
<view class="flex flex-col items-center justify-center text-center" @click="toHistory">
<view
class="box-content h-16 w-16 flex items-center justify-center rounded-full from-white to-blue-100/10 bg-gradient-to-b p-1 shadow-lg">
<image :src="indexMyReportIcon" class="h-12 w-12" mode="aspectFit" />
</view>
<text class="mt-1 text-center font-bold">
我的报告
</text>
</view>
</view>
</view>
<view class="relative p-4 pt-0">
<view class="grid grid-cols-2 my-4 gap-4" style="grid-template-rows: repeat(2, 1fr);">
<view v-for="(service, index) in services" :key="index"
class="relative min-h-18 flex flex-col rounded-xl px-4 py-2 shadow-lg"
:class="index === 0 ? 'row-span-2' : ''"
:style="`background: url(${service.bg}) no-repeat; background-size: 100% 100%; background-position: center;`"
@click="toInquire(service.name)">
<view class="min-h-18 flex items-end">
<!-- <text class="text-base text-gray-700 font-semibold">
{{ service.title }}
</text> -->
</view>
</view>
</view>
<scroll-view scroll-x class="risk-scroll my-4 px-1 pb-4 pt-2 -mx-1">
<view class="inline-flex gap-2">
<view v-for="(service, index) in riskServices" :key="index"
class="relative h-24 w-[107px] flex-shrink-0 rounded-xl shadow-lg"
:style="`background: url(${service.bg}) no-repeat; background-size: 100% 100%; background-position: center;`"
@click="toInquire(service.name)">
<view class="h-full flex items-end px-2 py-2">
<!-- <text class="text-sm text-gray-700 font-semibold">
{{ service.title }}
</text> -->
</view>
</view>
</view>
</scroll-view>
<view class="mb-3 mt-6 flex items-center">
<view class="bg-primary h-5 w-1.5 rounded-xl" />
<text class="ml-2 text-lg text-gray-800">
诚信专栏
</text>
</view>
<view class="mt-4 overflow-hidden rounded-xl bg-white shadow-xl">
<image :src="honestyBanner" class="block w-full" mode="widthFix" />
</view>
<view class="mt-4 box-border h-14 w-full flex items-center rounded-lg bg-white px-4 text-gray-700 shadow-lg"
@click="toHistory">
<view class="mr-4 h-full flex items-center justify-center">
<image class="h-10 w-10" :src="bgIcon" mode="aspectFit" />
</view>
<view class="flex-1">
<view class="text-gray-800">
我的历史查询记录
</view>
<view class="text-xs text-gray-500">
查询记录有效期为{{ queryRetentionDaysText }}
</view>
</view>
<image :src="rightIcon" class="h-6 w-6" mode="aspectFit" />
<view class="relative mt-4 h-[120px] overflow-hidden rounded-2xl shadow-xl">
<image class="absolute inset-0 h-full w-full" :src="honestyBanner" mode="aspectFill" />
<view class="absolute inset-0 bg-black/5" />
</view>
</view>
</view>
@@ -172,7 +96,17 @@ function toHistory() {
height: 160px;
}
.risk-scroll {
white-space: nowrap;
.card-gradient-1 {
background: linear-gradient(135deg, #e3f0ff 0%, #fafdff 100%);
box-shadow:
0 6px 24px 0 rgba(60, 120, 255, 0.1),
0 1.5px 4px 0 rgba(60, 120, 255, 0.08);
}
.card-gradient-2 {
background: linear-gradient(135deg, #e6f7fa 0%, #fafdff 100%);
box-shadow:
0 6px 24px 0 rgba(0, 200, 180, 0.1),
0 1.5px 4px 0 rgba(0, 200, 180, 0.08);
}
</style>

View File

@@ -2,6 +2,7 @@
import { storeToRefs } from 'pinia'
import { computed, onBeforeMount, ref, watch } from 'vue'
import { openCustomerService } from '@/composables/useCustomerService'
import { openAgentVipApplyInExternalBrowser } from '@/composables/useAgentVipH5'
import { useAgentStore } from '@/stores/agentStore'
import { useDialogStore } from '@/stores/dialogStore'
import { useUserStore } from '@/stores/userStore'
@@ -75,9 +76,7 @@ function maskName(name) {
return `${name.substring(0, 3)}****${name.substring(7)}`
}
function toHistory() {
uni.navigateTo({ url: '/pages/history-query' })
}
function toPromote() {
uni.navigateTo({ url: '/pages/promote' })
}
@@ -127,7 +126,7 @@ function toVipConfig() {
}
function toVipRenewal() {
uni.navigateTo({ url: '/pages/agent-vip-apply' })
openAgentVipApplyInExternalBrowser()
}
function formatExpiryTime(expiryTimeStr) {
@@ -298,17 +297,6 @@ onBeforeMount(() => {
</view>
<image src="/static/images/me/right.png" class="h-4 w-4" alt="右箭头" />
</view>
<view
class="w-full flex items-center justify-between border-b border-gray-100 px-6 py-4 transition-colors hover:bg-blue-50"
@click="toHistory">
<view class="flex items-center gap-3">
<image src="/static/images/index/wdbg.png" class="h-6 w-6" alt="我的报告" />
<text class="text-gray-700 font-medium">
我的报告
</text>
</view>
<image src="/static/images/me/right.png" class="h-4 w-4" alt="右箭头" />
</view>
<view
class="w-full flex items-center justify-between border-b border-gray-100 px-6 py-4 transition-colors hover:bg-blue-50"
@click="toUserAgreement">

View File

@@ -105,13 +105,13 @@ onShow(() => {
</view>
<view class="permissions">
<view class="permission-item">
1. 网络权限用于连接服务加载业务数据与提交操作请求
1. 网络权限用于连接服务加载页面与提交查询支付等操作
</view>
<view class="permission-item">
2. 存储权限用于缓存页面数据保存登录状态与图片文件
2. 相册/存储写入仅在您点击保存推广海报或二维码图片时将应用内生成的图片保存到手机相册
</view>
<view class="permission-item">
3. 设备信息权限用于运行稳定性安全风控与异常排查
3. 支付相关能力在您主动发起支付宝支付时由支付服务完成交易具体权限以支付 SDK 为准
</view>
</view>
<view class="hint">

View File

@@ -19,6 +19,9 @@ const formData = ref({
clientPrice: null,
})
/** 从首页报告卡片带入的 product_en配置加载后预选 */
const initialFeature = ref('')
const availableReportTypes = computed(() => {
if (!productConfig.value?.length)
return []
@@ -84,6 +87,17 @@ function openPricePicker() {
showPricePicker.value = true
}
function toExampleReport() {
const feature = formData.value.productType
if (!feature) {
uni.showToast({ title: '请先选择报告类型', icon: 'none' })
return
}
uni.navigateTo({
url: `/pages/report-example-webview?feature=${encodeURIComponent(feature)}`,
})
}
async function getPromoteConfig() {
loadingConfig.value = true
try {
@@ -91,7 +105,10 @@ async function getPromoteConfig() {
if (data.value && !error.value && data.value.code === 200) {
const list = data.value.data.AgentProductConfig || []
productConfig.value = list
const availableType = availableReportTypes.value[0]
const preset = initialFeature.value
? availableReportTypes.value.find(item => item.value === initialFeature.value)
: null
const availableType = preset || availableReportTypes.value[0]
if (availableType) {
selectProductType(availableType.value)
}
@@ -143,6 +160,12 @@ async function generatePromotionCode() {
}
}
onLoad((options) => {
const feature = options?.feature
if (feature)
initialFeature.value = decodeURIComponent(String(feature))
})
onMounted(() => {
getPromoteConfig()
})
@@ -163,8 +186,14 @@ onMounted(() => {
</view>
<VipBanner />
<view class="card mb-4">
<view class="mb-2 text-xl font-semibold">
<view class="card mb-4 relative">
<text
class="absolute right-4 top-4 z-10 text-sm text-blue-600 active:opacity-70"
@click="toExampleReport"
>
示例报告
</text>
<view class="mb-2 pr-20 text-xl font-semibold">
生成推广码
</view>
<wd-form ref="promotionForm" :model="formData">

3
src/uni-pages.d.ts vendored
View File

@@ -9,7 +9,6 @@ type _LocationUrl =
"/pages/agent-promote-details" |
"/pages/agent-rewards-details" |
"/pages/agent-service-agreement" |
"/pages/agent-vip-apply" |
"/pages/agent-vip-config" |
"/pages/agent-vip" |
"/pages/agent" |
@@ -18,8 +17,6 @@ type _LocationUrl =
"/pages/help-detail" |
"/pages/help-guide" |
"/pages/help" |
"/pages/history-query" |
"/pages/inquire" |
"/pages/invitation-agent-apply" |
"/pages/invitation" |
"/pages/launch" |