From c040e5609b7ae0b365e9231040007735632fb6f1 Mon Sep 17 00:00:00 2001
From: Alex-larget <33240357+Alex-larget@users.noreply.github.com>
Date: Wed, 25 Mar 2026 15:47:31 +0800
Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=A1=B9=E7=9B=AE=E7=B4=A2?=
=?UTF-8?q?=E5=BC=95=EF=BC=8C=E6=96=B0=E5=A2=9E2026-03-24=E7=9A=84?=
=?UTF-8?q?=E5=BC=80=E5=8F=91=E8=BF=9B=E5=BA=A6=E5=90=8C=E6=AD=A5=E4=BC=9A?=
=?UTF-8?q?=E8=AE=AE=E8=AE=B0=E5=BD=95=EF=BC=8C=E8=A1=A5=E5=85=85=E5=90=84?=
=?UTF-8?q?=E8=A7=92=E8=89=B2=E7=9A=84=E9=A1=B9=E7=9B=AE=E7=B4=A2=E5=BC=95?=
=?UTF-8?q?=E4=BF=A1=E6=81=AF=EF=BC=8C=E7=A1=AE=E4=BF=9D=E6=96=87=E6=A1=A3?=
=?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=8E=9F=E5=88=99=E5=BE=97=E5=88=B0=E7=A1=AE?=
=?UTF-8?q?=E8=AE=A4=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
miniprogram/app.js | 2 +-
miniprogram/pages/index/index.js | 262 +----
miniprogram/pages/index/index.wxml | 31 +-
miniprogram/pages/index/index.wxss | 156 ---
miniprogram/utils/soulBridge.js | 15 +-
soul-admin/dist/assets/index-D2vksFq_.css | 1 -
soul-admin/dist/assets/index-IPC9MvjH.css | 1 +
soul-admin/dist/assets/index-Z-J7LogK.js | 1010 +++++++++++++++++
soul-admin/dist/assets/index-l3L5iIYa.js | 1010 -----------------
soul-admin/dist/index.html | 4 +-
.../modules/user/MemberUserSelect.tsx | 473 ++++++++
soul-admin/src/pages/content/ContentPage.tsx | 53 +-
.../src/pages/content/PersonAddEditModal.tsx | 238 +++-
.../pages/distribution/DistributionPage.tsx | 587 +++++++++-
soul-admin/src/pages/users/UsersPage.tsx | 194 +++-
soul-admin/tsconfig.tsbuildinfo | 2 +-
soul-api/internal/handler/autolink.go | 62 +-
soul-api/internal/handler/ckb.go | 68 +-
soul-api/internal/handler/db_ckb_leads.go | 120 +-
soul-api/internal/handler/db_person.go | 156 ++-
soul-api/internal/router/router.go | 3 +
21 files changed, 2939 insertions(+), 1509 deletions(-)
delete mode 100644 soul-admin/dist/assets/index-D2vksFq_.css
create mode 100644 soul-admin/dist/assets/index-IPC9MvjH.css
create mode 100644 soul-admin/dist/assets/index-Z-J7LogK.js
delete mode 100644 soul-admin/dist/assets/index-l3L5iIYa.js
create mode 100644 soul-admin/src/components/modules/user/MemberUserSelect.tsx
diff --git a/miniprogram/app.js b/miniprogram/app.js
index 6fcdd7dc..aa854e05 100644
--- a/miniprogram/app.js
+++ b/miniprogram/app.js
@@ -118,7 +118,7 @@ App({
const pages = getCurrentPages()
const cur = pages[pages.length - 1]
const route = (cur && cur.route) || ''
- const needPrivacyPages = ['avatar-nickname', 'profile-edit', 'read', 'my', 'gift-pay/detail', 'index', 'settings']
+ const needPrivacyPages = ['avatar-nickname', 'profile-edit', 'read', 'my', 'gift-pay/detail', 'settings']
const needShow = needPrivacyPages.some(p => route.includes(p))
if (cur && typeof cur.setData === 'function' && needShow) {
cur.setData({ showPrivacyModal: true })
diff --git a/miniprogram/pages/index/index.js b/miniprogram/pages/index/index.js
index 1dc4723e..02d8d3dd 100644
--- a/miniprogram/pages/index/index.js
+++ b/miniprogram/pages/index/index.js
@@ -9,9 +9,10 @@ const { trackClick } = require('../../utils/trackClick')
const { cleanSingleLineField } = require('../../utils/contentParser')
const { navigateMpPath } = require('../../utils/mpNavigate.js')
const { isSafeImageSrc } = require('../../utils/imageUrl.js')
+const { submitCkbLead } = require('../../utils/soulBridge')
+/** 置顶人物无头像时的占位图 */
const DEFAULT_KARUO_LINK_AVATAR = '/assets/images/karuo-link-avatar.png'
-const KARUO_USER_ID = 'ogpTW5Wbbo9DfSyB3-xCWN6EGc-g'
/** 与首页固定「卡若」获客位重复时从横滑列表剔除(含历史误写「卡路」) */
function isKaruoHostDuplicateName(displayName) {
@@ -72,11 +73,6 @@ Page({
// 加载状态
loading: true,
- // 链接卡若 - 留资弹窗
- showLeadModal: false,
- leadPhone: '',
- showPrivacyModal: false,
-
// 展开状态(首页精选/最新)
featuredExpanded: false,
latestExpanded: false,
@@ -91,15 +87,18 @@ Page({
// mp_config.mpUi.homePage(后台系统设置 mpUi)
mpUiLogoTitle: '卡若创业派对',
mpUiLogoSubtitle: '来自派对房的真实故事',
- mpUiLinkKaruoText: '点击链接卡若',
- /** 最终展示:后台 linkKaruoAvatar 或本包默认卡若照片 */
+ /** 仅当有置顶 @人物时展示,文案与头像由 _applyHomeMpUi 写入 */
+ mpUiLinkKaruoText: '',
mpUiLinkKaruoDisplay: DEFAULT_KARUO_LINK_AVATAR,
mpUiSearchPlaceholder: '搜索章节标题或内容...',
mpUiBannerTag: '推荐',
mpUiBannerReadMore: '点击阅读',
mpUiSuperTitle: '超级个体',
mpUiPickTitle: '精选推荐',
- mpUiLatestTitle: '最新新增'
+ mpUiLatestTitle: '最新新增',
+
+ /** 后台 @列表置顶人物:有则右上角展示绑定用户头像 + @名称,点击走 ckb/lead */
+ homePinnedPerson: null,
},
onLoad(options) {
@@ -125,7 +124,7 @@ Page({
onShow() {
console.log('[Index] onShow 触发')
this.setData({ auditMode: app.globalData.auditMode || false })
- this._applyHomeMpUi()
+ void this.loadHomePinnedPerson()
// 设置TabBar选中状态
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
@@ -322,31 +321,52 @@ Page({
_applyHomeMpUi() {
const h = app.globalData.configCache?.mpConfig?.mpUi?.homePage || {}
- let linkKaruoAvatar = String(h.linkKaruoAvatar || h.linkKaruoImage || '').trim()
- if (linkKaruoAvatar && !isSafeImageSrc(linkKaruoAvatar)) linkKaruoAvatar = ''
- this.setData({
+ const patch = {
mpUiLogoTitle: String(h.logoTitle || '卡若创业派对').trim() || '卡若创业派对',
mpUiLogoSubtitle: String(h.logoSubtitle || '来自派对房的真实故事').trim() || '来自派对房的真实故事',
- mpUiLinkKaruoText: String(h.linkKaruoText || '点击链接卡若').trim() || '点击链接卡若',
- mpUiLinkKaruoDisplay: linkKaruoAvatar || DEFAULT_KARUO_LINK_AVATAR,
mpUiSearchPlaceholder: String(h.searchPlaceholder || '搜索章节标题或内容...').trim() || '搜索章节标题或内容...',
mpUiBannerTag: String(h.bannerTag || '推荐').trim() || '推荐',
mpUiBannerReadMore: String(h.bannerReadMoreText || '点击阅读').trim() || '点击阅读',
mpUiSuperTitle: String(h.superSectionTitle || '超级个体').trim() || '超级个体',
mpUiPickTitle: String(h.pickSectionTitle || '精选推荐').trim() || '精选推荐',
- mpUiLatestTitle: String(h.latestSectionTitle || '最新新增').trim() || '最新新增'
- })
- if (!linkKaruoAvatar) this._loadKaruoAvatarLazy()
+ mpUiLatestTitle: String(h.latestSectionTitle || '最新新增').trim() || '最新新增',
+ }
+ const pinned = this.data.homePinnedPerson
+ if (pinned && pinned.token) {
+ const displayAv =
+ pinned.avatar && isSafeImageSrc(pinned.avatar) ? pinned.avatar : DEFAULT_KARUO_LINK_AVATAR
+ patch.mpUiLinkKaruoText = `点击链接${pinned.name || '好友'}`
+ patch.mpUiLinkKaruoDisplay = displayAv
+ } else {
+ patch.mpUiLinkKaruoText = ''
+ patch.mpUiLinkKaruoDisplay = DEFAULT_KARUO_LINK_AVATAR
+ }
+ this.setData(patch)
},
- _loadKaruoAvatarLazy() {
- app.request({ url: `/api/miniprogram/user/profile?userId=${KARUO_USER_ID}`, silent: true, timeout: 3000 })
- .then(res => {
- if (res?.success && res.data?.avatar && isSafeImageSrc(res.data.avatar)) {
- this.setData({ mpUiLinkKaruoDisplay: res.data.avatar })
- }
- })
- .catch(() => {})
+ /** 拉取后台置顶 @人物,合并到首页右上角「链接」区 */
+ async loadHomePinnedPerson() {
+ try {
+ const res = await app.request({ url: '/api/miniprogram/ckb/pinned-person', silent: true })
+ if (res && res.success && res.data && res.data.token) {
+ const name = cleanSingleLineField(res.data.name) || '好友'
+ let av = String(res.data.avatar || '').trim()
+ if (!isSafeImageSrc(av)) av = ''
+ this.setData({
+ homePinnedPerson: {
+ token: String(res.data.token).trim(),
+ name,
+ avatar: av,
+ },
+ })
+ } else {
+ this.setData({ homePinnedPerson: null })
+ }
+ } catch (e) {
+ console.log('[Index] pinned-person:', e)
+ this.setData({ homePinnedPerson: null })
+ }
+ this._applyHomeMpUi()
},
async loadFeatureConfig() {
@@ -380,6 +400,7 @@ Page({
})
this._applyHomeMpUi()
}
+ await this.loadHomePinnedPerson()
},
// 跳转到搜索页
@@ -409,187 +430,14 @@ Page({
},
async onLinkKaruo() {
- trackClick('home', 'btn_click', '链接卡若')
- 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
- 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.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
- }
- })
- 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.message || '提交失败', icon: 'none' })
- }
- return
- }
- this.setData({ showLeadModal: true, leadPhone: '' })
- },
-
- closeLeadModal() {
- this.setData({ showLeadModal: false, leadPhone: '', showPrivacyModal: false })
- },
-
- // 阻止弹窗内部点击事件冒泡到遮罩层
- stopPropagation() {},
-
- preventMove() {},
-
- onLeadPrivacyAuthorize() {
- this.onAgreePrivacyForLead()
- },
-
- onDisagreePrivacyForLead() {
- if (app._privacyResolve) {
- try {
- app._privacyResolve({ event: 'disagree' })
- } catch (_) {}
- app._privacyResolve = null
- }
- this.setData({ showPrivacyModal: false })
- },
-
- onLeadPhoneInput(e) {
- this.setData({ leadPhone: (e.detail.value || '').trim() })
- },
-
- // 微信隐私协议同意(getPhoneNumber 需先同意)
- onAgreePrivacyForLead() {
- if (app._privacyResolve) {
- app._privacyResolve({ buttonId: 'agree-privacy-btn', event: 'agree' })
- app._privacyResolve = null
- }
- this.setData({ showPrivacyModal: false })
- },
-
- // 一键获取手机号(微信能力),成功后直接提交链接卡若
- async onGetPhoneNumberForLead(e) {
- if (e.detail.errMsg !== 'getPhoneNumber:ok') {
- wx.showToast({ title: '未获取到手机号,请手动输入', icon: 'none' })
- return
- }
- const code = e.detail.code
- if (!code) {
- wx.showToast({ title: '获取失败,请手动输入', icon: 'none' })
- return
- }
- const app = getApp()
- const userId = app.globalData.userInfo?.id
- wx.showLoading({ title: '获取中...', mask: true })
- try {
- const res = await app.request({
- url: '/api/miniprogram/phone',
- method: 'POST',
- data: { code, userId }
- })
- wx.hideLoading()
- if (res && res.success && res.phoneNumber) {
- await this._submitLeadWithPhone(res.phoneNumber)
- } else {
- wx.showToast({ title: '获取失败,请手动输入', icon: 'none' })
- }
- } catch (err) {
- wx.hideLoading()
- wx.showToast({ title: err.message || '获取失败,请手动输入', icon: 'none' })
- }
- },
-
- // 内部:用手机号提交链接卡若(一键获取与手动输入共用)
- async _submitLeadWithPhone(phone) {
- const p = (phone || '').trim().replace(/\s/g, '')
- if (!p || p.length < 11) {
- wx.showToast({ title: '请输入正确的手机号', icon: 'none' })
- return
- }
- const app = getApp()
- const userId = app.globalData.userInfo?.id
- wx.showLoading({ title: '提交中...', mask: true })
- try {
- const res = await app.request({
- url: '/api/miniprogram/ckb/index-lead',
- method: 'POST',
- data: {
- userId,
- phone: p,
- name: (app.globalData.userInfo?.nickname || '').trim() || undefined,
- },
- })
- wx.hideLoading()
- this.setData({ showLeadModal: false, leadPhone: '' })
- if (res && res.success) {
- wx.setStorageSync('lead_last_submit_ts', Date.now())
- // 同步手机号到用户资料
- try {
- if (userId) {
- await app.request({
- url: '/api/miniprogram/user/profile',
- method: 'POST',
- data: { userId, phone: p },
- })
- if (app.globalData.userInfo) {
- app.globalData.userInfo.phone = p
- wx.setStorageSync('userInfo', app.globalData.userInfo)
- }
- wx.setStorageSync('user_phone', p)
- }
- } catch (e) {
- console.log('[Index] 同步手机号到用户资料失败:', e && e.message)
- }
- wx.showToast({ title: res.message || '提交成功,卡若会尽快联系您', icon: 'success' })
- } else {
- wx.showToast({ title: (res && res.message) || '提交失败', icon: 'none' })
- }
- } catch (e) {
- wx.hideLoading()
- wx.showToast({ title: e.message || '提交失败', icon: 'none' })
- }
- },
-
- async submitLead() {
- const phone = (this.data.leadPhone || '').trim().replace(/\s/g, '')
- if (!phone) {
- wx.showToast({ title: '请输入手机号', icon: 'none' })
- return
- }
- await this._submitLeadWithPhone(phone)
+ const pinned = this.data.homePinnedPerson
+ if (!pinned || !pinned.token) return
+ trackClick('home', 'btn_click', '置顶@人物留资')
+ await submitCkbLead(getApp(), {
+ targetUserId: pinned.token,
+ targetNickname: pinned.name,
+ source: 'home_pinned_person',
+ })
},
goToSuperList() {
diff --git a/miniprogram/pages/index/index.wxml b/miniprogram/pages/index/index.wxml
index 05222397..7b594ce7 100644
--- a/miniprogram/pages/index/index.wxml
+++ b/miniprogram/pages/index/index.wxml
@@ -4,7 +4,7 @@
-
+