diff --git a/miniprogram/app.js b/miniprogram/app.js index 16e686f3..d7a15421 100644 --- a/miniprogram/app.js +++ b/miniprogram/app.js @@ -85,6 +85,8 @@ App({ lastVipContactCheck: 0, // 头像昵称检测:上次检测时间戳,onShow 节流 5 分钟 lastAvatarNicknameCheck: 0, + // 登录过期:401 后用户点「去登录」时设为 true,我的页 onShow 会检测并自动弹登录 + pendingLoginAfterExpire: false, }, @@ -875,7 +877,29 @@ App({ } if (res.statusCode === 401) { this.logout() - showError('未授权,请重新登录') + if (!silent) { + wx.showModal({ + title: '登录已过期', + content: '请重新登录后继续使用', + confirmText: '去登录', + cancelText: '稍后', + success: (modalRes) => { + if (modalRes.confirm) { + const pages = getCurrentPages() + const cur = pages[pages.length - 1] + const route = (cur && cur.route) || '' + if (route === 'pages/my/my' && typeof cur.showLogin === 'function') { + cur.showLogin() + } else { + this.globalData.pendingLoginAfterExpire = true + wx.switchTab({ url: '/pages/my/my' }) + } + } + } + }) + } else { + showError('未授权,请重新登录') + } reject(new Error('未授权')) return } diff --git a/miniprogram/components/icon/icon.js b/miniprogram/components/icon/icon.js index 095a9068..10918dac 100644 --- a/miniprogram/components/icon/icon.js +++ b/miniprogram/components/icon/icon.js @@ -135,7 +135,7 @@ Component({ 'run-in': '\ue6a7', 'pin': '\ue6a8', 'save': '\ue6a9', - 'search': '\ue6aa', + 'search': '', // 强制走 SVG 'share': '\ue6ab', 'scanning': '\ue6ac', 'security': '\ue6ad', @@ -157,19 +157,19 @@ Component({ 'chevron-right': '\ue6c6', 'chevron-down': '\ue6c4', 'chevron-up': '\ue6c2', - 'x': '\ue6c3', - 'check': '\ue6c7', + 'x': '', // 强制走 SVG + 'check': '', // 强制走 SVG 'trash-2': '\ue66a', 'pencil': '\ue685', - 'zap': '\ue75c', + 'zap': '', // 强制走 SVG,找伙伴页等统一用 Lucide 风格 'info': '\ue69c', 'map-pin': '\ue6a8', - 'message-circle': '\ue678', - 'smartphone': '\ue6a0', + 'message-circle': '', // 强制走 SVG + 'smartphone': '', // 强制走 SVG 'refresh-cw': '\ue6a4', 'shield': '\ue6ad', - 'star': '\ue689', - 'heart': '\ue68e', + 'star': '', // 强制走 SVG + 'heart': '', // 强制走 SVG 'bar-chart': '\ue672', 'clock': '\ue6b5', } @@ -224,7 +224,9 @@ Component({ 'corner-down-left': s + '', 'folder': s + '', 'bar-chart': s + '', - 'link': s + '' + 'link': s + '', + 'zap': s + '', + 'x': s + '' } return svgMap[name] || '' }, diff --git a/miniprogram/pages/index/index.js b/miniprogram/pages/index/index.js index c9d457f2..53fcba0d 100644 --- a/miniprogram/pages/index/index.js +++ b/miniprogram/pages/index/index.js @@ -73,7 +73,10 @@ Page({ searchEnabled: true, // 审核模式:隐藏支付相关入口 - auditMode: false + auditMode: false, + + // 置顶人(链接人与事,管理端设置,小程序首页展示) + pinnedPerson: null // { nickname, avatar, token } 或 null }, onLoad(options) { @@ -98,7 +101,10 @@ Page({ onShow() { console.log('[Index] onShow 触发') + const app = getApp() this.setData({ auditMode: app.globalData.auditMode || false }) + // 每次展示时刷新置顶人(管理端可能已更换置顶) + this.loadPinnedPerson() // 设置TabBar选中状态 if (typeof this.getTabBar === 'function' && this.getTabBar()) { @@ -131,6 +137,21 @@ Page({ this.loadBookData() this.loadFeaturedAndLatest() this.loadSuperMembers() + this.loadPinnedPerson() + }, + + async loadPinnedPerson() { + const app = getApp() + try { + const res = await app.request({ url: '/api/miniprogram/ckb/pinned-person', silent: true }) + if (res && res.success && res.data) { + this.setData({ pinnedPerson: res.data }) + } else { + this.setData({ pinnedPerson: null }) + } + } catch (e) { + this.setData({ pinnedPerson: null }) + } }, async loadSuperMembers() { diff --git a/miniprogram/pages/index/index.wxml b/miniprogram/pages/index/index.wxml index 8739bf3a..bf9fb1c4 100644 --- a/miniprogram/pages/index/index.wxml +++ b/miniprogram/pages/index/index.wxml @@ -18,8 +18,8 @@ - - 点击链接卡若 + + 点击链接{{pinnedPerson && pinnedPerson.nickname ? pinnedPerson.nickname : '卡若'}} diff --git a/miniprogram/pages/my/my.js b/miniprogram/pages/my/my.js index 973c1e59..cacbfb73 100644 --- a/miniprogram/pages/my/my.js +++ b/miniprogram/pages/my/my.js @@ -107,6 +107,11 @@ Page({ } } this.initUserStatus() + // 登录过期后用户点「去登录」跳转过来,自动弹出登录弹窗 + if (app.globalData.pendingLoginAfterExpire) { + app.globalData.pendingLoginAfterExpire = false + setTimeout(() => this.showLogin(), 100) + } }, async loadFeatureConfig() { diff --git a/soul-admin/src/pages/content/ContentPage.tsx b/soul-admin/src/pages/content/ContentPage.tsx index 37d124a5..c7b85206 100644 --- a/soul-admin/src/pages/content/ContentPage.tsx +++ b/soul-admin/src/pages/content/ContentPage.tsx @@ -266,6 +266,9 @@ export function ContentPage() { const [personModalOpen, setPersonModalOpen] = useState(false) const [editingPerson, setEditingPerson] = useState(null) const [personToDelete, setPersonToDelete] = useState(null) + // 链接人与事置顶(小程序首页展示) + const [pinnedPersonToken, setPinnedPersonToken] = useState('') + const [personPinLoading, setPersonPinLoading] = useState(false) // CKB 获客统计(按人物 token 聚合) const [ckbLeadCounts, setCkbLeadCounts] = useState>({}) const [ckbLeadDetailOpen, setCkbLeadDetailOpen] = useState(false) @@ -533,6 +536,37 @@ export function ContentPage() { } catch { /* ignore */ } }, []) + const loadPinnedPersonToken = useCallback(async () => { + try { + const data = await get<{ success?: boolean; token?: string }>('/api/db/persons/pinned-token') + if (data?.success && typeof data.token === 'string') { + setPinnedPersonToken(data.token) + } else { + setPinnedPersonToken('') + } + } catch { + setPinnedPersonToken('') + } + }, []) + + const handlePersonPin = useCallback(async (token: string) => { + if (personPinLoading) return + setPersonPinLoading(true) + try { + const res = await put<{ success?: boolean; error?: string; message?: string }>('/api/db/persons/pin', { token }) + if (res?.success) { + toast.success(res.message ?? '已置顶') + setPinnedPersonToken(token) + } else { + toast.error(res?.error ?? '置顶失败') + } + } catch (e) { + toast.error(e instanceof Error ? e.message : '置顶失败') + } finally { + setPersonPinLoading(false) + } + }, [personPinLoading]) + const openCkbLeadDetail = useCallback(async (token: string, name: string, page = 1) => { setCkbLeadDetailToken(token) setCkbLeadDetailName(name) @@ -667,8 +701,9 @@ export function ContentPage() { loadPersons() loadLinkTags() loadCkbLeadCounts() + loadPinnedPersonToken() loadLinkedMps() - }, [loadPinnedSections, loadPreviewPercent, loadPersons, loadLinkTags, loadCkbLeadCounts, loadLinkedMps]) + }, [loadPinnedSections, loadPreviewPercent, loadPersons, loadLinkTags, loadCkbLeadCounts, loadPinnedPersonToken, loadLinkedMps]) useEffect(() => { loadLinkTagList() @@ -2461,7 +2496,7 @@ export function ContentPage() { variant="outline" size="sm" className="border-gray-600 text-gray-400 hover:bg-gray-700/50" - onClick={() => loadPersons()} + onClick={() => { loadPersons(); loadPinnedPersonToken(); }} title="刷新" > @@ -2495,9 +2530,14 @@ export function ContentPage() { {persons.map(p => ( - + {p.id} - {p.name} + + + {p.name} + {pinnedPersonToken === p.id && } + + {(() => { const leadCount = ckbLeadCounts[p.id] || 0 return ( @@ -2532,6 +2572,16 @@ export function ContentPage() {
+