diff --git a/miniprogram/pages/member-detail/member-detail.js b/miniprogram/pages/member-detail/member-detail.js
index a93673a7..8a95f6e7 100644
--- a/miniprogram/pages/member-detail/member-detail.js
+++ b/miniprogram/pages/member-detail/member-detail.js
@@ -684,7 +684,7 @@ Page({
goToSuperArticleDetail(e) {
const id = e.currentTarget?.dataset?.id
if (!id) return
- wx.navigateTo({ url: `/pages/super-article-detail/super-article-detail?id=${encodeURIComponent(String(id))}` })
+ wx.navigateTo({ url: `/pages/read/read?superArticleId=${encodeURIComponent(String(id))}` })
},
/** 有档案块(职业画像/擅长/故事/互助)或动态(含加载中)时展示 Tab 区 */
diff --git a/miniprogram/pages/read/read.js b/miniprogram/pages/read/read.js
index 43b2367b..3c20103f 100644
--- a/miniprogram/pages/read/read.js
+++ b/miniprogram/pages/read/read.js
@@ -20,6 +20,7 @@ const contentParser = require('../../utils/contentParser.js')
const { trackClick, trackViewChapter } = require('../../utils/trackClick')
const { checkAndExecute, executeRule, getChapterReadMarketingRule } = require('../../utils/ruleEngine')
const soulBridge = require('../../utils/soulBridge.js')
+const superArticleDetailUtil = require('../../utils/superArticleDetail.js')
const app = getApp()
const mpPagePopups = require('../../utils/mpPagePopups.js')
@@ -218,7 +219,17 @@ Page({
// 系统信息
statusBarHeight: 44,
navBarHeight: 88,
-
+
+ /** chapter:书籍章节;super_article:超级个体动态(与独立动态详情合并为本页) */
+ pageMode: 'chapter',
+ superArticle: null,
+ superArticleId: '',
+ superGalleryItems: [],
+ superLoading: true,
+ superAuditTip: '',
+ superAuditRejectReason: '',
+ superShowAuditEdit: false,
+
// 章节信息
sectionId: '',
section: null,
@@ -471,6 +482,98 @@ Page({
if (!sp) {
this.refreshPay365MarketingFromRules()
}
+ if (this.data.pageMode === 'super_article' && this.data.superArticleId) {
+ this._superArticleShowCount = (this._superArticleShowCount || 0) + 1
+ if (this._superArticleShowCount > 1) {
+ this.loadSuperArticleDetail(this.data.superArticleId)
+ }
+ }
+ },
+
+ /**
+ * 超级个体动态:与章节阅读共用本页(query superArticleId / saId)
+ */
+ async _bootstrapSuperArticlePage(idStr) {
+ this._superArticleShowCount = 0
+ this.setData({
+ pageMode: 'super_article',
+ superArticleId: String(idStr),
+ superLoading: true,
+ loading: false,
+ accessState: 'unknown',
+ sectionId: '',
+ sectionMid: null,
+ superArticle: null,
+ superGalleryItems: [],
+ superAuditTip: '',
+ superAuditRejectReason: '',
+ superShowAuditEdit: false,
+ statusBarHeight: app.globalData.statusBarHeight || 44,
+ navBarHeight: app.globalData.navBarHeight || 88,
+ })
+ await this.loadSuperArticleDetail(idStr)
+ },
+
+ async loadSuperArticleDetail(id) {
+ this.setData({ superLoading: true })
+ const baseUrl = String((app.globalData && app.globalData.baseUrl) || '').replace(/\/$/, '')
+ try {
+ const viewerId = app.globalData.userInfo?.id
+ const q =
+ viewerId != null && String(viewerId).trim() !== ''
+ ? `?viewerUserId=${encodeURIComponent(String(viewerId).trim())}`
+ : ''
+ const res = await app.request({
+ url: `/api/miniprogram/super/articles/${encodeURIComponent(String(id))}${q}`,
+ silent: true,
+ })
+ const article = res?.data
+ if (!article) {
+ this.setData({ superLoading: false, superArticle: null, superGalleryItems: [] })
+ return
+ }
+ const audit = superArticleDetailUtil.buildSuperArticleAuditMeta(article, app.globalData.userInfo?.id)
+ const galleryItems = superArticleDetailUtil.buildGalleryItems(article.images, baseUrl)
+ this.setData({
+ superArticle: Object.assign({}, article, {
+ createdAtText: superArticleDetailUtil.formatSuperArticleTime(article.createdAt),
+ contentHtml: superArticleDetailUtil.renderSuperArticleHtml(article.content),
+ }),
+ superGalleryItems: galleryItems,
+ superAuditTip: audit.auditTip,
+ superAuditRejectReason: audit.auditRejectReason,
+ superShowAuditEdit: audit.showAuditEdit,
+ superLoading: false,
+ })
+ } catch (_) {
+ this.setData({ superLoading: false, superArticle: null, superGalleryItems: [] })
+ }
+ },
+
+ goSuperArticleEdit() {
+ const id = this.data.superArticleId
+ if (!id) return
+ wx.navigateTo({
+ url: `/pages/super-article-editor/super-article-editor?id=${encodeURIComponent(String(id))}`,
+ })
+ },
+
+ onSuperGalleryImgError(e) {
+ const idx = Number(e.currentTarget.dataset.idx)
+ if (Number.isNaN(idx)) return
+ this.setData({ [`superGalleryItems[${idx}].loadFailed`]: true })
+ },
+
+ previewSuperGallery(e) {
+ const current = e.currentTarget.dataset.src || ''
+ const urls = (this.data.superGalleryItems || [])
+ .filter((x) => x.src && !x.loadFailed)
+ .map((x) => x.src)
+ if (!urls.length) return
+ wx.previewImage({
+ urls,
+ current: current || urls[0],
+ })
},
/**
@@ -478,6 +581,7 @@ Page({
* 修复:管理端取消超级个体后,若仍用订单兜底 VIP,需配合后端 isVipEffective;同时刷新端上 showPaywall/全文裁切。
*/
async _refreshAccessOnShow() {
+ if (this.data.pageMode === 'super_article') return
const id = this.data.sectionId
if (!id || this.data.loading || this.data.readSinglePageMode) return
const uid = app.globalData.userInfo?.id
@@ -521,6 +625,13 @@ Page({
async onLoad(options) {
wx.showShareMenu({ menus: ['shareAppMessage', 'shareTimeline'] })
+ const superRaw = (options && (options.superArticleId || options.saId)) || ''
+ const superArticleCandidate = String(superRaw).trim()
+ if (superArticleCandidate && /^\d+$/.test(superArticleCandidate)) {
+ await this._bootstrapSuperArticlePage(superArticleCandidate)
+ return
+ }
+
// 预加载:core+auditMode(getConfig)+ read-extras 懒加载(linkTags、linkedMiniprograms)
Promise.all([
app.getConfig(),
@@ -1510,6 +1621,16 @@ Page({
// 分享到微信 - 自动带分享人ID
onShareAppMessage(e) {
+ if (this.data.pageMode === 'super_article') {
+ const a = this.data.superArticle
+ const sid = this.data.superArticleId
+ const q = sid ? `superArticleId=${encodeURIComponent(String(sid))}` : ''
+ return {
+ title: a && a.title ? a.title : '动态',
+ path: sid ? `/pages/read/read?${q}` : '/pages/read/read',
+ }
+ }
+
trackClick('read', 'btn_click', '分享_' + this.data.sectionId)
const { section, sectionId, sectionMid } = this.data
const ref = app.getMyReferralCode()
@@ -1572,6 +1693,16 @@ Page({
// 分享到朋友圈:带文章标题,过长时截断
onShareTimeline() {
+ if (this.data.pageMode === 'super_article') {
+ const a = this.data.superArticle
+ const sid = this.data.superArticleId
+ const query = sid ? `superArticleId=${encodeURIComponent(String(sid))}` : ''
+ return {
+ title: a && a.title ? a.title : '动态',
+ query,
+ }
+ }
+
const { section, sectionId, sectionMid, chapterTitle, readUi } = this.data
const ref = app.getMyReferralCode()
const q = sectionMid ? `mid=${sectionMid}` : `id=${sectionId}`
diff --git a/miniprogram/pages/read/read.wxml b/miniprogram/pages/read/read.wxml
index e855a0a3..0f63f1c6 100644
--- a/miniprogram/pages/read/read.wxml
+++ b/miniprogram/pages/read/read.wxml
@@ -1,6 +1,7 @@
-
+
+
@@ -512,4 +513,56 @@
{{pay365AnchorLabel}}
›
+
+
+
+
+
+
+
+ 文章详情
+
+
+
+
+
+ 加载中…
+
+
+
+
+ {{superAuditTip}}
+ {{superAuditRejectReason}}
+ 重新编辑并提交
+
+ {{superArticle.title}}
+ 动态
+ {{superArticle.authorNickname || '超级个体'}} · {{superArticle.createdAtText}}
+
+
+
+
+
+
+
+ {{item.src ? '图片加载失败' : '配图缺失或地址无效'}}
+
+
+
+
+
+
+
+
+ 动态不存在或已下线
+
+
diff --git a/miniprogram/pages/read/read.wxss b/miniprogram/pages/read/read.wxss
index 435a5d3e..a0818862 100644
--- a/miniprogram/pages/read/read.wxss
+++ b/miniprogram/pages/read/read.wxss
@@ -1964,3 +1964,140 @@ button.action-share-native {
color: #38bdac;
margin-left: 12rpx;
}
+
+/* ---------- 超级个体动态(与章节共用阅读页 pageMode=super_article) ---------- */
+.read-super-nav-bar {
+ position: fixed;
+ left: 0;
+ right: 0;
+ top: 0;
+ z-index: 10;
+ height: 44px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 16rpx;
+ background: rgba(5, 11, 20, 0.92);
+ border-bottom: 1rpx solid rgba(34, 211, 238, 0.12);
+}
+.read-super-nav-back,
+.read-super-nav-placeholder {
+ width: 44px;
+ height: 44px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.read-super-nav-title {
+ font-size: 32rpx;
+ font-weight: 700;
+ color: #fff;
+}
+.read-super-nav-offset {
+ width: 100%;
+}
+.read-super-loading {
+ padding-top: 120rpx;
+ text-align: center;
+}
+.read-super-loading-text {
+ font-size: 26rpx;
+ color: #94a3b8;
+}
+.read-super-body {
+ padding: 8rpx 24rpx 48rpx;
+}
+.read-super-audit-banner {
+ margin-bottom: 20rpx;
+ padding: 20rpx 22rpx;
+ border-radius: 16rpx;
+ background: rgba(251, 191, 36, 0.08);
+ border: 1rpx solid rgba(251, 191, 36, 0.28);
+}
+.read-super-audit-main {
+ display: block;
+ font-size: 26rpx;
+ color: #fcd34d;
+ font-weight: 600;
+}
+.read-super-audit-sub {
+ display: block;
+ margin-top: 12rpx;
+ font-size: 24rpx;
+ color: rgba(226, 232, 240, 0.88);
+ line-height: 1.45;
+}
+.read-super-audit-edit-btn {
+ margin-top: 18rpx;
+ display: inline-block;
+ padding: 14rpx 28rpx;
+ border-radius: 999rpx;
+ font-size: 24rpx;
+ font-weight: 600;
+ color: #0b1220;
+ background: linear-gradient(135deg, #00ced1, #20b2aa);
+}
+.read-super-title {
+ font-size: 36rpx;
+ font-weight: 700;
+ line-height: 1.4;
+ display: block;
+ color: #fff;
+}
+.read-super-title-muted {
+ color: #94a3b8;
+ font-weight: 600;
+}
+.read-super-meta {
+ margin-top: 12rpx;
+ font-size: 22rpx;
+ color: #94a3b8;
+ display: block;
+}
+.read-super-gallery {
+ margin-top: 20rpx;
+ display: flex;
+ flex-direction: column;
+ gap: 12rpx;
+}
+.read-super-gallery-cell {
+ width: 100%;
+}
+.read-super-gallery-img {
+ width: 100%;
+ display: block;
+ border-radius: 12rpx;
+ box-sizing: border-box;
+ border: 2rpx solid rgba(34, 211, 238, 0.32);
+ background: rgba(15, 23, 42, 0.6);
+}
+.read-super-gallery-ph {
+ min-height: 220rpx;
+ border-radius: 12rpx;
+ border: 2rpx dashed rgba(148, 163, 184, 0.35);
+ background: rgba(15, 23, 42, 0.55);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 24rpx;
+ box-sizing: border-box;
+}
+.read-super-gallery-ph-text {
+ font-size: 24rpx;
+ color: #64748b;
+ text-align: center;
+}
+.read-super-content {
+ margin-top: 24rpx;
+ font-size: 28rpx;
+ line-height: 1.75;
+ color: #e2e8f0;
+ white-space: pre-wrap;
+ word-break: break-word;
+}
+.read-super-empty {
+ margin-top: 200rpx;
+ text-align: center;
+ color: #94a3b8;
+ font-size: 26rpx;
+}
diff --git a/miniprogram/pages/super-article-detail/super-article-detail.js b/miniprogram/pages/super-article-detail/super-article-detail.js
index 22ec506a..ab157aa2 100644
--- a/miniprogram/pages/super-article-detail/super-article-detail.js
+++ b/miniprogram/pages/super-article-detail/super-article-detail.js
@@ -1,196 +1,16 @@
-const app = getApp()
-
-function formatTime(value) {
- if (!value) return ''
- const d = new Date(value)
- if (isNaN(d.getTime())) return ''
- const m = `${d.getMonth() + 1}`.padStart(2, '0')
- const day = `${d.getDate()}`.padStart(2, '0')
- const hh = `${d.getHours()}`.padStart(2, '0')
- const mm = `${d.getMinutes()}`.padStart(2, '0')
- return `${m}-${day} ${hh}:${mm}`
-}
-
-/** 解析详情接口 images:数组或 JSON 字符串 */
-function parseImagesField(images) {
- if (images == null) return []
- if (typeof images === 'string') {
- const s = images.trim()
- if (!s) return []
- try {
- const p = JSON.parse(s)
- return Array.isArray(p) ? p : []
- } catch (_) {
- return []
- }
- }
- return Array.isArray(images) ? images : []
-}
-
/**
- * 转成小程序 可用的地址:相对路径 /uploads/... 须拼 API 域名(与 request 同源),否则真机不显示
+ * Soul创业派对 - 兼容入口:动态详情已并入阅读页 pages/read/read(superArticleId)
+ * 旧链接 / 分享路径仍可能指向本页,此处统一 redirect。
*/
-function normalizeArticleImageSrc(raw) {
- const u = String(raw || '').trim()
- if (!u || u === 'undefined' || u === 'null') return ''
- if (/^https?:\/\//i.test(u)) return u
- if (u.startsWith('//')) return `https:${u}`
- if (u.startsWith('wxfile://') || u.startsWith('cloud://')) return u
- const base = String((app.globalData && app.globalData.baseUrl) || '').replace(/\/$/, '')
- if (u.startsWith('/') && base) return `${base}${u}`
- return u
-}
-
-function buildGalleryItems(imagesField) {
- const rawList = parseImagesField(imagesField)
- return rawList.map((u, idx) => {
- const src = normalizeArticleImageSrc(String(u || '').trim())
- const ok =
- !!src &&
- (/^https?:\/\//i.test(src) || src.startsWith('wxfile://') || src.startsWith('cloud://'))
- return { idx, src: ok ? src : '', loadFailed: false }
- })
-}
-
-function renderArticleHtml(text) {
- const src = String(text || '')
- const isHtml = /<[a-z][\s\S]*>/i.test(src)
-
- 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 ``
- })
- }
-
- const escaped = src
- .replace(/&/g, '&')
- .replace(//g, '>')
- .replace(/"/g, '"')
- const withMentions = escaped.replace(/(^|[\s])(@[\u4e00-\u9fa5A-Za-z0-9_]+)/g, '$1$2')
- const withTags = withMentions.replace(/(^|[\s])(#[^\s#]+)/g, '$1$2')
- const withLinks = withTags.replace(
- /(https?:\/\/[^\s<]+)/g,
- '$1'
- )
- return withLinks.replace(/\n/g, '
')
-}
-
Page({
- data: {
- statusBarHeight: 44,
- article: null,
- articleId: '',
- galleryItems: [],
- loading: true,
- auditTip: '',
- auditRejectReason: '',
- showAuditEdit: false,
- },
onLoad(options) {
- this.setData({ statusBarHeight: app.globalData.statusBarHeight || 44 })
- const id = options?.id
- if (!id) {
- this.setData({ loading: false })
+ const id = options?.id != null ? String(options.id).trim() : ''
+ if (!id || !/^\d+$/.test(id)) {
+ wx.navigateBack({ fail: () => wx.switchTab({ url: '/pages/index/index' }) })
return
}
- this.loadArticle(id)
- },
- async loadArticle(id) {
- this.setData({ loading: true })
- try {
- const viewerId = app.globalData.userInfo?.id
- const q =
- viewerId != null && String(viewerId).trim() !== ''
- ? `?viewerUserId=${encodeURIComponent(String(viewerId).trim())}`
- : ''
- const res = await app.request({
- url: `/api/miniprogram/super/articles/${encodeURIComponent(String(id))}${q}`,
- silent: true,
- })
- const article = res?.data
- if (!article) {
- this.setData({ loading: false, galleryItems: [] })
- return
- }
- const uid = app.globalData.userInfo?.id
- const isOwner = !!(uid != null && String(article.userId) === String(uid))
- const st = String(article.auditStatus || 'approved').toLowerCase()
- let auditTip = ''
- let auditRejectReason = ''
- let showAuditEdit = false
- if (isOwner && st === 'pending') {
- auditTip = '审核中:通过后将在动态广场展示'
- } else if (isOwner && st === 'rejected') {
- auditTip = '未通过审核'
- auditRejectReason = String(article.rejectReason || '').trim()
- showAuditEdit = true
- }
- const galleryItems = buildGalleryItems(article.images)
- this.setData({
- article: Object.assign({}, article, {
- createdAtText: formatTime(article.createdAt),
- contentHtml: renderArticleHtml(article.content),
- }),
- galleryItems,
- articleId: String(id),
- auditTip,
- auditRejectReason,
- showAuditEdit,
- loading: false,
- })
- } catch (_) {
- this.setData({ loading: false, galleryItems: [] })
- }
- },
- goEditArticle() {
- const id = this.data.articleId
- if (!id) return
- wx.navigateTo({
- url: `/pages/super-article-editor/super-article-editor?id=${encodeURIComponent(String(id))}`,
+ wx.redirectTo({
+ url: `/pages/read/read?superArticleId=${encodeURIComponent(id)}`,
})
},
- goBack() {
- wx.navigateBack({ fail: () => wx.switchTab({ url: '/pages/my/my' }) })
- },
- onGalleryImgError(e) {
- const idx = Number(e.currentTarget.dataset.idx)
- if (Number.isNaN(idx)) return
- this.setData({ [`galleryItems[${idx}].loadFailed`]: true })
- },
- previewGallery(e) {
- const current = e.currentTarget.dataset.src || ''
- const urls = (this.data.galleryItems || [])
- .filter((x) => x.src && !x.loadFailed)
- .map((x) => x.src)
- if (!urls.length) return
- wx.previewImage({
- urls,
- current: current || urls[0],
- })
- },
- onShareAppMessage() {
- const a = this.data.article
- const id = this.data.articleId
- return {
- title: a && a.title ? a.title : '动态',
- path: id
- ? `/pages/super-article-detail/super-article-detail?id=${encodeURIComponent(String(id))}`
- : '/pages/super-article-detail/super-article-detail',
- }
- },
- onShareTimeline() {
- const a = this.data.article
- const id = this.data.articleId
- return {
- title: a && a.title ? a.title : '动态',
- query: id ? `id=${encodeURIComponent(String(id))}` : '',
- }
- },
})
diff --git a/miniprogram/pages/super-article-detail/super-article-detail.wxml b/miniprogram/pages/super-article-detail/super-article-detail.wxml
index b776fbb5..20efafef 100644
--- a/miniprogram/pages/super-article-detail/super-article-detail.wxml
+++ b/miniprogram/pages/super-article-detail/super-article-detail.wxml
@@ -1,46 +1 @@
-
-
-
-
-
- 动态详情
-
-
-
-
-
-
- {{auditTip}}
- {{auditRejectReason}}
- 重新编辑并提交
-
- {{article.title}}
- 动态
- {{article.authorNickname || '超级个体'}} · {{article.createdAtText}}
-
-
-
-
-
-
-
- {{item.src ? '图片加载失败' : '配图缺失或地址无效'}}
-
-
-
-
-
-
-
-
- 文章不存在或已下线
-
-
+
diff --git a/miniprogram/pages/super-article-detail/super-article-detail.wxss b/miniprogram/pages/super-article-detail/super-article-detail.wxss
index 0aa9d435..f569d30a 100644
--- a/miniprogram/pages/super-article-detail/super-article-detail.wxss
+++ b/miniprogram/pages/super-article-detail/super-article-detail.wxss
@@ -1,87 +1,4 @@
-.page { min-height: 100vh; background: #0b1220; color: #fff; }
-.nav-bar {
- position: fixed; left: 0; right: 0; top: 0; z-index: 10;
- display: flex; align-items: center; justify-content: space-between;
- height: 44px; padding: 0 24rpx; background: rgba(5, 11, 20, 0.9);
-}
-.nav-back, .nav-placeholder { width: 64rpx; }
-.nav-title { font-size: 32rpx; font-weight: 700; }
-.article-body {
- padding: 8rpx 24rpx 48rpx;
-}
-.audit-banner {
- margin-bottom: 20rpx;
- padding: 20rpx 22rpx;
- border-radius: 16rpx;
- background: rgba(251, 191, 36, 0.08);
- border: 1rpx solid rgba(251, 191, 36, 0.28);
-}
-.audit-banner-main {
- display: block;
- font-size: 26rpx;
- color: #fcd34d;
- font-weight: 600;
-}
-.audit-banner-sub {
- display: block;
- margin-top: 12rpx;
- font-size: 24rpx;
- color: rgba(226, 232, 240, 0.88);
- line-height: 1.45;
-}
-.audit-edit-btn {
- margin-top: 18rpx;
- display: inline-block;
- padding: 14rpx 28rpx;
- border-radius: 999rpx;
- font-size: 24rpx;
- font-weight: 600;
- color: #0b1220;
- background: linear-gradient(135deg, #00ced1, #20b2aa);
-}
-.title { font-size: 36rpx; font-weight: 700; line-height: 1.4; display: block; }
-.title-muted { color: #94a3b8; font-weight: 600; }
-.meta { margin-top: 12rpx; font-size: 22rpx; color: #94a3b8; display: block; }
-.img-gallery {
- margin-top: 20rpx;
- display: flex;
- flex-direction: column;
- gap: 12rpx;
-}
-.gallery-cell {
- width: 100%;
-}
-.article-gallery-img {
- width: 100%;
- display: block;
- border-radius: 12rpx;
- box-sizing: border-box;
- border: 2rpx solid rgba(34, 211, 238, 0.32);
- background: rgba(15, 23, 42, 0.6);
-}
-.gallery-placeholder {
- min-height: 220rpx;
- margin-top: 0;
- border-radius: 12rpx;
- border: 2rpx dashed rgba(148, 163, 184, 0.35);
- background: rgba(15, 23, 42, 0.55);
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 12rpx;
- padding: 24rpx;
- box-sizing: border-box;
-}
-.gallery-ph-text {
- font-size: 24rpx;
- color: #64748b;
- text-align: center;
-}
-.content {
- margin-top: 24rpx; font-size: 28rpx; line-height: 1.75;
- color: #e2e8f0; white-space: pre-wrap; word-break: break-word;
-}
-.empty {
- margin-top: 200rpx; text-align: center; color: #94a3b8; font-size: 26rpx;
+.super-article-detail-redirect {
+ min-height: 100vh;
+ background: #0b1220;
}
diff --git a/miniprogram/pages/super-article-mine/super-article-mine.js b/miniprogram/pages/super-article-mine/super-article-mine.js
index a74c9a4a..a28e9e77 100644
--- a/miniprogram/pages/super-article-mine/super-article-mine.js
+++ b/miniprogram/pages/super-article-mine/super-article-mine.js
@@ -4,6 +4,7 @@
const app = getApp()
const PAGE_SIZE = 10
+const THUMB_MAX = 3
function relativeTime(iso) {
if (!iso) return ''
@@ -27,15 +28,65 @@ function auditLabel(st) {
return '已发布'
}
+function parseImagesField(images) {
+ if (images == null) return []
+ if (typeof images === 'string') {
+ const s = images.trim()
+ if (!s) return []
+ try {
+ const p = JSON.parse(s)
+ return Array.isArray(p) ? p : []
+ } catch (_) {
+ return []
+ }
+ }
+ return Array.isArray(images) ? images : []
+}
+
+function normalizeArticleImageSrc(raw) {
+ const u = String(raw || '').trim()
+ if (!u || u === 'undefined' || u === 'null') return ''
+ if (/^https?:\/\//i.test(u)) return u
+ if (u.startsWith('//')) return `https:${u}`
+ if (u.startsWith('wxfile://') || u.startsWith('cloud://')) return u
+ const base = String((app.globalData && app.globalData.baseUrl) || '').replace(/\/$/, '')
+ if (u.startsWith('/') && base) return `${base}${u}`
+ return u
+}
+
+function buildThumbSlots(imagesField) {
+ const rawList = parseImagesField(imagesField)
+ const n = Math.min(rawList.length, THUMB_MAX)
+ const thumbSlots = []
+ for (let i = 0; i < n; i++) {
+ const srcNorm = normalizeArticleImageSrc(String(rawList[i] || '').trim())
+ const ok =
+ !!srcNorm &&
+ (/^https?:\/\//i.test(srcNorm) ||
+ srcNorm.startsWith('wxfile://') ||
+ srcNorm.startsWith('cloud://'))
+ thumbSlots.push({ slotIdx: i, src: ok ? srcNorm : '', loadFailed: false })
+ }
+ return {
+ showThumbRow: rawList.length > 0,
+ thumbSlots,
+ thumbMoreCount: rawList.length > THUMB_MAX ? rawList.length - THUMB_MAX : 0,
+ }
+}
+
function mapRows(rows) {
return (rows || []).map((r) => {
const st = String(r.auditStatus || 'approved').toLowerCase()
+ const thumb = buildThumbSlots(r.images)
return Object.assign({}, r, {
auditStatus: st === 'pending' || st === 'rejected' ? st : 'approved',
auditLabel: auditLabel(st),
timeText: relativeTime(r.createdAt),
preview: String(r.preview || '').trim(),
rejectReason: String(r.rejectReason || '').trim(),
+ showThumbRow: thumb.showThumbRow,
+ thumbSlots: thumb.thumbSlots,
+ thumbMoreCount: thumb.thumbMoreCount,
})
})
}
@@ -88,7 +139,7 @@ Page({
openDetail(e) {
const id = e.currentTarget?.dataset?.id
if (!id) return
- wx.navigateTo({ url: `/pages/super-article-detail/super-article-detail?id=${encodeURIComponent(String(id))}` })
+ wx.navigateTo({ url: `/pages/read/read?superArticleId=${encodeURIComponent(String(id))}` })
},
openEdit(e) {
@@ -138,6 +189,26 @@ Page({
})
},
+ onThumbError(e) {
+ const rowIdx = Number(e.currentTarget.dataset.rowidx)
+ const slotIdx = Number(e.currentTarget.dataset.slotidx)
+ if (Number.isNaN(rowIdx) || Number.isNaN(slotIdx)) return
+ this.setData({ [`list[${rowIdx}].thumbSlots[${slotIdx}].loadFailed`]: true })
+ },
+
+ previewRowImages(e) {
+ const rowIdx = Number(e.currentTarget.dataset.rowidx)
+ if (Number.isNaN(rowIdx)) return
+ const row = this.data.list[rowIdx]
+ if (!row) return
+ const urls = parseImagesField(row.images)
+ .map((u) => normalizeArticleImageSrc(String(u || '').trim()))
+ .filter((u) => /^https?:\/\//i.test(u))
+ if (!urls.length) return
+ const current = String(e.currentTarget.dataset.current || '').trim() || urls[0]
+ wx.previewImage({ urls, current })
+ },
+
async reload() {
const userId = app.globalData.userInfo?.id
if (!userId || !app.globalData.isLoggedIn) {
diff --git a/miniprogram/pages/super-article-mine/super-article-mine.wxml b/miniprogram/pages/super-article-mine/super-article-mine.wxml
index 593d4eeb..b80844e4 100644
--- a/miniprogram/pages/super-article-mine/super-article-mine.wxml
+++ b/miniprogram/pages/super-article-mine/super-article-mine.wxml
@@ -25,7 +25,7 @@
点击上方「发动态」写一条吧
-
+
{{item.auditLabel}}
@@ -34,7 +34,31 @@
{{item.title}}
{{item.preview}}
动态
- 正文与配图请在详情页查看
+
+
+
+
+
+
+
+ {{th.loadFailed ? '加载失败' : '无效'}}
+
+
+
+ +{{item.thumbMoreCount}}
+
+
+
+ 正文请在详情页查看
{
+ const src = normalizeArticleImageSrc(String(url || '').trim(), baseUrl)
+ const ok =
+ !!src &&
+ (/^https?:\/\//i.test(src) || src.startsWith('wxfile://') || src.startsWith('cloud://'))
+ return { idx, src: ok ? src : '', loadFailed: false }
+ })
+}
+
+function renderSuperArticleHtml(text) {
+ const src = String(text || '')
+ const isHtml = /<[a-z][\s\S]*>/i.test(src)
+
+ 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 ``
+ })
+ }
+
+ const escaped = src
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ const withMentions = escaped.replace(/(^|[\s])(@[\u4e00-\u9fa5A-Za-z0-9_]+)/g, '$1$2')
+ const withTags = withMentions.replace(/(^|[\s])(#[^\s#]+)/g, '$1$2')
+ const withLinks = withTags.replace(
+ /(https?:\/\/[^\s<]+)/g,
+ '$1'
+ )
+ return withLinks.replace(/\n/g, '
')
+}
+
+/** 根据详情接口 data + 当前登录用户,生成 setData 用的文案与按钮态 */
+function buildSuperArticleAuditMeta(article, viewerUserId) {
+ const uid = viewerUserId
+ const isOwner = !!(uid != null && article && String(article.userId) === String(uid))
+ const st = String((article && article.auditStatus) || 'approved').toLowerCase()
+ let auditTip = ''
+ let auditRejectReason = ''
+ let showAuditEdit = false
+ if (isOwner && st === 'pending') {
+ auditTip = '审核中:通过后将在动态广场展示'
+ } else if (isOwner && st === 'rejected') {
+ auditTip = '未通过审核'
+ auditRejectReason = String((article && article.rejectReason) || '').trim()
+ showAuditEdit = true
+ }
+ return { auditTip, auditRejectReason, showAuditEdit }
+}
+
+module.exports = {
+ formatSuperArticleTime,
+ parseImagesField,
+ normalizeArticleImageSrc,
+ buildGalleryItems,
+ renderSuperArticleHtml,
+ buildSuperArticleAuditMeta,
+}
diff --git a/soul-api/internal/handler/h5_super_article.go b/soul-api/internal/handler/h5_super_article.go
index 9c3f8e14..20877e86 100644
--- a/soul-api/internal/handler/h5_super_article.go
+++ b/soul-api/internal/handler/h5_super_article.go
@@ -116,11 +116,11 @@ func H5SuperArticlePage(c *gin.Context) {
publicBase = "https://soulapi.quwanzhi.com"
}
qv := url.Values{}
- qv.Set("id", idStr)
+ qv.Set("superArticleId", idStr)
if ref != "" {
qv.Set("ref", ref)
}
- mpPath := "pages/super-article-detail/super-article-detail?" + qv.Encode()
+ mpPath := "pages/read/read?" + qv.Encode()
canonical := fmt.Sprintf("%s/s/%s", publicBase, url.PathEscape(idStr))
if ref != "" {