From 73d56a225a7e35c14d0e1234da69324b0eecfe02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=98=E9=A3=8E?= Date: Fri, 24 Apr 2026 16:17:07 +0800 Subject: [PATCH] feat: enhance link handling in reading page - Added functionality to open article links in a mini program web-view, allowing users to preview external links. - Implemented mobile hints for external links to improve user experience on mobile devices. - Updated content parser to support new link types, including regular hyperlinks. - Enhanced styling for article links to improve visibility and interaction. Made-with: Cursor --- .../pages/link-preview/link-preview.js | 35 +++++++++++++++++-- miniprogram/pages/read/read.js | 16 +++++++++ miniprogram/pages/read/read.wxml | 6 ++-- miniprogram/pages/read/read.wxss | 8 +++++ .../super-article-detail.js | 18 +++++++++- miniprogram/utils/contentParser.js | 26 +++++++++----- 6 files changed, 95 insertions(+), 14 deletions(-) diff --git a/miniprogram/pages/link-preview/link-preview.js b/miniprogram/pages/link-preview/link-preview.js index 72cda7b5..ea7a82e2 100644 --- a/miniprogram/pages/link-preview/link-preview.js +++ b/miniprogram/pages/link-preview/link-preview.js @@ -1,5 +1,35 @@ const app = getApp() +function safeDecode(value) { + try { + return decodeURIComponent(value || '') + } catch (_) { + return String(value || '') + } +} + +function normalizeHttpUrl(raw) { + let s = String(raw || '').trim() + if (!s) return '' + if (/^\/\//.test(s)) return 'https:' + s + if (!/^https?:\/\//i.test(s)) s = 'https://' + s.replace(/^\/+/, '') + return s +} + +// 为外链补充移动端提示参数:目标站支持时按手机网页输出 +function appendMobileHints(rawUrl) { + const url = normalizeHttpUrl(rawUrl) + if (!url) return '' + const hasQuery = url.indexOf('?') >= 0 + const hasFrom = /(?:\?|&)from=/i.test(url) + const hasView = /(?:\?|&)view=/i.test(url) + const extras = [] + if (!hasFrom) extras.push('from=miniprogram') + if (!hasView) extras.push('view=mobile') + if (!extras.length) return url + return url + (hasQuery ? '&' : '?') + extras.join('&') +} + Page({ data: { url: '', @@ -10,8 +40,9 @@ Page({ }, onLoad(options) { - const url = decodeURIComponent(options.url || '') - const title = options.title ? decodeURIComponent(options.title) : '链接预览' + const rawUrl = safeDecode(options.url || '') + const url = appendMobileHints(rawUrl) + const title = options.title ? safeDecode(options.title) : '链接预览' this.setData({ url, title, diff --git a/miniprogram/pages/read/read.js b/miniprogram/pages/read/read.js index a6bc0e42..f65ca150 100644 --- a/miniprogram/pages/read/read.js +++ b/miniprogram/pages/read/read.js @@ -1257,6 +1257,22 @@ Page({ wx.showToast({ title: '暂无跳转地址', icon: 'none' }) }, + // 点击正文普通超链接:用小程序 web-view 打开(微信内置浏览器) + onArticleLinkTap(e) { + const url = String((e.currentTarget.dataset.url || '')).trim() + if (!url) { + wx.showToast({ title: '链接地址为空', icon: 'none' }) + return + } + const text = String((e.currentTarget.dataset.text || '链接预览')).trim() || '链接预览' + wx.navigateTo({ + url: `/pages/link-preview/link-preview?url=${encodeURIComponent(url)}&title=${encodeURIComponent(text)}`, + fail: (err) => { + wx.showToast({ title: err.errMsg || '打开失败', icon: 'none' }) + }, + }) + }, + // 点击正文图片 → 全屏预览 onImageTap(e) { const src = e.currentTarget.dataset.src diff --git a/miniprogram/pages/read/read.wxml b/miniprogram/pages/read/read.wxml index 9626a6df..c279d18b 100644 --- a/miniprogram/pages/read/read.wxml +++ b/miniprogram/pages/read/read.wxml @@ -82,7 +82,7 @@ - {{seg.text}}{{seg.mentionDisplay}}#{{seg.label}} + {{seg.text}}{{seg.mentionDisplay}}#{{seg.label}}{{seg.text}} @@ -158,7 +158,7 @@ {{item[0].number}}.{{item[0].text}} - {{seg.text}}{{seg.mentionDisplay}}#{{seg.label}} + {{seg.text}}{{seg.mentionDisplay}}#{{seg.label}}{{seg.text}} @@ -251,7 +251,7 @@ {{item[0].number}}.{{item[0].text}} - {{seg.text}}{{seg.mentionDisplay}}#{{seg.label}} + {{seg.text}}{{seg.mentionDisplay}}#{{seg.label}}{{seg.text}} diff --git a/miniprogram/pages/read/read.wxss b/miniprogram/pages/read/read.wxss index 4ee7f9c3..435a5d3e 100644 --- a/miniprogram/pages/read/read.wxss +++ b/miniprogram/pages/read/read.wxss @@ -282,6 +282,14 @@ padding: 0 4rpx; } +/* 正文内普通超链接(管理端插入 a 标签) */ +.paragraph .article-link { + color: #67e8f9; + text-decoration: underline; + text-underline-offset: 4rpx; + font-weight: 500; +} + /* 正文内图片 */ .content-image { width: 100%; diff --git a/miniprogram/pages/super-article-detail/super-article-detail.js b/miniprogram/pages/super-article-detail/super-article-detail.js index 57d491e4..a5b0c7ad 100644 --- a/miniprogram/pages/super-article-detail/super-article-detail.js +++ b/miniprogram/pages/super-article-detail/super-article-detail.js @@ -21,6 +21,22 @@ function buildH5ReadUrl(articleId) { function renderArticleHtml(text) { const src = String(text || '') + const isHtml = /<[a-z][\s\S]*>/i.test(src) + + // 富文本:保留原 HTML,仅统一补充 a 标签样式(可点击 + 高亮) + if (isHtml) { + return src.replace(/]*)>/gi, (full, attrs) => { + if (!/href\s*=/.test(attrs)) return full + const hasStyle = /\sstyle\s*=/.test(attrs) + const styleText = 'color:#22d3ee;text-decoration:underline;word-break:break-all;' + if (hasStyle) { + return ` ` style=${q}${s};${styleText}${q}`)}>` + } + return `` + }) + } + + // 纯文本:高亮 @/# 并自动识别 URL const escaped = src .replace(/&/g, '&') .replace(/$2') const withLinks = withTags.replace( /(https?:\/\/[^\s<]+)/g, - '$1' + '$1' ) return withLinks.replace(/\n/g, '
') } diff --git a/miniprogram/utils/contentParser.js b/miniprogram/utils/contentParser.js index b74bb6cd..db1339bc 100644 --- a/miniprogram/utils/contentParser.js +++ b/miniprogram/utils/contentParser.js @@ -6,6 +6,7 @@ * { type: 'text', text } * { type: 'mention', userId, nickname } — @某人,点击加好友(提交存客宝见 utils/soulBridge.submitCkbLead) * { type: 'linkTag', label, url, ... } — #链接标签,点击跳转(阅读页 onLinkTagTap:外链→link-preview、小程序→navigateToMiniProgram) + * { type: 'link', text, url } — 普通超链接,点击外链预览(阅读页 onArticleLinkTap) * { type: 'image', src, alt } — 图片 * { type: 'video', src } — 内嵌视频(管理端 rich-video-wrap /