feat: 内容管理第5批优化 - Bug修复 + 分享功能 + 代付功能
1. Bug修复: - 修复Markdown星号/下划线在小程序端原样显示问题(markdownToHtml增加__和_支持,contentParser增加Markdown格式剥离) - 修复@提及无反应(MentionSuggestion使用ref保持persons最新值,解决闭包捕获空数组问题) - 修复#链接标签点击"未找到小程序配置"(增加appId直接跳转降级路径) 2. 分享功能优化: - "分享到朋友圈"改为"分享给好友"(open-type从shareTimeline改为share) - 90%收益提示移到分享按钮下方 - 阅读20%后向上滑动弹出分享浮层提示(4秒自动消失) 3. 代付功能: - 后端:新增UserBalance/BalanceTransaction/GiftUnlock三个模型 - 后端:新增8个余额相关API(查询/充值/充值确认/代付/领取/退款/交易记录/礼物信息) - 小程序:阅读页新增"代付分享"按钮,支持用余额为好友解锁章节 - 分享链接携带gift参数,好友打开自动领取解锁 Made-with: Cursor
This commit is contained in:
@@ -431,6 +431,7 @@ App({
|
||||
url: this.globalData.baseUrl + url,
|
||||
method: options.method || 'GET',
|
||||
data: options.data || {},
|
||||
timeout: options.timeout || 15000,
|
||||
header: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': token ? `Bearer ${token}` : '',
|
||||
|
||||
@@ -57,7 +57,10 @@
|
||||
]
|
||||
},
|
||||
"usingComponents": {},
|
||||
"navigateToMiniProgramAppIdList": [],
|
||||
"navigateToMiniProgramAppIdList": [
|
||||
"wx6489c26045912fe1",
|
||||
"wx3d15ed02e98b04e3"
|
||||
],
|
||||
"__usePrivacyCheck__": true,
|
||||
"lazyCodeLoading": "requiredComponents",
|
||||
"style": "v2",
|
||||
|
||||
@@ -108,13 +108,15 @@ Page({
|
||||
this.updateUserStatus()
|
||||
},
|
||||
|
||||
// 初始化数据:首次进页面并行异步加载,加快首屏展示
|
||||
initData() {
|
||||
this.setData({ loading: false })
|
||||
this.loadBookData()
|
||||
this.loadFeaturedFromServer()
|
||||
this.loadSuperMembers()
|
||||
this.loadLatestChapters()
|
||||
Promise.all([
|
||||
this.loadBookData(),
|
||||
this.loadFeaturedFromServer(),
|
||||
this.loadSuperMembers(),
|
||||
this.loadLatestChapters()
|
||||
]).finally(() => {
|
||||
this.setData({ loading: false })
|
||||
})
|
||||
},
|
||||
|
||||
async loadSuperMembers() {
|
||||
|
||||
@@ -6,6 +6,7 @@ Page({
|
||||
title: '链接预览',
|
||||
statusBarHeight: 44,
|
||||
navBarHeight: 88,
|
||||
loadError: false,
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
@@ -19,6 +20,26 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
onWebViewError() {
|
||||
this.setData({ loadError: true })
|
||||
wx.showModal({
|
||||
title: '无法在小程序内打开',
|
||||
content: '该链接无法在小程序内预览,是否复制链接到浏览器打开?',
|
||||
confirmText: '复制链接',
|
||||
cancelText: '返回',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
wx.setClipboardData({
|
||||
data: this.data.url,
|
||||
success: () => wx.showToast({ title: '链接已复制,请在浏览器打开', icon: 'none', duration: 2000 }),
|
||||
})
|
||||
} else {
|
||||
this.goBack()
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
goBack() {
|
||||
const pages = getCurrentPages()
|
||||
if (pages.length > 1) {
|
||||
|
||||
@@ -18,8 +18,14 @@
|
||||
<view class="nav-placeholder" style="height: {{navBarHeight}}px;"></view>
|
||||
|
||||
<!-- 链接预览区域 -->
|
||||
<view class="webview-wrap" wx:if="{{url}}">
|
||||
<web-view src="{{url}}"></web-view>
|
||||
<view class="webview-wrap" wx:if="{{url && !loadError}}">
|
||||
<web-view src="{{url}}" binderror="onWebViewError"></web-view>
|
||||
</view>
|
||||
<view class="error-wrap" wx:elif="{{loadError}}">
|
||||
<text class="error-text">该链接无法在小程序内预览</text>
|
||||
<view class="error-btn" bindtap="copyLink">
|
||||
<text class="error-btn-text">复制链接到浏览器打开</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="empty-wrap" wx:else>
|
||||
<text class="empty-text">暂无链接地址</text>
|
||||
|
||||
@@ -81,3 +81,27 @@ web-view {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.error-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 80rpx 40rpx;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.error-btn {
|
||||
padding: 16rpx 40rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #38bdac;
|
||||
}
|
||||
|
||||
.error-btn-text {
|
||||
font-size: 26rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,9 @@ Page({
|
||||
showPosterModal: false,
|
||||
isPaying: false,
|
||||
isGeneratingPoster: false,
|
||||
showShareTip: false,
|
||||
_shareTipShown: false,
|
||||
_lastScrollTop: 0,
|
||||
|
||||
// 章节 mid(扫码/海报分享用,便于分享 path 带 mid)
|
||||
sectionMid: null
|
||||
@@ -97,8 +100,6 @@ Page({
|
||||
if (app.globalData.initialSectionMid) delete app.globalData.initialSectionMid
|
||||
if (app.globalData.initialSectionId) delete app.globalData.initialSectionId
|
||||
|
||||
console.log("页面:",mid);
|
||||
|
||||
// mid 有值但无 id 时,从 bookData 或 API 解析 id
|
||||
if (mid && !id) {
|
||||
const bookData = app.globalData.bookData || []
|
||||
@@ -138,6 +139,11 @@ Page({
|
||||
app.handleReferralCode({ query: { ref } })
|
||||
}
|
||||
|
||||
const giftCode = options.gift || ''
|
||||
if (giftCode) {
|
||||
this._pendingGiftCode = giftCode
|
||||
}
|
||||
|
||||
try {
|
||||
const config = await accessManager.fetchLatestConfig()
|
||||
this.setData({
|
||||
@@ -160,6 +166,13 @@ Page({
|
||||
// 加载内容(复用已拉取的章节数据,避免二次请求)
|
||||
await this.loadContent(id, accessState, chapterRes)
|
||||
|
||||
// 自动领取礼物码(代付解锁)
|
||||
if (this._pendingGiftCode && !canAccess && app.globalData.isLoggedIn) {
|
||||
await this._redeemGiftCode(this._pendingGiftCode)
|
||||
this._pendingGiftCode = null
|
||||
return
|
||||
}
|
||||
|
||||
// 【标准流程】4. 如果有权限,初始化阅读追踪
|
||||
if (canAccess) {
|
||||
readingTracker.init(id)
|
||||
@@ -184,6 +197,11 @@ Page({
|
||||
return
|
||||
}
|
||||
|
||||
const currentScrollTop = e.scrollTop || 0
|
||||
const lastScrollTop = this.data._lastScrollTop || 0
|
||||
const isScrollingDown = currentScrollTop < lastScrollTop
|
||||
this.setData({ _lastScrollTop: currentScrollTop })
|
||||
|
||||
// 获取滚动信息并更新追踪器
|
||||
const query = wx.createSelectorQuery()
|
||||
query.select('.page').boundingClientRect()
|
||||
@@ -202,6 +220,12 @@ Page({
|
||||
? Math.min((scrollInfo.scrollTop / totalScrollable) * 100, 100)
|
||||
: 0
|
||||
this.setData({ readingProgress: progress })
|
||||
|
||||
// 阅读超过20%且向上滑动时,弹出一次分享提示
|
||||
if (progress >= 20 && isScrollingDown && !this.data._shareTipShown) {
|
||||
this.setData({ showShareTip: true, _shareTipShown: true })
|
||||
setTimeout(() => { this.setData({ showShareTip: false }) }, 4000)
|
||||
}
|
||||
|
||||
// 更新阅读追踪器(记录最大进度、判断是否读完)
|
||||
readingTracker.updateProgress(scrollInfo)
|
||||
@@ -492,33 +516,38 @@ Page({
|
||||
}
|
||||
}
|
||||
|
||||
// CKB 类型:复用 @mention 加好友流程,弹出留资表单
|
||||
// CKB 类型:走「链接卡若」留资流程(与首页 onLinkKaruo 一致)
|
||||
if (tagType === 'ckb') {
|
||||
// 触发通用加好友(无特定 personId,使用全局 CKB Key)
|
||||
this.onMentionTap({ currentTarget: { dataset: { userId: '', nickname: label } } })
|
||||
this._doCkbLead(label)
|
||||
return
|
||||
}
|
||||
|
||||
// 小程序类型:用密钥查 linkedMiniprograms 得 appId,再唤醒(需在 app.json 的 navigateToMiniProgramAppIdList 中配置)
|
||||
// 小程序类型:先查 linkedMiniprograms 得 appId,降级直接用 mpKey/appId 字段
|
||||
if (tagType === 'miniprogram') {
|
||||
let appId = (e.currentTarget.dataset.appId || '').trim()
|
||||
if (!mpKey && label) {
|
||||
const cached = (app.globalData.linkTagsConfig || []).find(t => t.label === label)
|
||||
if (cached) mpKey = cached.mpKey || ''
|
||||
if (cached) {
|
||||
mpKey = cached.mpKey || ''
|
||||
if (!appId && cached.appId) appId = cached.appId
|
||||
}
|
||||
}
|
||||
const linked = (app.globalData.linkedMiniprograms || []).find(m => m.key === mpKey)
|
||||
if (linked && linked.appId) {
|
||||
const targetAppId = (linked && linked.appId) ? linked.appId : (appId || mpKey || '')
|
||||
if (targetAppId) {
|
||||
wx.navigateToMiniProgram({
|
||||
appId: linked.appId,
|
||||
path: pagePath || linked.path || '',
|
||||
appId: targetAppId,
|
||||
path: pagePath || (linked && linked.path) || '',
|
||||
envVersion: 'release',
|
||||
success: () => {},
|
||||
fail: (err) => {
|
||||
wx.showToast({ title: err.errMsg || '跳转失败', icon: 'none' })
|
||||
console.warn('[LinkTag] 小程序跳转失败:', err)
|
||||
wx.showToast({ title: '跳转失败,请检查小程序配置', icon: 'none' })
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
if (mpKey) wx.showToast({ title: '未找到关联小程序配置', icon: 'none' })
|
||||
wx.showToast({ title: '未配置关联小程序', icon: 'none' })
|
||||
}
|
||||
|
||||
// 小程序内部路径(pagePath 或 url 以 /pages/ 开头)
|
||||
@@ -638,6 +667,76 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
async _doCkbLead(label) {
|
||||
const app = getApp()
|
||||
if (!app.globalData.isLoggedIn || !app.globalData.userInfo) {
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: '请先登录后再链接',
|
||||
confirmText: '去登录',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) wx.switchTab({ url: '/pages/my/my' })
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
const userId = app.globalData.userInfo.id
|
||||
const leadLastTs = wx.getStorageSync('lead_last_submit_ts') || 0
|
||||
if (Date.now() - leadLastTs < 2 * 60 * 1000) {
|
||||
wx.showToast({ title: '操作太频繁,请2分钟后再试', icon: 'none' })
|
||||
return
|
||||
}
|
||||
let phone = (app.globalData.userInfo.phone || '').trim()
|
||||
let wechatId = (app.globalData.userInfo.wechatId || app.globalData.userInfo.wechat_id || '').trim()
|
||||
if (!phone && !wechatId) {
|
||||
try {
|
||||
const profileRes = await app.request({ url: `/api/miniprogram/user/profile?userId=${userId}`, silent: true })
|
||||
if (profileRes?.success && profileRes.data) {
|
||||
phone = (profileRes.data.phone || '').trim()
|
||||
wechatId = (profileRes.data.wechatId || profileRes.data.wechat_id || '').trim()
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
if (!phone && !wechatId) {
|
||||
wx.showModal({
|
||||
title: '完善资料',
|
||||
content: '请先填写手机号或微信号,以便对方联系您',
|
||||
confirmText: '去填写',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) wx.navigateTo({ url: '/pages/profile-edit/profile-edit' })
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
wx.showLoading({ title: '提交中...', mask: true })
|
||||
try {
|
||||
const res = await app.request({
|
||||
url: '/api/miniprogram/ckb/index-lead',
|
||||
method: 'POST',
|
||||
data: {
|
||||
userId,
|
||||
phone: phone || undefined,
|
||||
wechatId: wechatId || undefined,
|
||||
name: (app.globalData.userInfo.nickname || '').trim() || undefined,
|
||||
source: 'article_ckb_tag',
|
||||
tagLabel: label || undefined
|
||||
}
|
||||
})
|
||||
wx.hideLoading()
|
||||
if (res && res.success) {
|
||||
wx.setStorageSync('lead_last_submit_ts', Date.now())
|
||||
wx.showToast({ title: res.message || '提交成功', icon: 'success' })
|
||||
} else {
|
||||
wx.showToast({ title: (res && res.message) || '提交失败', icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: (e && e.message) || '提交失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
// 分享弹窗
|
||||
showShare() {
|
||||
this.setData({ showShareModal: true })
|
||||
@@ -687,14 +786,19 @@ Page({
|
||||
const { section, sectionId, sectionMid } = this.data
|
||||
const ref = app.getMyReferralCode()
|
||||
const q = sectionMid ? `mid=${sectionMid}` : `id=${sectionId}`
|
||||
const shareTitle = section?.title
|
||||
const giftCode = this._giftCodeToShare || ''
|
||||
this._giftCodeToShare = null
|
||||
|
||||
let shareTitle = section?.title
|
||||
? `📚 ${section.title.length > 20 ? section.title.slice(0, 20) + '...' : section.title}`
|
||||
: '📚 Soul创业派对 - 真实商业故事'
|
||||
return {
|
||||
title: shareTitle,
|
||||
path: ref ? `/pages/read/read?${q}&ref=${ref}` : `/pages/read/read?${q}`
|
||||
// 不设置 imageUrl,使用当前阅读页截图作为分享卡片中间图片
|
||||
}
|
||||
if (giftCode) shareTitle = `🎁 好友已为你解锁:${section?.title || '精选文章'}`
|
||||
|
||||
let path = `/pages/read/read?${q}`
|
||||
if (ref) path += `&ref=${ref}`
|
||||
if (giftCode) path += `&gift=${giftCode}`
|
||||
|
||||
return { title: shareTitle, path }
|
||||
},
|
||||
|
||||
// 分享到朋友圈:带文章标题,过长时截断(朋友圈卡片标题显示有限)
|
||||
@@ -1357,7 +1461,84 @@ Page({
|
||||
closePosterModal() {
|
||||
this.setData({ showPosterModal: false })
|
||||
},
|
||||
|
||||
|
||||
closeShareTip() {
|
||||
this.setData({ showShareTip: false })
|
||||
},
|
||||
|
||||
// 代付分享:用余额帮好友解锁当前章节
|
||||
async handleGiftPay() {
|
||||
if (!app.globalData.isLoggedIn || !app.globalData.userInfo) {
|
||||
wx.showModal({ title: '提示', content: '请先登录', confirmText: '去登录', success: (r) => { if (r.confirm) this.showLoginModal() } })
|
||||
return
|
||||
}
|
||||
const sectionId = this.data.sectionId
|
||||
const userId = app.globalData.userInfo.id
|
||||
const price = (this.data.section && this.data.section.price != null) ? this.data.section.price : (this.data.sectionPrice || 1)
|
||||
|
||||
const balRes = await app.request({ url: `/api/miniprogram/balance?userId=${userId}`, silent: true }).catch(() => null)
|
||||
const balance = (balRes && balRes.data) ? balRes.data.balance : 0
|
||||
|
||||
wx.showModal({
|
||||
title: '代付分享',
|
||||
content: `为好友代付本章 ¥${price}\n当前余额: ¥${balance.toFixed(2)}\n${balance < price ? '余额不足,请先充值' : '确认后将从余额扣除'}`,
|
||||
confirmText: balance >= price ? '确认代付' : '去充值',
|
||||
success: async (res) => {
|
||||
if (!res.confirm) return
|
||||
if (balance < price) {
|
||||
wx.navigateTo({ url: '/pages/wallet/wallet' })
|
||||
return
|
||||
}
|
||||
wx.showLoading({ title: '处理中...' })
|
||||
try {
|
||||
const giftRes = await app.request({
|
||||
url: '/api/miniprogram/balance/gift',
|
||||
method: 'POST',
|
||||
data: { giverId: userId, sectionId }
|
||||
})
|
||||
wx.hideLoading()
|
||||
if (giftRes && giftRes.data && giftRes.data.giftCode) {
|
||||
const giftCode = giftRes.data.giftCode
|
||||
wx.showModal({
|
||||
title: '代付成功!',
|
||||
content: `已为好友代付 ¥${price},分享链接后好友可免费阅读`,
|
||||
confirmText: '分享给好友',
|
||||
success: (r) => {
|
||||
if (r.confirm) {
|
||||
this._giftCodeToShare = giftCode
|
||||
wx.shareAppMessage()
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
wx.showToast({ title: (giftRes && giftRes.error) || '代付失败', icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '代付失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 领取礼物码解锁
|
||||
async _redeemGiftCode(giftCode) {
|
||||
if (!app.globalData.isLoggedIn || !app.globalData.userInfo) return
|
||||
try {
|
||||
const res = await app.request({
|
||||
url: '/api/miniprogram/balance/gift/redeem',
|
||||
method: 'POST',
|
||||
data: { giftCode, receiverId: app.globalData.userInfo.id }
|
||||
})
|
||||
if (res && res.data) {
|
||||
wx.showToast({ title: '好友已为你解锁!', icon: 'success' })
|
||||
this.onLoad({ id: this.data.sectionId })
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[Gift] 领取失败:', e)
|
||||
}
|
||||
},
|
||||
|
||||
// 保存海报到相册
|
||||
savePoster() {
|
||||
wx.canvasToTempFilePath({
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<!-- 阅读内容 -->
|
||||
<view class="read-content">
|
||||
<!-- 章节标题 -->
|
||||
<view class="chapter-header">
|
||||
<view class="chapter-header" wx:if="{{section}}">
|
||||
<view class="chapter-meta">
|
||||
<text class="chapter-id">{{section.id}}</text>
|
||||
<text class="tag tag-free" wx:if="{{section.isFree}}">免费</text>
|
||||
@@ -85,15 +85,22 @@
|
||||
<!-- 分享操作区 -->
|
||||
<view class="action-section">
|
||||
<view class="action-row-inline">
|
||||
<button class="action-btn-inline btn-share-inline" open-type="shareTimeline">
|
||||
<button class="action-btn-inline btn-share-inline" open-type="share">
|
||||
<text class="action-icon-small">📣</text>
|
||||
<text class="action-text-small">分享到朋友圈</text>
|
||||
<text class="action-text-small">分享给好友</text>
|
||||
</button>
|
||||
<view class="action-btn-inline btn-gift-inline" bindtap="handleGiftPay">
|
||||
<text class="action-icon-small">🎁</text>
|
||||
<text class="action-text-small">代付分享</text>
|
||||
</view>
|
||||
<view class="action-btn-inline btn-poster-inline" bindtap="generatePoster">
|
||||
<text class="action-icon-small">🖼️</text>
|
||||
<text class="action-text-small">生成海报</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="share-tip-inline">
|
||||
<text class="share-tip-text">分享后好友购买,你可获得 90% 收益</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
@@ -242,6 +249,14 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分享提示浮层(阅读20%后下拉触发) -->
|
||||
<view class="share-float-tip {{showShareTip ? 'show' : ''}}" wx:if="{{showShareTip}}">
|
||||
<text class="share-float-icon">💰</text>
|
||||
<text class="share-float-text">分享给好友,好友购买你可获得 90% 收益</text>
|
||||
<button class="share-float-btn" open-type="share">立即分享</button>
|
||||
<view class="share-float-close" bindtap="closeShareTip">✕</view>
|
||||
</view>
|
||||
|
||||
<!-- 海报生成弹窗 -->
|
||||
<view class="modal-overlay" wx:if="{{showPosterModal}}" bindtap="closePosterModal">
|
||||
<view class="modal-content poster-modal" catchtap="stopPropagation">
|
||||
|
||||
@@ -1003,3 +1003,77 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ===== 分享提示文字(底部导航上方) ===== */
|
||||
.share-tip-inline {
|
||||
text-align: center;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
.share-tip-text {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* ===== 分享浮层提示(阅读20%触发) ===== */
|
||||
.share-float-tip {
|
||||
position: fixed;
|
||||
top: 180rpx;
|
||||
left: 40rpx;
|
||||
right: 40rpx;
|
||||
background: linear-gradient(135deg, #1a3a4a 0%, #0d2533 100%);
|
||||
border: 1rpx solid rgba(0, 206, 209, 0.3);
|
||||
border-radius: 24rpx;
|
||||
padding: 28rpx 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
z-index: 10000;
|
||||
box-shadow: 0 12rpx 48rpx rgba(0, 0, 0, 0.6);
|
||||
opacity: 0;
|
||||
transform: translateY(-40rpx);
|
||||
transition: opacity 0.35s ease, transform 0.35s ease;
|
||||
}
|
||||
.share-float-tip.show {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
.share-float-icon {
|
||||
font-size: 40rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.share-float-text {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
flex: 1;
|
||||
}
|
||||
.share-float-btn {
|
||||
background: linear-gradient(135deg, #00CED1, #20B2AA) !important;
|
||||
color: #fff !important;
|
||||
font-size: 24rpx;
|
||||
padding: 10rpx 28rpx;
|
||||
border-radius: 32rpx;
|
||||
border: none;
|
||||
flex-shrink: 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.share-float-btn::after {
|
||||
border: none;
|
||||
}
|
||||
.share-float-close {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
padding: 8rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ===== 代付分享按钮 ===== */
|
||||
.btn-gift-inline {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 16rpx;
|
||||
background: rgba(255, 165, 0, 0.1);
|
||||
border: 1rpx solid rgba(255, 165, 0, 0.3);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
"name": "唤醒",
|
||||
"pathName": "pages/read/read",
|
||||
"query": "mid=209",
|
||||
"scene": null,
|
||||
"launchMode": "default"
|
||||
"launchMode": "default",
|
||||
"scene": null
|
||||
},
|
||||
{
|
||||
"name": "pages/my/my",
|
||||
|
||||
@@ -144,9 +144,29 @@ function parseHtmlToSegments(html) {
|
||||
return { lines, segments }
|
||||
}
|
||||
|
||||
/** 纯文本按行解析(无 HTML 标签) */
|
||||
/** 清理 Markdown 格式标记(**加粗** *斜体* __加粗__ _斜体_ ~~删除线~~ `代码` 等)*/
|
||||
function stripMarkdownFormatting(text) {
|
||||
if (!text) return text
|
||||
let s = text
|
||||
s = s.replace(/^#{1,6}\s+/gm, '')
|
||||
s = s.replace(/\*\*(.+?)\*\*/g, '$1')
|
||||
s = s.replace(/__(.+?)__/g, '$1')
|
||||
s = s.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, '$1')
|
||||
s = s.replace(/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, '$1')
|
||||
s = s.replace(/~~(.+?)~~/g, '$1')
|
||||
s = s.replace(/`([^`]+)`/g, '$1')
|
||||
s = s.replace(/^>\s+/gm, '')
|
||||
s = s.replace(/^---$/gm, '')
|
||||
s = s.replace(/^\* /gm, '• ')
|
||||
s = s.replace(/^- /gm, '• ')
|
||||
s = s.replace(/^\d+\.\s/gm, '')
|
||||
return s
|
||||
}
|
||||
|
||||
/** 纯文本/Markdown 按行解析 */
|
||||
function parsePlainTextToSegments(text) {
|
||||
const lines = text.split('\n').map(l => l.trim()).filter(l => l.length > 0)
|
||||
const cleaned = stripMarkdownFormatting(text)
|
||||
const lines = cleaned.split('\n').map(l => l.trim()).filter(l => l.length > 0)
|
||||
const segments = lines.map(line => [{ type: 'text', text: line }])
|
||||
return { lines, segments }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user