diff --git a/.env b/.env index 9ea1208..48e28e9 100644 --- a/.env +++ b/.env @@ -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=赤眉 diff --git a/_backup/pages/agent-vip-apply.vue b/_backup/pages/agent-vip-apply.vue new file mode 100644 index 0000000..2a0ea19 --- /dev/null +++ b/_backup/pages/agent-vip-apply.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/components/InquireForm.vue b/_backup/pages/components/InquireForm.vue similarity index 100% rename from src/components/InquireForm.vue rename to _backup/pages/components/InquireForm.vue diff --git a/src/pages/history-query.vue b/_backup/pages/history-query.vue similarity index 100% rename from src/pages/history-query.vue rename to _backup/pages/history-query.vue diff --git a/src/pages/inquire.vue b/_backup/pages/inquire.vue similarity index 95% rename from src/pages/inquire.vue rename to _backup/pages/inquire.vue index b73373a..682e363 100644 --- a/src/pages/inquire.vue +++ b/_backup/pages/inquire.vue @@ -1,6 +1,6 @@ diff --git a/src/composables/openExternalUrl.ts b/src/composables/openExternalUrl.ts new file mode 100644 index 0000000..c42ef57 --- /dev/null +++ b/src/composables/openExternalUrl.ts @@ -0,0 +1,46 @@ +/** 在系统默认浏览器中打开链接(App 用 plus,H5 开发调试用 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 +} diff --git a/src/composables/uni-router.ts b/src/composables/uni-router.ts index 7cd2bba..cfd0cef 100644 --- a/src/composables/uni-router.ts +++ b/src/composables/uni-router.ts @@ -6,7 +6,7 @@ import { pages } from 'virtual:uni-pages' const pathToPage: Record = { '/': '/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 = { 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 = { '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 = { '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 = { ...(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) diff --git a/src/composables/useAgentVipH5.ts b/src/composables/useAgentVipH5.ts new file mode 100644 index 0000000..0e719b3 --- /dev/null +++ b/src/composables/useAgentVipH5.ts @@ -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 = {}) { + const { buildSitePathUrl } = useReportWebview() + return buildSitePathUrl(VIP_APPLY_H5_PATH, params) +} + +function buildTypeQuery(options?: { type?: 'vip' | 'svip' | 'VIP' | 'SVIP' }) { + const query: Record = {} + 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) +} diff --git a/src/composables/useNavigationAuthGuard.ts b/src/composables/useNavigationAuthGuard.ts index 3286107..abfeabe 100644 --- a/src/composables/useNavigationAuthGuard.ts +++ b/src/composables/useNavigationAuthGuard.ts @@ -18,7 +18,6 @@ const HOME_URL = '/pages/index' */ const ROUTE_PERMISSION_MAP: Record = { '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 }, diff --git a/src/composables/useReportWebview.ts b/src/composables/useReportWebview.ts index da81d27..10b998e 100644 --- a/src/composables/useReportWebview.ts +++ b/src/composables/useReportWebview.ts @@ -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 = {}): Record { + const merged: Record = { ...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 { const parts: string[] = [] @@ -20,46 +41,37 @@ export function useReportWebview() { } const buildReportUrl = (page: 'example' | 'report', params: Record = {}) => { - const token = getToken() || '' const base = resolveBase() if (!base) { console.warn('[report webview] 请配置 VITE_SITE_ORIGIN 或 VITE_REPORT_BASE_URL') return '' } - const merged: Record = { 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 = {}) => { - const token = getToken() || '' const base = resolveBase() if (!base) { console.warn('[report webview] 请配置 VITE_SITE_ORIGIN 或 VITE_REPORT_BASE_URL') return '' } - const merged: Record = { 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 = {}) => { - 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 = { source: 'app', ...params } - if (token) - merged.token = token + const merged = appendAppAuthQueryParams(params) const search = buildQueryString(merged) return `${base}${normalizedPath}${search ? `?${search}` : ''}` } diff --git a/src/layouts/default.vue b/src/layouts/default.vue index db18bcb..32c3db8 100644 --- a/src/layouts/default.vue +++ b/src/layouts/default.vue @@ -1,6 +1,6 @@ diff --git a/src/pages/agent-vip.vue b/src/pages/agent-vip.vue index 88881f3..385b783 100644 --- a/src/pages/agent-vip.vue +++ b/src/pages/agent-vip.vue @@ -1,9 +1,10 @@