feat: 完成20260315用户管理3全部5个功能
1. 链接人和事:补充CKB_OPEN_API_KEY/ACCOUNT配置,新增fix-ckb批量创建获客计划API 2. 规则配置:打通DB规则与ruleEngine,新增/api/miniprogram/user-rules接口, ruleEngine改为从API动态加载规则并按enabled状态执行 3. 获客计划:修复获客数统计中personId/token不匹配导致永远为0的bug, 管理端新增"修复CKB密钥"按钮 4. 支付问题:修复钱包充值和代付分享中openId缺失导致400错误, 添加getOpenId()兜底逻辑 5. 朋友圈分享:shareToMoments改为复制文章前200字+省略号+手指箭头emoji Made-with: Cursor
This commit is contained in:
@@ -83,14 +83,16 @@ Page({
|
||||
async onLoad(options) {
|
||||
wx.showShareMenu({ withShareTimeline: true })
|
||||
|
||||
// 预加载 linkTags、linkedMiniprograms(供 onLinkTagTap 用密钥查 appId)
|
||||
if (!app.globalData.linkTagsConfig || !app.globalData.linkedMiniprograms) {
|
||||
app.request({ url: '/api/miniprogram/config', silent: true }).then(cfg => {
|
||||
// 预加载 linkTags、linkedMiniprograms、persons(供 onLinkTagTap / onMentionTap 和内容自动匹配用)
|
||||
if (!app.globalData.linkTagsConfig || !app.globalData.linkedMiniprograms || !app.globalData.personsConfig) {
|
||||
try {
|
||||
const cfg = await app.request({ url: '/api/miniprogram/config', silent: true })
|
||||
if (cfg) {
|
||||
if (Array.isArray(cfg.linkTags)) app.globalData.linkTagsConfig = cfg.linkTags
|
||||
if (Array.isArray(cfg.linkedMiniprograms)) app.globalData.linkedMiniprograms = cfg.linkedMiniprograms
|
||||
if (Array.isArray(cfg.persons)) app.globalData.personsConfig = cfg.persons
|
||||
}
|
||||
}).catch(() => {})
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// 支持 scene(扫码)、mid、id、ref
|
||||
@@ -270,7 +272,8 @@ Page({
|
||||
// 已解锁用 data.content(完整内容),未解锁用 content(预览);先 determineAccessState 再 loadContent 保证顺序正确
|
||||
const displayContent = accessManager.canAccessFullContent(accessState) ? (res.data?.content ?? res.content) : res.content
|
||||
if (res && displayContent) {
|
||||
const { lines, segments } = contentParser.parseContent(displayContent)
|
||||
const parserConfig = { persons: app.globalData.personsConfig || [], linkTags: app.globalData.linkTagsConfig || [] }
|
||||
const { lines, segments } = contentParser.parseContent(displayContent, parserConfig)
|
||||
// 预览内容由后端统一截取比例,这里展示全部预览内容
|
||||
const previewCount = lines.length
|
||||
const updates = {
|
||||
@@ -294,7 +297,8 @@ Page({
|
||||
try {
|
||||
const cached = wx.getStorageSync(cacheKey)
|
||||
if (cached && cached.content) {
|
||||
const { lines, segments } = contentParser.parseContent(cached.content)
|
||||
const cachedParserConfig = { persons: app.globalData.personsConfig || [], linkTags: app.globalData.linkTagsConfig || [] }
|
||||
const { lines, segments } = contentParser.parseContent(cached.content, cachedParserConfig)
|
||||
// 预览内容由后端统一截取比例,这里展示全部预览内容
|
||||
const previewCount = lines.length
|
||||
this.setData({
|
||||
@@ -551,15 +555,30 @@ Page({
|
||||
}
|
||||
const linked = (app.globalData.linkedMiniprograms || []).find(m => m.key === mpKey)
|
||||
const targetAppId = (linked && linked.appId) ? linked.appId : (appId || mpKey || '')
|
||||
const selfAppId = (app.globalData.config?.mpConfig?.appId || app.globalData.appId || 'wxb8bbb2b10dec74aa')
|
||||
const targetPath = pagePath || (linked && linked.path) || ''
|
||||
if (targetAppId === selfAppId || !targetAppId) {
|
||||
if (targetPath) {
|
||||
const navPath = targetPath.startsWith('/') ? targetPath : '/' + targetPath
|
||||
wx.navigateTo({ url: navPath, fail: () => wx.switchTab({ url: navPath }) })
|
||||
} else {
|
||||
wx.switchTab({ url: '/pages/index/index' })
|
||||
}
|
||||
return
|
||||
}
|
||||
if (targetAppId) {
|
||||
wx.navigateToMiniProgram({
|
||||
appId: targetAppId,
|
||||
path: pagePath || (linked && linked.path) || '',
|
||||
path: targetPath,
|
||||
envVersion: 'release',
|
||||
success: () => {},
|
||||
fail: (err) => {
|
||||
console.warn('[LinkTag] 小程序跳转失败:', err)
|
||||
wx.showToast({ title: '跳转失败,请检查小程序配置', icon: 'none' })
|
||||
if (targetPath) {
|
||||
wx.navigateTo({ url: targetPath.startsWith('/') ? targetPath : '/' + targetPath, fail: () => {} })
|
||||
} else {
|
||||
wx.showToast({ title: '跳转失败,请检查小程序配置', icon: 'none' })
|
||||
}
|
||||
},
|
||||
})
|
||||
return
|
||||
@@ -596,9 +615,17 @@ Page({
|
||||
|
||||
// 点击正文中的 @某人:确认弹窗 → 登录/资料校验 → 调用 ckb/lead 加好友留资
|
||||
onMentionTap(e) {
|
||||
const userId = e.currentTarget.dataset.userId
|
||||
let userId = e.currentTarget.dataset.userId
|
||||
const nickname = (e.currentTarget.dataset.nickname || '').trim() || 'TA'
|
||||
if (!userId) return
|
||||
if (!userId && nickname !== 'TA') {
|
||||
const persons = app.globalData.personsConfig || []
|
||||
const match = persons.find(p => p.name === nickname || (p.aliases || '').split(',').map(a => a.trim()).includes(nickname))
|
||||
if (match) userId = match.personId || ''
|
||||
}
|
||||
if (!userId) {
|
||||
wx.showToast({ title: `暂无 @${nickname} 的信息`, icon: 'none' })
|
||||
return
|
||||
}
|
||||
wx.showModal({
|
||||
title: '添加好友',
|
||||
content: `是否添加 @${nickname} ?`,
|
||||
@@ -629,19 +656,21 @@ Page({
|
||||
const myUserId = app.globalData.userInfo.id
|
||||
let phone = (app.globalData.userInfo.phone || '').trim()
|
||||
let wechatId = (app.globalData.userInfo.wechatId || app.globalData.userInfo.wechat_id || '').trim()
|
||||
let avatar = (app.globalData.userInfo.avatar || app.globalData.userInfo.avatarUrl || '').trim()
|
||||
if (!phone && !wechatId) {
|
||||
try {
|
||||
const profileRes = await app.request({ url: `/api/miniprogram/user/profile?userId=${myUserId}`, silent: true })
|
||||
if (profileRes?.success && profileRes.data) {
|
||||
phone = (profileRes.data.phone || '').trim()
|
||||
wechatId = (profileRes.data.wechatId || profileRes.data.wechat_id || '').trim()
|
||||
if (!avatar) avatar = (profileRes.data.avatar || '').trim()
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
if (!phone && !wechatId) {
|
||||
if ((!phone && !wechatId) || !avatar) {
|
||||
wx.showModal({
|
||||
title: '完善资料',
|
||||
content: '请先填写手机号或微信号,以便对方联系您',
|
||||
content: !avatar ? '请先设置头像和填写联系方式,以便对方联系您' : '请先填写手机号或微信号,以便对方联系您',
|
||||
confirmText: '去填写',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
@@ -706,19 +735,21 @@ Page({
|
||||
}
|
||||
let phone = (app.globalData.userInfo.phone || '').trim()
|
||||
let wechatId = (app.globalData.userInfo.wechatId || app.globalData.userInfo.wechat_id || '').trim()
|
||||
let avatar = (app.globalData.userInfo.avatar || app.globalData.userInfo.avatarUrl || '').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()
|
||||
if (!avatar) avatar = (profileRes.data.avatar || '').trim()
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
if (!phone && !wechatId) {
|
||||
if ((!phone && !wechatId) || !avatar) {
|
||||
wx.showModal({
|
||||
title: '完善资料',
|
||||
content: '请先填写手机号或微信号,以便对方联系您',
|
||||
content: !avatar ? '请先设置头像和填写联系方式,以便对方联系您' : '请先填写手机号或微信号,以便对方联系您',
|
||||
confirmText: '去填写',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
@@ -832,11 +863,18 @@ Page({
|
||||
},
|
||||
|
||||
shareToMoments() {
|
||||
wx.showModal({
|
||||
title: '分享到朋友圈',
|
||||
content: '点击右上角「···」菜单,选择「分享到朋友圈」即可。\n\n朋友圈分享文案已自动生成。',
|
||||
showCancel: false,
|
||||
confirmText: '知道了',
|
||||
const title = this.data.section?.title || this.data.chapterTitle || '好文推荐'
|
||||
const raw = (this.data.content || '').replace(/[#@]\S+/g, '').replace(/\s+/g, ' ').trim()
|
||||
const excerpt = raw.length > 200 ? raw.slice(0, 200) + '……' : raw.length > 100 ? raw + '……' : raw
|
||||
const copyText = `${title}\n\n${excerpt}\n\n👉 来自「Soul创业派对」`
|
||||
wx.setClipboardData({
|
||||
data: copyText,
|
||||
success: () => {
|
||||
wx.showToast({ title: '文案已复制,去朋友圈粘贴发布', icon: 'none', duration: 2500 })
|
||||
},
|
||||
fail: () => {
|
||||
wx.showToast({ title: '复制失败,请手动复制', icon: 'none' })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
@@ -1511,104 +1549,125 @@ Page({
|
||||
|
||||
wx.showModal({
|
||||
title: '代付分享',
|
||||
content: `为好友代付本章 ¥${price}\n\n支付后将生成代付链接,好友点击即可免费阅读`,
|
||||
confirmText: '微信支付',
|
||||
cancelText: '用余额',
|
||||
content: `为好友代付本章 ¥${price}\n支付后将生成代付链接,好友点击即可免费阅读`,
|
||||
confirmText: '确认代付',
|
||||
cancelText: '取消',
|
||||
success: async (res) => {
|
||||
if (!res.confirm && !res.cancel) return
|
||||
|
||||
if (res.confirm) {
|
||||
// Direct WeChat Pay
|
||||
wx.showLoading({ title: '创建订单...' })
|
||||
try {
|
||||
const payRes = await app.request({
|
||||
url: '/api/miniprogram/pay',
|
||||
method: 'POST',
|
||||
data: {
|
||||
openId: app.globalData.openId,
|
||||
productType: 'gift',
|
||||
productId: sectionId,
|
||||
amount: price,
|
||||
description: `代付解锁:${this.data.section?.title || sectionId}`,
|
||||
userId: userId,
|
||||
}
|
||||
})
|
||||
wx.hideLoading()
|
||||
if (payRes && payRes.payParams) {
|
||||
wx.requestPayment({
|
||||
...payRes.payParams,
|
||||
success: async () => {
|
||||
// After payment, create gift code via balance gift API
|
||||
// First confirm recharge to add to balance, then deduct for gift
|
||||
try {
|
||||
const giftRes = await app.request({
|
||||
url: '/api/miniprogram/balance/gift',
|
||||
method: 'POST',
|
||||
data: { giverId: userId, sectionId }
|
||||
})
|
||||
if (giftRes && giftRes.data && giftRes.data.giftCode) {
|
||||
this._giftCodeToShare = giftRes.data.giftCode
|
||||
wx.showModal({
|
||||
title: '代付成功!',
|
||||
content: `已为好友代付 ¥${price},分享后好友可免费阅读`,
|
||||
confirmText: '分享给好友',
|
||||
success: (r) => { if (r.confirm) wx.shareAppMessage() }
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
wx.showToast({ title: '生成分享链接失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => { wx.showToast({ title: '支付取消', icon: 'none' }) }
|
||||
})
|
||||
} else {
|
||||
wx.showToast({ title: '创建支付失败', icon: 'none' })
|
||||
if (!res.confirm) return
|
||||
wx.showActionSheet({
|
||||
itemList: ['微信支付', '用余额支付'],
|
||||
success: async (actionRes) => {
|
||||
if (actionRes.tapIndex === 0) {
|
||||
this._giftPayViaWechat(sectionId, userId, price)
|
||||
} else if (actionRes.tapIndex === 1) {
|
||||
this._giftPayViaBalance(sectionId, userId, price)
|
||||
}
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '支付失败', icon: 'none' })
|
||||
}
|
||||
} else {
|
||||
// Use balance (existing flow)
|
||||
const balRes = await app.request({ url: `/api/miniprogram/balance?userId=${userId}`, silent: true }).catch(() => null)
|
||||
const balance = (balRes && balRes.data) ? balRes.data.balance : 0
|
||||
|
||||
if (balance < price) {
|
||||
wx.showModal({
|
||||
title: '余额不足',
|
||||
content: `当前余额 ¥${balance.toFixed(2)},需要 ¥${price}\n请先充值`,
|
||||
confirmText: '去充值',
|
||||
success: (r) => { if (r.confirm) 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) {
|
||||
this._giftCodeToShare = giftRes.data.giftCode
|
||||
wx.showModal({
|
||||
title: '代付成功!',
|
||||
content: `已从余额扣除 ¥${price},分享后好友可免费阅读`,
|
||||
confirmText: '分享给好友',
|
||||
success: (r) => { if (r.confirm) wx.shareAppMessage() }
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '代付失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
async _giftPayViaWechat(sectionId, userId, price) {
|
||||
let openId = app.globalData.openId || wx.getStorageSync('openId')
|
||||
if (!openId) { openId = await app.getOpenId() }
|
||||
if (!openId) { wx.showToast({ title: '获取支付凭证失败,请重新登录', icon: 'none' }); return }
|
||||
wx.showLoading({ title: '创建订单...' })
|
||||
try {
|
||||
const payRes = await app.request({
|
||||
url: '/api/miniprogram/pay',
|
||||
method: 'POST',
|
||||
data: {
|
||||
openId: openId,
|
||||
productType: 'gift',
|
||||
productId: sectionId,
|
||||
amount: price,
|
||||
description: `代付解锁:${this.data.section?.title || sectionId}`,
|
||||
userId: userId,
|
||||
}
|
||||
})
|
||||
wx.hideLoading()
|
||||
const params = (payRes && payRes.data && payRes.data.payParams) ? payRes.data.payParams : (payRes && payRes.payParams ? payRes.payParams : null)
|
||||
if (params) {
|
||||
wx.requestPayment({
|
||||
...params,
|
||||
success: async () => {
|
||||
wx.showLoading({ title: '生成分享链接...' })
|
||||
try {
|
||||
const giftRes = await app.request({
|
||||
url: '/api/miniprogram/balance/gift',
|
||||
method: 'POST',
|
||||
data: { giverId: userId, sectionId, paidViaWechat: true }
|
||||
})
|
||||
wx.hideLoading()
|
||||
if (giftRes && giftRes.data && giftRes.data.giftCode) {
|
||||
this._giftCodeToShare = giftRes.data.giftCode
|
||||
wx.showModal({
|
||||
title: '代付成功',
|
||||
content: `已为好友代付 ¥${price},分享后好友可免费阅读`,
|
||||
confirmText: '分享给好友',
|
||||
cancelText: '稍后分享',
|
||||
success: (r) => { if (r.confirm) wx.shareAppMessage() }
|
||||
})
|
||||
} else {
|
||||
wx.showToast({ title: '支付成功,请手动分享', icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '支付成功,生成链接失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => { wx.showToast({ title: '支付已取消', icon: 'none' }) }
|
||||
})
|
||||
} else {
|
||||
wx.showToast({ title: '创建支付失败', icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
console.error('[GiftPay] WeChat pay error:', e)
|
||||
wx.showToast({ title: '支付失败,请重试', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
async _giftPayViaBalance(sectionId, userId, price) {
|
||||
const balRes = await app.request({ url: `/api/miniprogram/balance?userId=${userId}`, silent: true }).catch(() => null)
|
||||
const balance = (balRes && balRes.data) ? balRes.data.balance : 0
|
||||
|
||||
if (balance < price) {
|
||||
wx.showModal({
|
||||
title: '余额不足',
|
||||
content: `当前余额 ¥${balance.toFixed(2)},需要 ¥${price}\n请先充值`,
|
||||
confirmText: '去充值',
|
||||
cancelText: '取消',
|
||||
success: (r) => { if (r.confirm) 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) {
|
||||
this._giftCodeToShare = giftRes.data.giftCode
|
||||
wx.showModal({
|
||||
title: '代付成功',
|
||||
content: `已从余额扣除 ¥${price},分享后好友可免费阅读`,
|
||||
confirmText: '分享给好友',
|
||||
cancelText: '稍后分享',
|
||||
success: (r) => { if (r.confirm) wx.shareAppMessage() }
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '代付失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
// 领取礼物码解锁
|
||||
async _redeemGiftCode(giftCode) {
|
||||
if (!app.globalData.isLoggedIn || !app.globalData.userInfo) return
|
||||
|
||||
@@ -348,17 +348,22 @@
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
margin-bottom: 48rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 24rpx;
|
||||
border-radius: 24rpx;
|
||||
max-width: 48%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nav-btn-placeholder {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
max-width: 48%;
|
||||
}
|
||||
|
||||
@@ -433,6 +438,7 @@
|
||||
.action-btn-inline {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8rpx;
|
||||
@@ -466,11 +472,11 @@
|
||||
}
|
||||
|
||||
.action-icon-small {
|
||||
font-size: 28rpx;
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.action-text-small {
|
||||
font-size: 24rpx;
|
||||
font-size: 22rpx;
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user