Add linked mini program functionality and enhance link tag handling

- Introduced `navigateToMiniProgramAppIdList` in app.json for mini program navigation.
- Updated link tag handling in the read page to support mini program keys and app IDs.
- Enhanced content parsing to include app ID and mini program key in link tags.
- Added linked mini programs management in the admin panel with API endpoints for CRUD operations.
- Improved UI for selecting linked mini programs in the content creation page.
This commit is contained in:
Alex-larget
2026-03-12 16:51:12 +08:00
parent 41ebc70a50
commit db4b4b8b87
13 changed files with 696 additions and 34 deletions

View File

@@ -57,6 +57,7 @@
]
},
"usingComponents": {},
"navigateToMiniProgramAppIdList": [],
"__usePrivacyCheck__": true,
"lazyCodeLoading": "requiredComponents",
"style": "v2",

View File

@@ -78,11 +78,12 @@ Page({
async onLoad(options) {
wx.showShareMenu({ withShareTimeline: true })
// 预加载 linkTags 配置(供 onLinkTagTap 旧格式降级匹配 type 用
if (!app.globalData.linkTagsConfig) {
// 预加载 linkTags、linkedMiniprograms(供 onLinkTagTap 用密钥查 appId
if (!app.globalData.linkTagsConfig || !app.globalData.linkedMiniprograms) {
app.request({ url: '/api/miniprogram/config', silent: true }).then(cfg => {
if (cfg && Array.isArray(cfg.linkTags)) {
app.globalData.linkTagsConfig = cfg.linkTags
if (cfg) {
if (Array.isArray(cfg.linkTags)) app.globalData.linkTagsConfig = cfg.linkTags
if (Array.isArray(cfg.linkedMiniprograms)) app.globalData.linkedMiniprograms = cfg.linkedMiniprograms
}
}).catch(() => {})
}
@@ -469,12 +470,13 @@ Page({
getApp().goBackOrToHome()
},
// 点击正文中的 #链接标签:小程序内页/预览页跳转
// 点击正文中的 #链接标签:小程序内页/预览页/唤醒其他小程序
onLinkTagTap(e) {
let url = (e.currentTarget.dataset.url || '').trim()
const label = (e.currentTarget.dataset.label || '').trim()
let tagType = (e.currentTarget.dataset.tagType || '').trim()
let pagePath = (e.currentTarget.dataset.pagePath || '').trim()
let mpKey = (e.currentTarget.dataset.mpKey || '').trim() || (e.currentTarget.dataset.appId || '').trim()
// 旧格式(<a href>tagType 为空 → 按 label 从缓存 linkTags 补充类型信息
if (!tagType && label) {
@@ -483,6 +485,7 @@ Page({
tagType = cached.type || 'url'
pagePath = cached.pagePath || ''
if (!url) url = cached.url || ''
if (cached.mpKey) mpKey = cached.mpKey
}
}
@@ -493,6 +496,28 @@ Page({
return
}
// 小程序类型:用密钥查 linkedMiniprograms 得 appId再唤醒需在 app.json 的 navigateToMiniProgramAppIdList 中配置)
if (tagType === 'miniprogram') {
if (!mpKey && label) {
const cached = (app.globalData.linkTagsConfig || []).find(t => t.label === label)
if (cached) mpKey = cached.mpKey || cached.appId || ''
}
const linked = (app.globalData.linkedMiniprograms || []).find(m => (m.key || m.id) === mpKey)
if (linked && linked.appId) {
wx.navigateToMiniProgram({
appId: linked.appId,
path: pagePath || linked.path || '',
envVersion: 'release',
success: () => {},
fail: (err) => {
wx.showToast({ title: err.errMsg || '跳转失败', icon: 'none' })
},
})
return
}
if (mpKey) wx.showToast({ title: '未找到关联小程序配置', icon: 'none' })
}
// 小程序内部路径pagePath 或 url 以 /pages/ 开头)
const internalPath = pagePath || (url.startsWith('/pages/') ? url : '')
if (internalPath) {

View File

@@ -48,7 +48,7 @@
<block wx:for="{{item}}" wx:key="index" wx:for-item="seg">
<text wx:if="{{seg.type === 'text'}}">{{seg.text}}</text>
<text wx:elif="{{seg.type === 'mention'}}" class="mention" bindtap="onMentionTap" data-user-id="{{seg.userId}}" data-nickname="{{seg.nickname}}">@{{seg.nickname}}</text>
<text wx:elif="{{seg.type === 'linkTag'}}" class="link-tag" bindtap="onLinkTagTap" data-url="{{seg.url}}" data-label="{{seg.label}}" data-tag-type="{{seg.tagType}}" data-page-path="{{seg.pagePath}}" data-tag-id="{{seg.tagId}}">#{{seg.label}}</text>
<text wx:elif="{{seg.type === 'linkTag'}}" class="link-tag" bindtap="onLinkTagTap" data-url="{{seg.url}}" data-label="{{seg.label}}" data-tag-type="{{seg.tagType}}" data-page-path="{{seg.pagePath}}" data-tag-id="{{seg.tagId}}" data-app-id="{{seg.appId}}" data-mp-key="{{seg.mpKey}}">#{{seg.label}}</text>
<image wx:elif="{{seg.type === 'image'}}" class="content-image" src="{{seg.src}}" mode="widthFix" show-menu-by-longpress bindtap="onImageTap" data-src="{{seg.src}}"></image>
</block>
</view>

View File

@@ -23,12 +23,19 @@
"condition": {
"miniprogram": {
"list": [
{
"name": "唤醒",
"pathName": "pages/read/read",
"query": "mid=209",
"scene": null,
"launchMode": "default"
},
{
"name": "pages/my/my",
"pathName": "pages/my/my",
"query": "",
"scene": null,
"launchMode": "singlePage"
"launchMode": "singlePage",
"scene": null
},
{
"name": "pages/read/read",

View File

@@ -55,17 +55,21 @@ function parseBlockToSegments(block) {
if (userId || nickname) segs.push({ type: 'mention', userId, nickname })
} else if (/data-type="linkTag"/i.test(tag)) {
// #linkTag — 自定义 span 格式data-type="linkTag" data-url="..." data-tag-type="..." data-page-path="..."
// #linkTag — 自定义 span 格式data-type="linkTag" data-url="..." data-tag-type="..." data-page-path="..." data-app-id="..."
const urlMatch = tag.match(/data-url="([^"]*)"/)
const tagTypeMatch = tag.match(/data-tag-type="([^"]*)"/)
const pagePathMatch = tag.match(/data-page-path="([^"]*)"/)
const tagIdMatch = tag.match(/data-tag-id="([^"]*)"/)
const appIdMatch = tag.match(/data-app-id="([^"]*)"/)
const mpKeyMatch = tag.match(/data-mp-key="([^"]*)"/)
const innerText = tag.replace(/<[^>]+>/g, '').replace(/^#/, '').trim()
const url = urlMatch ? urlMatch[1] : ''
const tagType = tagTypeMatch ? tagTypeMatch[1] : 'url'
const pagePath = pagePathMatch ? pagePathMatch[1] : ''
const tagId = tagIdMatch ? tagIdMatch[1] : ''
segs.push({ type: 'linkTag', label: innerText || '#', url, tagType, pagePath, tagId })
const appId = appIdMatch ? appIdMatch[1] : ''
const mpKey = mpKeyMatch ? mpKeyMatch[1] : (tagType === 'miniprogram' ? appId : '')
segs.push({ type: 'linkTag', label: innerText || '#', url, tagType, pagePath, tagId, appId, mpKey })
} else if (/^<a /i.test(tag)) {
// #linkTag — 旧格式 <a href>insertLinkTag 旧版产生url 可能为空)