feat: add MBTI tag functionality to user profile
- Introduced a new MBTI tag in the user profile section, displaying the user's MBTI type or a default label if not available. - Implemented navigation to a mini program for MBTI testing, ensuring user phone number is included if logged in. - Enhanced styling for the MBTI tag to improve visibility and user interaction. Made-with: Cursor
This commit is contained in:
191
miniprogram/utils/linkedMiniprogramNavigate.js
Normal file
191
miniprogram/utils/linkedMiniprogramNavigate.js
Normal file
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* 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<string, unknown>} 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,
|
||||
}
|
||||
Reference in New Issue
Block a user