/** * Soul 创业派对 - 关联小程序跳转(与阅读页 onLinkTagTap miniprogram 分支一致) */ const app = getApp() function normalizeLinkTagLabel(raw) { return String(raw || '') .replace(/^[##\s\u00a0\u200b\u3000]+/u, '') .replace(/[\s\u00a0\u200b\u3000]+$/u, '') .trim() .toLowerCase() } function resolveLinkTagByLabel(label) { const normalized = normalizeLinkTagLabel(label) if (!normalized) return null const tags = Array.isArray(app.globalData.linkTagsConfig) ? app.globalData.linkTagsConfig : [] for (const t of tags) { if (!t) continue const candidates = [t.label] if (typeof t.aliases === 'string' && t.aliases.trim()) { candidates.push(...t.aliases.split(',')) } for (const c of candidates) { if (normalizeLinkTagLabel(c) === normalized) return t } } return null } function pickLinkTagField(tag, keys, defaultValue = '') { if (!tag || typeof tag !== 'object') return defaultValue for (const key of keys) { const v = tag[key] if (v == null) continue const s = String(v).trim() if (s) return s } return defaultValue } function parseDatasetBool(v) { if (typeof v === 'boolean') return v const s = String(v || '').trim().toLowerCase() return s === '1' || s === 'true' || s === 'yes' || s === 'on' } function normalizeQueryKey(raw, defaultKey = 'phone') { const s = String(raw || '').trim() if (!s) return defaultKey if (!/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(s)) return defaultKey return s } /** 跳转其他小程序时在 path 上追加/覆盖 query(保留已有 ?…&…,避免重复同名 key) */ function appendQueryToPath(path, key, value) { if (value == null || value === '') return (path || '').trim() const base = (path || '').trim() const hashIdx = base.indexOf('#') const hashPart = hashIdx >= 0 ? base.slice(hashIdx) : '' const noHash = hashIdx >= 0 ? base.slice(0, hashIdx) : base const qIdx = noHash.indexOf('?') const pathname = qIdx >= 0 ? noHash.slice(0, qIdx) : noHash const query = qIdx >= 0 ? noHash.slice(qIdx + 1) : '' const params = [] if (query) { query.split('&').forEach((pair) => { if (!pair) return const [k = '', v = ''] = pair.split('=') const dk = decodeURIComponent(k || '') if (dk && dk !== key) params.push(`${encodeURIComponent(dk)}=${v}`) }) } params.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`) return `${pathname}${params.length ? '?' + params.join('&') : ''}${hashPart}` } function getLoggedInUserPhone() { const u = app.globalData.userInfo || {} return (u.phone || u.phoneNumber || wx.getStorageSync('user_phone') || '').trim().replace(/\s/g, '') } async function ensureLoggedInUserPhone() { let phone = getLoggedInUserPhone() if (phone) return phone try { const profileRes = await app.request({ url: '/api/miniprogram/user/profile', silent: true, timeout: 3000 }) const p = (profileRes && profileRes.data) ? profileRes.data : {} phone = String(p.phone || p.phoneNumber || '').trim().replace(/\s/g, '') if (phone) { const merged = { ...(app.globalData.userInfo || {}), ...p, phone } app.globalData.userInfo = merged try { wx.setStorageSync('userInfo', merged) wx.setStorageSync('user_phone', phone) } catch (_) {} } } catch (e) {} return phone } function resolveMiniprogramLinkFromTag(tag) { if (!tag || typeof tag !== 'object') return null const tagType = pickLinkTagField(tag, ['type', 'tagType'], '').toLowerCase() if (tagType !== 'miniprogram') return null const mpKey = pickLinkTagField(tag, ['mpKey', 'mp_key', 'appId', 'app_id'], '') const pagePath = pickLinkTagField(tag, ['pagePath', 'page_path'], '') let passPhone = true const hasCachedPassPhone = !(tag.passPhone == null || tag.passPhone === '') if (hasCachedPassPhone) passPhone = parseDatasetBool(tag.passPhone) const phoneParamName = normalizeQueryKey(tag.phoneParamName, 'phone') return { mpKey, pagePath, passPhone, phoneParamName } } /** * @param {{ mpKey: string, pagePath?: string, passPhone?: boolean, phoneParamName?: string }} opts */ async function navigateLinkedMiniprogram(opts) { const mpKey = String(opts.mpKey || '').trim() if (!mpKey) { wx.showToast({ title: '未配置小程序', icon: 'none' }) return } await app.getReadExtras() const linked = (app.globalData.linkedMiniprograms || []).find((m) => m.key === mpKey) if (!linked || !linked.appId) { wx.showToast({ title: '未找到关联小程序配置', icon: 'none' }) return } let targetPath = (opts.pagePath != null && String(opts.pagePath).trim()) ? String(opts.pagePath).trim() : (linked.path || '') const phone = await ensureLoggedInUserPhone() const passPhone = opts.passPhone !== false const phoneParamName = normalizeQueryKey(opts.phoneParamName, 'phone') if (passPhone && phone) { targetPath = appendQueryToPath(targetPath, phoneParamName, phone) } wx.navigateToMiniProgram({ appId: linked.appId, path: targetPath || '', envVersion: 'release', success: () => {}, fail: (err) => { wx.showToast({ title: err.errMsg || '跳转失败', icon: 'none' }) }, }) } async function navigateMiniprogramFromLinkTagConfig(tag) { const r = resolveMiniprogramLinkFromTag(tag) if (!r || !r.mpKey) return false await navigateLinkedMiniprogram(r) return true } /** * @param {Record} myPageUi mp_config.mpUi.myPage */ function findMbtiLinkTagInConfig(myPageUi) { const tags = Array.isArray(app.globalData.linkTagsConfig) ? app.globalData.linkTagsConfig : [] const preferred = myPageUi && String(myPageUi.mbtiLinkLabel || '').trim() if (preferred) { const t = resolveLinkTagByLabel(preferred) if (t) { const r = resolveMiniprogramLinkFromTag(t) if (r && r.mpKey) return t } } for (const t of tags) { const r = resolveMiniprogramLinkFromTag(t) if (!r || !r.mpKey) continue const lbl = pickLinkTagField(t, ['label'], '').toLowerCase() const key = String(r.mpKey || '').toLowerCase() const path = String(r.pagePath || '').toLowerCase() if (lbl.includes('mbti') || key.includes('mbti') || path.includes('mbti')) return t } return null } module.exports = { appendQueryToPath, getLoggedInUserPhone, ensureLoggedInUserPhone, navigateLinkedMiniprogram, navigateMiniprogramFromLinkTagConfig, findMbtiLinkTagInConfig, resolveLinkTagByLabel, resolveMiniprogramLinkFromTag, normalizeQueryKey, }