Files
crmeb-mp/pages/annex/link_view/index.vue

138 lines
5.5 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
小程序专用 web-view 壳页(2026-11-04)—— 只给「链接系统 / 福利系统」的小程序码用
方案:box_app/docs/lianjie-wxacode-plan.md(v4 硬编码,第四节之一 4.2)
入口:
1) 扫小程序码:options.scene = "l=g8I0yMDYzlCu" / "f=zanxte3x"(微信会 URL 编码 scene)
2) 转发卡片: /pages/annex/link_view/index?t=l&k=g8I0yMDYzlCu
只做三件事:解析 scene 或转发参数 → 拼地址 → web-view 打开;解析不出来显示兜底屏。
⚠️ TARGETS 必须和 box_app backend/app/services/link_wxacode_serv.py 的同名表一致
(短键、地址前缀、标识规则)。改这张表 = 两端一起改 + 小程序发版;
码里只有标识,所以发版后旧码照常可用。
⚠️ 不注入任何小程序身份(openid/unionid),也不反查分享图/标题:两家都走自己的
公众号网页授权(方案第五节),所以本页**不引入** webviewHost / shareCard mixin。
⚠️ 本页路径必须等于 box_app 生产 .env 的 LINK_WXACODE_PAGE(默认就是这个路径),
改路径要同步改那边,否则已生成的码全部扫不进页面。
⚠️ web-view 打开的域名必须在小程序后台「业务域名」里(方案 4.4):
wenda.douba.top / fuli.douba.top / dbh5.ywsz.com.cn。
-->
<template>
<view>
<web-view v-if="url" :src="url"></web-view>
<view v-else class="lv-fallback">
<view class="lv-emoji">🔗</view>
<view class="lv-title">链接已失效</view>
<view class="lv-desc">这个码可能已过期,或者链接已下架</view>
<button class="lv-btn" hover-class="lv-btn-hover" @tap="goHome">去首页</button>
</view>
</view>
</template>
<script>
/* 目标表(方案第三节):短键 → 地址前缀 + 标识规则。
base 写死在小程序里:换域名或新增系统要发版过审,这一点在方案第八节已知。 */
const TARGETS = {
l: { base: 'https://wenda.douba.top/s/', re: /^[0-9A-Za-z]{1,30}$/, title: '兜爸心选 · 好物推荐' },
f: { base: 'https://fuli.douba.top/a/', re: /^[a-z0-9-]{1,30}$/, title: '兜爸心选 · 福利活动' },
}
/* 微信给小程序的 scene 是 URL 编码的("l%3Dslug"),个别机型/版本还会二次编码。
必须 try/catch:裸 '%' 会让 decodeURIComponent 抛错,直接冲掉 onLoad → 白屏。
解不开就用原文,后面正则校验会把它挡成兜底屏,绝不会把野值塞进 web-view。 */
function decodeScene(raw) {
let s = String(raw == null ? '' : raw)
for (let i = 0; i < 2 && s.indexOf('%') !== -1; i++) {
try {
const next = decodeURIComponent(s)
if (next === s) break
s = next
} catch (e) {
break
}
}
return s
}
/* "l=slug&ch=XX" → { l: 'slug', ch: 'XX' }
按第一个 '=' 切,值里再出现 '=' 也不会被截断。 */
function parseScene(raw) {
const out = {}
String(raw || '').split('&').forEach((kv) => {
const i = kv.indexOf('=')
if (i > 0) out[kv.slice(0, i)] = kv.slice(i + 1)
})
return out
}
/* 返回 { t, k, url };返回 null 表示走兜底屏。
优先级:显式 t/k(转发落地)> scene(扫码)——同一条码两种情况都要能用。 */
function resolveTarget(options) {
const opt = options || {}
let t = opt.t ? String(opt.t) : ''
let k = opt.k ? String(opt.k) : ''
if (!t && opt.scene) {
const sc = parseScene(decodeScene(opt.scene))
for (const key in TARGETS) {
if (sc[key]) {
t = key
k = sc[key]
break
}
}
}
const target = TARGETS[t]
if (!target || !k || !target.re.test(k)) return null
return { t, k, url: target.base + k }
}
export default {
data() {
return {
url: '', // 为空 = 兜底屏(web-view 不渲染)
t: '', // 短键(分享时回填)
k: '', // 标识(分享时回填)
}
},
onLoad(options) {
const opt = options || {}
const hit = resolveTarget(opt)
if (!hit) {
// 真机排查用:一条日志区分「码/参数不对」和「域名没加业务域名」
console.warn('[link-view] 标识不合法,显示兜底屏: scene=' + (opt.scene || '') +
' t=' + (opt.t || '') + ' k=' + (opt.k || ''))
return
}
this.t = hit.t
this.k = hit.k
this.url = hit.url
console.log('[link-view] scene=' + (opt.scene || '') + ' 短键=' + hit.t +
' 标识=' + hit.k + ' → ' + hit.url)
},
onShareAppMessage() {
// 转发出去还是这个壳页:好友点开按 t/k 重新拼地址,不带上任何身份参数
if (!this.url) return { title: '兜爸心选', path: '/pages/index/index' }
return {
title: TARGETS[this.t].title,
path: '/pages/annex/link_view/index?t=' + this.t + '&k=' + this.k,
}
},
methods: {
goHome() {
uni.switchTab({ url: '/pages/index/index' })
},
},
}
</script>
<style scoped>
/* 兜底屏沿用宿主页配色(pages/annex/web_view):#2B2320 主文字 / #A79C95 次要 / #F4593C 主按钮 */
.lv-fallback { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 70vh; padding: 0 40rpx; }
.lv-emoji { font-size: 64rpx; line-height: 1; }
.lv-title { margin-top: 24rpx; font-size: 32rpx; font-weight: 700; color: #2B2320; }
.lv-desc { margin-top: 12rpx; font-size: 26rpx; color: #A79C95; line-height: 1.6; text-align: center; }
.lv-btn { margin-top: 36rpx; padding: 0 48rpx; height: 80rpx; line-height: 80rpx; border-radius: 40rpx; background: #F4593C; color: #fff; font-size: 28rpx; }
.lv-btn-hover { opacity: .85; }
</style>