feat: MBTI头像与用户规则链路升级,三端页面与接口同步
Made-with: Cursor
This commit is contained in:
13
.cursor/agent/开发助理/evolution/2026-03-21-MBTI头像C端全链路兜底.md
Normal file
13
.cursor/agent/开发助理/evolution/2026-03-21-MBTI头像C端全链路兜底.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# 2026-03-21 MBTI 头像 C 端全链路兜底
|
||||
|
||||
## 问题
|
||||
系统设置瘦身与 MBTI 映射迁到用户管理后,需在小程序多页面与匹配接口统一「无微信头像 → MBTI 映射」行为,避免仅海报单点生效。
|
||||
|
||||
## 做法
|
||||
- 新增 `miniprogram/utils/mbtiAvatar.js`(`resolveAvatarWithMbti`);`app.resolveAvatarWithMbti` 封装全局 map。
|
||||
- 我的页 `profileAvatarDisplay`;资料编辑 `avatarPreviewUrl`;profile-show、member-detail、referral 海报复用同一逻辑。
|
||||
- 后端 `match.go`:`avatar` 为空时用 `getMbtiAvatar`;响应增加 `mbti` 字段;找伙伴卡片 wxml 增加无图占位。
|
||||
- 管理端 `MbtiAvatarsManager` 补充 downloadFile 域名说明。
|
||||
|
||||
## 可复用规则
|
||||
配置驱动展示:公开 `GET /api/miniprogram/config/mbti-avatars` + 本地短时缓存;业务侧只调 `resolveAvatarWithMbti`,避免重复拼接 baseUrl。
|
||||
@@ -4,4 +4,5 @@
|
||||
|
||||
| 日期 | 摘要 | 文件 |
|
||||
|------|------|------|
|
||||
| 2026-03-21 | MBTI 头像小程序全链路兜底 + 匹配接口回填 | 2026-03-21-MBTI头像C端全链路兜底.md |
|
||||
| 2026-03-16 | 用户交互习惯分析(基于 agent-transcripts) | 2026-03-16-交互习惯分析.md |
|
||||
|
||||
@@ -7,8 +7,8 @@ description: >
|
||||
triggers: 运营报表、视频切片、多平台分发、飞书视频下载、派对运营、卡若创业派对、派对填表、视频剪辑、一键分发、妙记下载
|
||||
owner: 水岸
|
||||
group: 运营
|
||||
version: "1.1"
|
||||
updated: "2026-03-21"
|
||||
version: "1.2"
|
||||
updated: "2026-03-23"
|
||||
---
|
||||
|
||||
# 卡若创业派对运营 Skill 包
|
||||
@@ -123,6 +123,14 @@ python3 "$DIST_SCRIPT/distribute_all.py" --now
|
||||
|
||||
**详细流程**:见 `skills/多平台分发_SKILL.md`
|
||||
|
||||
#### 视频号发布前置(强制)
|
||||
|
||||
在执行视频号发布前,固定做以下 3 步:
|
||||
|
||||
1. **账号信息校验**:调用 `auth_data` 校验 `nickname` 与 `headImgUrl`,不一致先改到目标值再发。
|
||||
2. **线上失败/重复清理**:先查 `post_list`,删除失败条目;同标题仅保留最新一条(去重后再补发)。
|
||||
3. **仅定时发布**:禁止立即发布;若页面定时控件失效,使用 `post_create` 注入定时参数并拦截立即发布。
|
||||
|
||||
---
|
||||
|
||||
## 四、完整流程(派对结束后)
|
||||
@@ -279,5 +287,6 @@ curl -sS -X POST -H "Content-Type: application/json" -d "$TEXT" "$FEISHU_PARTY_C
|
||||
|
||||
| 版本 | 日期 | 说明 |
|
||||
|:---|:---|:---|
|
||||
| 1.2 | 2026-03-23 | 新增视频号发布前置三步:头像昵称校验、失败/重复清理、强制定时发布(含请求注入兜底) |
|
||||
| 1.1 | 2026-03-21 | 新增 §九 闭环复盘发群:卡若五块复盘 + 飞书 Webhook v2(msg_type 必填) |
|
||||
| 1.0 | 2026-03-20 | 初版:整合运营报表、视频切片、多平台分发、飞书视频文字下载 4 大技能,统一凭证管理 |
|
||||
|
||||
@@ -7,14 +7,14 @@ description: >
|
||||
triggers: 多平台分发、一键分发、全平台发布、批量分发、视频分发
|
||||
owner: 木叶
|
||||
group: 木
|
||||
version: "4.3"
|
||||
version: "4.4"
|
||||
updated: "2026-03-23"
|
||||
---
|
||||
|
||||
# 多平台分发 Skill(v4.3)
|
||||
# 多平台分发 Skill(v4.4)
|
||||
|
||||
> **核心原则**:API 发布为主,Playwright 为辅。确保确定性地分发到各平台。
|
||||
> **v4.3**:默认**静默**(不自动 `channels_login`);需弹窗时 `--auto-channels-login` 或 `CHANNELS_AUTO_LOGIN=1`(独立脚本)。**v4.2**:智能排期与去重下标对齐。
|
||||
> **v4.4**:视频号新增发前强制检查(头像昵称校验、失败清理、同标题去重)与“仅定时发布(请求注入兜底)”。**v4.3**:默认静默登录。
|
||||
|
||||
## 〇、执行原则(第一性原理)
|
||||
|
||||
@@ -89,7 +89,7 @@ python3 distribute_all.py --platforms 视频号 --auto-channels-login --video-di
|
||||
| 平台 | 定时方式 | 参数 |
|
||||
|------|----------|------|
|
||||
| B站 | API `meta.dtime` | Unix 时间戳(秒) |
|
||||
| 视频号 | API `postTimingInfo.postTime`(秒级 Unix);首条若时间过近则立即发 | `channels_api_publish._scheduled_ts_for_channels` |
|
||||
| 视频号 | API `postTimingInfo.postTime`(秒级 Unix);过近时间自动顺延,不允许立即发 | `channels_api_publish._scheduled_ts_for_channels` |
|
||||
| 抖音 | API `timing_ts` | Unix 时间戳 |
|
||||
| 快手 | Playwright UI | `schedule_helper.py` |
|
||||
| 小红书 | Playwright UI | `schedule_helper.py` |
|
||||
@@ -148,6 +148,17 @@ meta.hashtags("视频号") # … + #小程序卡若创业派对 #公众号卡
|
||||
|
||||
---
|
||||
|
||||
## 六点五、视频号发布前置检查(强制)
|
||||
|
||||
每次发布视频号前,必须先跑:
|
||||
|
||||
1. `auth/auth_data`:校验 `nickname` 与 `headImgUrl`(不一致先改号资料,再执行发布)。
|
||||
2. `post/post_list`:筛查失败条目并删除。
|
||||
3. 同标题去重:若存在多条,仅保留最新 `objectId`,其余调用 `post/post_delete` 删除。
|
||||
4. 发布阶段若页面定时控件失败,改为 `post_create` 请求注入 `postTimingInfo`,继续定时发布;注入也失败则中止该条(防止误发立即)。
|
||||
|
||||
---
|
||||
|
||||
## 七、去重机制
|
||||
|
||||
- 日志:`publish_log.json`(JSON Lines)
|
||||
|
||||
@@ -12,7 +12,7 @@ const DEFAULT_WITHDRAW_TMPL_ID = 'u3MbZGPRkrZIk-I7QdpwzFxnO_CeQPaCWF2FkiIablE'
|
||||
const PRODUCTION_BASE_URL = 'https://soulapi.quwanzhi.com'
|
||||
const CONFIG_CACHE_KEY = 'mpConfigCacheV1'
|
||||
// 与上传版本号对齐;设置页展示优先用 wx.getAccountInfoSync().miniProgram.version(正式版),否则用本字段
|
||||
const APP_DISPLAY_VERSION = '1.7.1'
|
||||
const APP_DISPLAY_VERSION = '1.7.2'
|
||||
|
||||
App({
|
||||
globalData: {
|
||||
@@ -98,6 +98,9 @@ App({
|
||||
lastVipContactCheck: 0,
|
||||
// 头像昵称检测:上次检测时间戳(与 VIP 检测同周期刷新)
|
||||
lastAvatarNicknameCheck: 0,
|
||||
/** MBTI → 默认头像 URL(/api/miniprogram/config/mbti-avatars),供推广海报等 */
|
||||
mbtiAvatarsMap: {},
|
||||
mbtiAvatarsExpires: 0,
|
||||
},
|
||||
|
||||
|
||||
@@ -824,27 +827,58 @@ App({
|
||||
async loadMpConfig() {
|
||||
try {
|
||||
const res = await this.getConfig()
|
||||
if (!res) return
|
||||
const mp = (res && res.mpConfig) || (res && res.configs && res.configs.mp_config)
|
||||
if (mp && typeof mp === 'object') {
|
||||
if (mp.appId) this.globalData.appId = mp.appId
|
||||
if (mp.mchId) this.globalData.mchId = mp.mchId
|
||||
if (mp.withdrawSubscribeTmplId) this.globalData.withdrawSubscribeTmplId = mp.withdrawSubscribeTmplId
|
||||
this.globalData.auditMode = !!mp.auditMode
|
||||
this.globalData.supportWechat = mp.supportWechat || mp.customerWechat || mp.serviceWechat || ''
|
||||
// 通知当前已加载的页面刷新 auditMode(从后台切回时配置更新后立即生效)
|
||||
try {
|
||||
const pages = getCurrentPages()
|
||||
pages.forEach(p => {
|
||||
if (p && p.data && 'auditMode' in p.data) {
|
||||
p.setData({ auditMode: this.globalData.auditMode || false })
|
||||
}
|
||||
})
|
||||
} catch (_) {}
|
||||
if (res) {
|
||||
const mp = (res && res.mpConfig) || (res && res.configs && res.configs.mp_config)
|
||||
if (mp && typeof mp === 'object') {
|
||||
if (mp.appId) this.globalData.appId = mp.appId
|
||||
if (mp.mchId) this.globalData.mchId = mp.mchId
|
||||
if (mp.withdrawSubscribeTmplId) this.globalData.withdrawSubscribeTmplId = mp.withdrawSubscribeTmplId
|
||||
this.globalData.auditMode = !!mp.auditMode
|
||||
this.globalData.supportWechat = mp.supportWechat || mp.customerWechat || mp.serviceWechat || ''
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[App] loadMpConfig 失败,使用默认值:', e?.message || e)
|
||||
}
|
||||
// 审核模式不走 5min 本地 config 缓存:始终以独立接口为准,避免后台已开审核端仍显示支付入口
|
||||
try {
|
||||
await this.getAuditMode()
|
||||
} catch (_) {}
|
||||
this.loadMbtiAvatarsMap()
|
||||
},
|
||||
|
||||
/** 拉取后台配置的 16 型 MBTI 默认头像(公开接口,约 5 分钟本地缓存) */
|
||||
async loadMbtiAvatarsMap() {
|
||||
try {
|
||||
const now = Date.now()
|
||||
if (this.globalData.mbtiAvatarsExpires && this.globalData.mbtiAvatarsExpires > now) return
|
||||
const res = await this.request({
|
||||
url: '/api/miniprogram/config/mbti-avatars',
|
||||
silent: true,
|
||||
timeout: 8000,
|
||||
})
|
||||
if (res && res.success && res.avatars && typeof res.avatars === 'object') {
|
||||
this.globalData.mbtiAvatarsMap = res.avatars
|
||||
this.globalData.mbtiAvatarsExpires = now + 5 * 60 * 1000
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[App] loadMbtiAvatarsMap:', e?.message || e)
|
||||
}
|
||||
},
|
||||
|
||||
/** 展示用头像:优先用户头像,否则 MBTI 映射(需已 loadMbtiAvatarsMap) */
|
||||
resolveAvatarWithMbti(avatar, mbti) {
|
||||
try {
|
||||
const { resolveAvatarWithMbti } = require('./utils/mbtiAvatar.js')
|
||||
return resolveAvatarWithMbti(
|
||||
avatar,
|
||||
mbti,
|
||||
this.globalData.mbtiAvatarsMap || {},
|
||||
this.globalData.baseUrl || ''
|
||||
)
|
||||
} catch (_) {
|
||||
return (avatar && String(avatar).trim()) || ''
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
BIN
miniprogram/assets/images/karuo-link-avatar.png
Normal file
BIN
miniprogram/assets/images/karuo-link-avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
BIN
miniprogram/assets/images/part-books/0.png
Normal file
BIN
miniprogram/assets/images/part-books/0.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
BIN
miniprogram/assets/images/part-books/1.png
Normal file
BIN
miniprogram/assets/images/part-books/1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
BIN
miniprogram/assets/images/part-books/2.png
Normal file
BIN
miniprogram/assets/images/part-books/2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
BIN
miniprogram/assets/images/part-books/3.png
Normal file
BIN
miniprogram/assets/images/part-books/3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
BIN
miniprogram/assets/images/part-books/4.png
Normal file
BIN
miniprogram/assets/images/part-books/4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
@@ -7,6 +7,8 @@
|
||||
|
||||
const app = getApp()
|
||||
const { trackClick } = require('../../utils/trackClick')
|
||||
const { partEmojiForBodyIndex } = require('../../utils/partIcons.js')
|
||||
const { isSafeImageSrc } = require('../../utils/imageUrl.js')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
@@ -116,15 +118,21 @@ Page({
|
||||
{ id: 'appendix-2', title: '附录2|创业者自检清单', mid: fixedMap['appendix-2'] },
|
||||
{ id: 'appendix-3', title: '附录3|本书提到的工具和资源', mid: fixedMap['appendix-3'] }
|
||||
]
|
||||
const bookData = parts.map((p) => ({
|
||||
id: p.id,
|
||||
icon: p.icon || '',
|
||||
title: p.title,
|
||||
subtitle: p.subtitle || '',
|
||||
chapterCount: p.chapterCount || 0,
|
||||
chapters: [],
|
||||
alwaysShow: (p.title || '').indexOf('每日派对干货') > -1
|
||||
}))
|
||||
const bookData = parts.map((p, idx) => {
|
||||
let icon = String(p.icon || '').trim()
|
||||
if (icon && !isSafeImageSrc(icon)) icon = ''
|
||||
const iconEmoji = icon ? '' : partEmojiForBodyIndex(idx)
|
||||
return {
|
||||
id: p.id,
|
||||
icon,
|
||||
iconEmoji,
|
||||
title: p.title,
|
||||
subtitle: p.subtitle || '',
|
||||
chapterCount: p.chapterCount || 0,
|
||||
chapters: [],
|
||||
alwaysShow: (p.title || '').indexOf('每日派对干货') > -1
|
||||
}
|
||||
})
|
||||
app.globalData.totalSections = totalSections
|
||||
this.setData({
|
||||
bookData,
|
||||
|
||||
@@ -72,7 +72,8 @@
|
||||
<!-- 篇章标题 -->
|
||||
<view class="part-header" bindtap="togglePart" data-id="{{item.id}}">
|
||||
<view class="part-left">
|
||||
<image wx:if="{{item.icon}}" class="part-icon-img" src="{{item.icon}}" mode="aspectFit"/>
|
||||
<image wx:if="{{item.icon}}" class="part-icon-img" src="{{item.icon}}" mode="aspectFill"/>
|
||||
<view wx:elif="{{item.iconEmoji}}" class="part-icon part-icon-emoji">{{item.iconEmoji}}</view>
|
||||
<view wx:else class="part-icon">{{item.title[0] || '篇'}}</view>
|
||||
<view class="part-info">
|
||||
<text class="part-title">{{item.title}}</text>
|
||||
|
||||
@@ -356,6 +356,15 @@
|
||||
color: #ffffff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
/* 与管理端 ChapterTree 篇头 emoji 一致 */
|
||||
.part-icon-emoji {
|
||||
font-size: 34rpx;
|
||||
font-weight: 400;
|
||||
line-height: 1;
|
||||
color: #ffffff;
|
||||
background: linear-gradient(135deg, #1e3a4a 0%, #0f172a 100%);
|
||||
border: 2rpx solid rgba(0, 206, 209, 0.35);
|
||||
}
|
||||
.part-icon-img {
|
||||
width: 64rpx; height: 64rpx; border-radius: 16rpx; flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@ const app = getApp()
|
||||
const { trackClick } = require('../../utils/trackClick')
|
||||
const { cleanSingleLineField } = require('../../utils/contentParser')
|
||||
const { navigateMpPath } = require('../../utils/mpNavigate.js')
|
||||
const { isSafeImageSrc } = require('../../utils/imageUrl.js')
|
||||
|
||||
const DEFAULT_KARUO_LINK_AVATAR = '/assets/images/karuo-link-avatar.png'
|
||||
const KARUO_USER_ID = 'ogpTW5Wbbo9DfSyB3-xCWN6EGc-g'
|
||||
|
||||
/** 与首页固定「卡若」获客位重复时从横滑列表剔除(含历史误写「卡路」) */
|
||||
function isKaruoHostDuplicateName(displayName) {
|
||||
@@ -92,11 +96,12 @@ Page({
|
||||
mpUiLogoTitle: '卡若创业派对',
|
||||
mpUiLogoSubtitle: '来自派对房的真实故事',
|
||||
mpUiLinkKaruoText: '点击链接卡若',
|
||||
/** 最终展示:后台 linkKaruoAvatar 或本包默认卡若照片 */
|
||||
mpUiLinkKaruoDisplay: DEFAULT_KARUO_LINK_AVATAR,
|
||||
mpUiSearchPlaceholder: '搜索章节标题或内容...',
|
||||
mpUiBannerTag: '推荐',
|
||||
mpUiBannerReadMore: '点击阅读',
|
||||
mpUiSuperTitle: '超级个体',
|
||||
mpUiSuperLinkText: '获客入口',
|
||||
mpUiPickTitle: '精选推荐',
|
||||
mpUiLatestTitle: '最新新增'
|
||||
},
|
||||
@@ -321,51 +326,62 @@ 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({
|
||||
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() || '超级个体',
|
||||
mpUiSuperLinkText: String(h.superSectionLinkText || '获客入口').trim() || '获客入口',
|
||||
mpUiPickTitle: String(h.pickSectionTitle || '精选推荐').trim() || '精选推荐',
|
||||
mpUiLatestTitle: String(h.latestSectionTitle || '最新新增').trim() || '最新新增'
|
||||
})
|
||||
if (!linkKaruoAvatar) this._loadKaruoAvatarLazy()
|
||||
},
|
||||
|
||||
/** 超级个体右侧文案:默认跳转找伙伴 Tab(路径可由 homePage.superSectionLinkPath 配置) */
|
||||
goSuperSectionLink() {
|
||||
const p = String(
|
||||
app.globalData.configCache?.mpConfig?.mpUi?.homePage?.superSectionLinkPath || '/pages/match/match'
|
||||
).trim()
|
||||
if (p) navigateMpPath(p)
|
||||
_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 loadFeatureConfig() {
|
||||
try {
|
||||
const hasCachedFeatures = app.globalData.features && typeof app.globalData.features.searchEnabled === 'boolean'
|
||||
if (hasCachedFeatures) {
|
||||
this.setData({
|
||||
searchEnabled: app.globalData.features.searchEnabled,
|
||||
auditMode: app.globalData.auditMode || false
|
||||
})
|
||||
this._applyHomeMpUi()
|
||||
return
|
||||
if (!hasCachedFeatures) {
|
||||
const res = await app.getConfig()
|
||||
const features = (res && res.features) || (res && res.data && res.data.features) || {}
|
||||
const searchEnabled = features.searchEnabled !== false
|
||||
if (!app.globalData.features) app.globalData.features = {}
|
||||
app.globalData.features.searchEnabled = searchEnabled
|
||||
if (typeof features.matchEnabled === 'boolean') app.globalData.features.matchEnabled = features.matchEnabled
|
||||
if (typeof features.referralEnabled === 'boolean') app.globalData.features.referralEnabled = features.referralEnabled
|
||||
const mp = (res && res.mpConfig) || {}
|
||||
app.globalData.auditMode = !!mp.auditMode
|
||||
}
|
||||
const res = await app.getConfig()
|
||||
const features = (res && res.features) || {}
|
||||
const mp = (res && res.mpConfig) || {}
|
||||
const searchEnabled = features.searchEnabled !== false
|
||||
const auditMode = !!mp.auditMode
|
||||
if (!app.globalData.features) app.globalData.features = {}
|
||||
app.globalData.features.searchEnabled = searchEnabled
|
||||
app.globalData.auditMode = auditMode
|
||||
this.setData({ searchEnabled, auditMode })
|
||||
await app.getAuditMode()
|
||||
const searchEnabled = app.globalData.features?.searchEnabled !== false
|
||||
this.setData({
|
||||
searchEnabled,
|
||||
auditMode: app.globalData.auditMode || false
|
||||
})
|
||||
this._applyHomeMpUi()
|
||||
} catch (e) {
|
||||
this.setData({ searchEnabled: true, auditMode: app.globalData.auditMode || false })
|
||||
try {
|
||||
await app.getAuditMode()
|
||||
} catch (_) {}
|
||||
this.setData({
|
||||
searchEnabled: app.globalData.features?.searchEnabled !== false,
|
||||
auditMode: app.globalData.auditMode || false
|
||||
})
|
||||
this._applyHomeMpUi()
|
||||
}
|
||||
},
|
||||
@@ -459,6 +475,22 @@ Page({
|
||||
// 阻止弹窗内部点击事件冒泡到遮罩层
|
||||
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() })
|
||||
},
|
||||
|
||||
@@ -16,7 +16,12 @@
|
||||
<text class="logo-subtitle">{{mpUiLogoSubtitle}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="header-right"></view>
|
||||
<view class="header-right" wx:if="{{!auditMode}}">
|
||||
<view class="contact-btn" catchtap="onLinkKaruo" hover-class="none">
|
||||
<image class="contact-avatar" src="{{mpUiLinkKaruoDisplay}}" mode="aspectFill"/>
|
||||
<text class="contact-name">{{mpUiLinkKaruoText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索栏(根据配置显示) -->
|
||||
@@ -52,7 +57,6 @@
|
||||
<view class="section" wx:if="{{!auditMode}}">
|
||||
<view class="section-header">
|
||||
<text class="section-title">{{mpUiSuperTitle}}</text>
|
||||
<text class="section-subtitle" bindtap="goSuperSectionLink">{{mpUiSuperLinkText}}</text>
|
||||
</view>
|
||||
<!-- 加载中:骨架动画 -->
|
||||
<view wx:if="{{superMembersLoading}}" class="super-loading">
|
||||
@@ -160,4 +164,30 @@
|
||||
<!-- 底部留白 -->
|
||||
<view class="bottom-space"></view>
|
||||
|
||||
<!-- 隐私授权(首页在 needPrivacy 列表内,需有遮罩否则无法完成 agree) -->
|
||||
<view class="privacy-mask" wx:if="{{showPrivacyModal}}" catchtouchmove="preventMove">
|
||||
<view class="privacy-modal">
|
||||
<text class="privacy-title">温馨提示</text>
|
||||
<text class="privacy-desc">使用手机号能力前,请先同意《用户隐私保护指引》</text>
|
||||
<button id="agree-privacy-btn" class="privacy-btn" open-type="agreePrivacyAuthorization" bindagreeprivacyauthorization="onLeadPrivacyAuthorize">同意</button>
|
||||
<view class="privacy-cancel" bindtap="onDisagreePrivacyForLead">拒绝</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 链接卡若 - 留资弹窗 -->
|
||||
<view class="lead-mask" wx:if="{{showLeadModal}}" catchtap="closeLeadModal">
|
||||
<view class="lead-box" catchtap="stopPropagation">
|
||||
<text class="lead-title">留下联系方式</text>
|
||||
<text class="lead-desc">方便卡若与您联系</text>
|
||||
<button class="lead-get-phone-btn" open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumberForLead">一键获取手机号</button>
|
||||
<text class="lead-divider">或手动输入</text>
|
||||
<view class="lead-input-wrap">
|
||||
<input class="lead-input" placeholder="请输入手机号" type="number" maxlength="11" value="{{leadPhone}}" bindinput="onLeadPhoneInput"/>
|
||||
</view>
|
||||
<view class="lead-actions">
|
||||
<button class="lead-btn lead-btn-cancel" bindtap="closeLeadModal">取消</button>
|
||||
<button class="lead-btn lead-btn-submit" bindtap="submitLead">提交</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -85,6 +85,10 @@
|
||||
font-size: 20rpx;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
white-space: nowrap;
|
||||
max-width: 140rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo-title {
|
||||
@@ -963,6 +967,61 @@
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
/* ===== 隐私授权(与 avatar-nickname 对齐) ===== */
|
||||
.privacy-mask {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
z-index: 2000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 48rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.privacy-modal {
|
||||
width: 100%;
|
||||
max-width: 560rpx;
|
||||
background: #17212F;
|
||||
border-radius: 24rpx;
|
||||
padding: 48rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.privacy-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
.privacy-desc {
|
||||
font-size: 28rpx;
|
||||
color: #94A3B8;
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
.privacy-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
text-align: center;
|
||||
background: #5EEAD4;
|
||||
color: #000;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 16rpx;
|
||||
border: none;
|
||||
}
|
||||
.privacy-btn::after { border: none; }
|
||||
.privacy-cancel {
|
||||
margin-top: 24rpx;
|
||||
font-size: 28rpx;
|
||||
color: #64748B;
|
||||
}
|
||||
|
||||
/* ===== 链接卡若 - 留资弹窗 ===== */
|
||||
.lead-mask {
|
||||
position: fixed;
|
||||
@@ -971,7 +1030,7 @@
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
z-index: 1000;
|
||||
z-index: 2100;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@@ -121,7 +121,8 @@
|
||||
<!-- 用户卡片 -->
|
||||
<view class="match-card">
|
||||
<view class="card-header">
|
||||
<image class="match-avatar" src="{{currentMatch.avatar}}" mode="aspectFill"></image>
|
||||
<image wx:if="{{currentMatch.avatar}}" class="match-avatar" src="{{currentMatch.avatar}}" mode="aspectFill"></image>
|
||||
<view wx:else class="match-avatar match-avatar-fallback"><text>{{currentMatch.nickname ? currentMatch.nickname[0] : '?'}}</text></view>
|
||||
<view class="match-info">
|
||||
<text class="match-name">{{currentMatch.nickname}}</text>
|
||||
<view class="match-tags">
|
||||
|
||||
@@ -445,6 +445,20 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.match-avatar-fallback {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0, 206, 209, 0.15);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.match-avatar-fallback text {
|
||||
font-size: 48rpx;
|
||||
font-weight: 600;
|
||||
color: #00CED1;
|
||||
}
|
||||
|
||||
.match-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
const app = getApp()
|
||||
const { trackClick } = require('../../utils/trackClick')
|
||||
const { isSafeImageSrc } = require('../../utils/imageUrl.js')
|
||||
const { resolveAvatarWithMbti } = require('../../utils/mbtiAvatar.js')
|
||||
|
||||
Page({
|
||||
data: { statusBarHeight: 44, navBarTotalPx: 88, member: null, loading: true, isOwnProfile: false },
|
||||
@@ -29,6 +30,9 @@ Page({
|
||||
},
|
||||
|
||||
async loadMember(id) {
|
||||
try {
|
||||
if (app.loadMbtiAvatarsMap) await app.loadMbtiAvatarsMap()
|
||||
} catch (_) {}
|
||||
const myId = app.globalData.userInfo?.id
|
||||
const isOwn = !!(myId && id != null && String(id) === String(myId))
|
||||
if (isOwn && app.globalData.isLoggedIn && myId) {
|
||||
@@ -114,10 +118,19 @@ Page({
|
||||
enrichAndFormat(raw) {
|
||||
const e = (v) => this._emptyIfPlaceholder(v)
|
||||
const rawAv = raw.avatar || raw.vipAvatar || raw.vip_avatar || ''
|
||||
let dispAv = isSafeImageSrc(rawAv) ? String(rawAv).trim() : ''
|
||||
if (!dispAv) {
|
||||
dispAv = resolveAvatarWithMbti(
|
||||
'',
|
||||
raw.mbti,
|
||||
app.globalData.mbtiAvatarsMap || {},
|
||||
app.globalData.baseUrl || ''
|
||||
)
|
||||
}
|
||||
const merged = {
|
||||
id: raw.id,
|
||||
name: raw.nickname || raw.name || raw.vipName || raw.vip_name || '创业者',
|
||||
avatar: isSafeImageSrc(rawAv) ? String(rawAv).trim() : '',
|
||||
avatar: dispAv,
|
||||
isVip: !!(raw.isVip || raw.is_vip),
|
||||
mbti: e(raw.mbti),
|
||||
region: e(raw.region),
|
||||
@@ -275,7 +288,7 @@ Page({
|
||||
const member = this.data.member
|
||||
if (!member) return
|
||||
const nickname = (member.name || 'TA').trim() || 'TA'
|
||||
trackClick('member_detail', 'btn_click', '链接头像_' + (member.id || ''))
|
||||
trackClick('member_detail', 'avatar_click', '链接头像_' + (member.id || ''))
|
||||
|
||||
if (!app.globalData.isLoggedIn || !app.globalData.userInfo) {
|
||||
wx.showModal({
|
||||
|
||||
@@ -20,6 +20,8 @@ Page({
|
||||
// 用户状态
|
||||
isLoggedIn: false,
|
||||
userInfo: null,
|
||||
/** 我的页头像展示:微信头像或 MBTI 映射图 */
|
||||
profileAvatarDisplay: '',
|
||||
|
||||
// 统计数据
|
||||
totalSections: 62,
|
||||
@@ -142,6 +144,16 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
async _refreshMyAvatarDisplay(safeUser) {
|
||||
if (!safeUser || !app.globalData.isLoggedIn) return
|
||||
try {
|
||||
if (app.loadMbtiAvatarsMap) await app.loadMbtiAvatarsMap()
|
||||
} catch (_) {}
|
||||
const url = app.resolveAvatarWithMbti ? app.resolveAvatarWithMbti(safeUser.avatar, safeUser.mbti) : ''
|
||||
if (!this.data.isLoggedIn) return
|
||||
this.setData({ profileAvatarDisplay: url || '' })
|
||||
},
|
||||
|
||||
async loadFeatureConfig() {
|
||||
try {
|
||||
const res = await app.getConfig()
|
||||
@@ -150,8 +162,9 @@ Page({
|
||||
const referralEnabled = features.referralEnabled !== false
|
||||
const searchEnabled = features.searchEnabled !== false
|
||||
const mp = (res && res.mpConfig) || {}
|
||||
const auditMode = !!mp.auditMode
|
||||
app.globalData.auditMode = auditMode
|
||||
app.globalData.auditMode = !!mp.auditMode
|
||||
await app.getAuditMode()
|
||||
const auditMode = app.globalData.auditMode || false
|
||||
app.globalData.features = { matchEnabled, referralEnabled, searchEnabled }
|
||||
this.setData({ matchEnabled, referralEnabled, searchEnabled, auditMode })
|
||||
this._applyMyMpUiLabels()
|
||||
@@ -181,10 +194,11 @@ Page({
|
||||
this.setData({
|
||||
isLoggedIn: true,
|
||||
userInfo: safeUser,
|
||||
profileAvatarDisplay: '',
|
||||
userIdShort,
|
||||
userWechat,
|
||||
readCount: 0,
|
||||
referralCount: userInfo.referralCount || 0,
|
||||
referralCount: 0,
|
||||
earnings: '-',
|
||||
pendingEarnings: '-',
|
||||
earningsLoading: true,
|
||||
@@ -200,12 +214,14 @@ Page({
|
||||
this.loadPendingConfirm()
|
||||
this.loadVipStatus()
|
||||
this.loadWalletBalance()
|
||||
this._refreshMyAvatarDisplay(safeUser)
|
||||
} else {
|
||||
const guestReadCount = app.getReadCount()
|
||||
const guestRecent = this._mergeRecentChaptersFromLocal([])
|
||||
this.setData({
|
||||
isLoggedIn: false,
|
||||
userInfo: null,
|
||||
profileAvatarDisplay: '',
|
||||
userIdShort: '',
|
||||
readCount: guestReadCount,
|
||||
readCountText: formatStatNum(guestReadCount),
|
||||
@@ -644,6 +660,7 @@ Page({
|
||||
const userInfo = this.data.userInfo
|
||||
userInfo.avatar = avatarUrl
|
||||
this.setData({ userInfo })
|
||||
this._refreshMyAvatarDisplay(userInfo)
|
||||
app.globalData.userInfo = userInfo
|
||||
wx.setStorageSync('userInfo', userInfo)
|
||||
|
||||
@@ -689,9 +706,9 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
// 点击昵称:跳转资料编辑页(type="nickname" 在弹窗内无法触发微信昵称选择器,需在主页面)
|
||||
// 点击昵称:先进个人资料名片页,再在右上角进入编辑(与需求「编辑收进名片流」一致)
|
||||
editNickname() {
|
||||
wx.navigateTo({ url: '/pages/profile-edit/profile-edit' })
|
||||
wx.navigateTo({ url: '/pages/profile-show/profile-show' })
|
||||
},
|
||||
|
||||
// 关闭昵称弹窗
|
||||
@@ -916,14 +933,17 @@ Page({
|
||||
},
|
||||
|
||||
// 跳转到推广中心(需登录)
|
||||
goToReferral() {
|
||||
trackClick('my', 'nav_click', '推广中心')
|
||||
goToReferral(e) {
|
||||
const focus = e && e.currentTarget && e.currentTarget.dataset ? (e.currentTarget.dataset.focus || '') : ''
|
||||
const action = focus === 'bindings' ? '推荐好友' : focus === 'earnings' ? '我的收益' : '推广中心'
|
||||
trackClick('my', 'nav_click', action)
|
||||
if (!this.data.isLoggedIn) {
|
||||
this.showLogin()
|
||||
return
|
||||
}
|
||||
if (!this.data.referralEnabled) return
|
||||
wx.navigateTo({ url: '/pages/referral/referral' })
|
||||
const url = focus ? `/pages/referral/referral?focus=${focus}` : '/pages/referral/referral'
|
||||
wx.navigateTo({ url })
|
||||
},
|
||||
|
||||
// 退出登录
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<view class="profile-top-row">
|
||||
<view class="avatar-wrap" bindtap="tapAvatar">
|
||||
<view class="avatar-inner {{isVip ? 'avatar-vip' : ''}}">
|
||||
<image wx:if="{{userInfo.avatar && userInfo.avatar.length > 5}}" class="avatar-img" src="{{userInfo.avatar}}" mode="aspectFill"/>
|
||||
<image wx:if="{{profileAvatarDisplay}}" class="avatar-img" src="{{profileAvatarDisplay}}" mode="aspectFill"/>
|
||||
<text wx:else class="avatar-text">{{userInfo.nickname ? userInfo.nickname[0] : '?'}}</text>
|
||||
</view>
|
||||
<view class="vip-badge" wx:if="{{isVip}}">VIP</view>
|
||||
@@ -32,7 +32,7 @@
|
||||
<view class="profile-name-row">
|
||||
<text class="user-name" bindtap="editNickname">{{userInfo.nickname || '点击设置昵称'}}</text>
|
||||
</view>
|
||||
<view class="profile-actions-row" wx:if="{{!auditMode}}">
|
||||
<view class="profile-actions-row profile-actions-under-name" wx:if="{{!auditMode}}">
|
||||
<view class="profile-action-btn" catchtap="goToMySuperCard">{{mpUiCardLabel}}</view>
|
||||
<view class="profile-action-btn" catchtap="goToVip">{{isVip ? mpUiVipLabelVip : mpUiVipLabelGuest}}</view>
|
||||
</view>
|
||||
@@ -43,7 +43,7 @@
|
||||
<text class="profile-stat-val">{{readCountText || '0'}}</text>
|
||||
<text class="profile-stat-label">{{mpUiReadStatLabel}}</text>
|
||||
</view>
|
||||
<view class="profile-stat" wx:if="{{referralEnabled}}" bindtap="goToReferral">
|
||||
<view class="profile-stat" wx:if="{{referralEnabled}}" bindtap="goToReferral" data-focus="bindings">
|
||||
<text class="profile-stat-val">{{referralCount}}</text>
|
||||
<text class="profile-stat-label">推荐好友</text>
|
||||
</view>
|
||||
@@ -51,7 +51,7 @@
|
||||
<text class="profile-stat-val">{{matchHistoryText}}</text>
|
||||
<text class="profile-stat-label">匹配伙伴</text>
|
||||
</view>
|
||||
<view class="profile-stat" wx:if="{{!auditMode}}" bindtap="goToReferral">
|
||||
<view class="profile-stat" wx:if="{{!auditMode}}" bindtap="goToReferral" data-focus="earnings">
|
||||
<text class="profile-stat-val">{{pendingEarnings || '0.00'}}</text>
|
||||
<text class="profile-stat-label">我的收益</text>
|
||||
</view>
|
||||
|
||||
@@ -79,6 +79,8 @@
|
||||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0;
|
||||
}
|
||||
.profile-actions-row { display: flex; flex-wrap: wrap; align-items: center; gap: 12rpx; }
|
||||
/* 名片/会员中心:紧挨昵称下方(在 profile-meta 内) */
|
||||
.profile-actions-under-name { margin-top: 4rpx; }
|
||||
/* 名片 / 会员中心:统一品牌青,与 tabBar 选中色一致 */
|
||||
.profile-action-btn {
|
||||
padding: 12rpx 28rpx; border: 2rpx solid #4FD1C5; color: #4FD1C5;
|
||||
|
||||
@@ -48,6 +48,8 @@ Page({
|
||||
wizardMode: false,
|
||||
wizardStep: 1,
|
||||
totalWizardSteps: 3,
|
||||
/** 头像区展示:含 MBTI 默认图 */
|
||||
avatarPreviewUrl: '',
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
@@ -83,6 +85,9 @@ Page({
|
||||
app.request({ url: `/api/miniprogram/user/profile?userId=${userId}`, silent: true }),
|
||||
app.request({ url: `/api/miniprogram/vip/status?userId=${userId}`, silent: true }),
|
||||
])
|
||||
try {
|
||||
if (app.loadMbtiAvatarsMap) await app.loadMbtiAvatarsMap()
|
||||
} catch (_) {}
|
||||
this.setData({ isVip: vipRes?.data?.isVip || false })
|
||||
const res = profileRes
|
||||
if (res?.success && res.data) {
|
||||
@@ -110,6 +115,7 @@ Page({
|
||||
loading: false,
|
||||
})
|
||||
this._applyWizardModeFromProfile(d)
|
||||
this._syncAvatarPreview()
|
||||
setTimeout(() => this.generateShareCard(), 200)
|
||||
} else {
|
||||
this.setData({ loading: false })
|
||||
@@ -367,7 +373,22 @@ Page({
|
||||
|
||||
onMbtiPickerChange(e) {
|
||||
const i = parseInt(e.detail.value, 10)
|
||||
this.setData({ mbtiIndex: i, mbti: MBTI_OPTIONS[i] })
|
||||
this.setData({ mbtiIndex: i, mbti: MBTI_OPTIONS[i] }, () => this._syncAvatarPreview())
|
||||
},
|
||||
|
||||
_syncAvatarPreview() {
|
||||
try {
|
||||
const { resolveAvatarWithMbti } = require('../../utils/mbtiAvatar.js')
|
||||
const url = resolveAvatarWithMbti(
|
||||
this.data.avatar,
|
||||
this.data.mbti,
|
||||
app.globalData.mbtiAvatarsMap || {},
|
||||
app.globalData.baseUrl || ''
|
||||
)
|
||||
this.setData({ avatarPreviewUrl: url || '' })
|
||||
} catch (_) {
|
||||
this.setData({ avatarPreviewUrl: (this.data.avatar || '').trim() })
|
||||
}
|
||||
},
|
||||
|
||||
// 微信原生 chooseAvatar 回调(点击头像直接弹出原生选择器:用微信头像/从相册选择/拍照)
|
||||
@@ -400,7 +421,7 @@ Page({
|
||||
if (avatarUrl && !avatarUrl.startsWith('http')) {
|
||||
avatarUrl = app.globalData.baseUrl + avatarUrl
|
||||
}
|
||||
this.setData({ avatar: avatarUrl })
|
||||
this.setData({ avatar: avatarUrl }, () => this._syncAvatarPreview())
|
||||
const avatarToSave = toAvatarPath(avatarUrl)
|
||||
await app.request({
|
||||
url: '/api/miniprogram/user/profile',
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<button class="avatar-wrap-btn" open-type="chooseAvatar" bindchooseavatar="onChooseAvatar">
|
||||
<view class="avatar-wrap">
|
||||
<view class="avatar-inner">
|
||||
<image wx:if="{{avatar}}" class="avatar-img" src="{{avatar}}" mode="aspectFill"/>
|
||||
<image wx:if="{{avatarPreviewUrl}}" class="avatar-img" src="{{avatarPreviewUrl}}" mode="aspectFill"/>
|
||||
<view wx:else class="avatar-placeholder">{{nickname ? nickname[0] : '?'}}</view>
|
||||
</view>
|
||||
<view class="avatar-camera"><icon name="camera" size="48" color="#ffffff"></icon></view>
|
||||
@@ -179,7 +179,7 @@
|
||||
<button class="avatar-wrap-btn" open-type="chooseAvatar" bindchooseavatar="onChooseAvatar">
|
||||
<view class="avatar-wrap">
|
||||
<view class="avatar-inner">
|
||||
<image wx:if="{{avatar}}" class="avatar-img" src="{{avatar}}" mode="aspectFill"/>
|
||||
<image wx:if="{{avatarPreviewUrl}}" class="avatar-img" src="{{avatarPreviewUrl}}" mode="aspectFill"/>
|
||||
<view wx:else class="avatar-placeholder">{{nickname ? nickname[0] : '?'}}</view>
|
||||
</view>
|
||||
<view class="avatar-camera"><icon name="camera" size="48" color="#ffffff"></icon></view>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
const app = getApp()
|
||||
const { isSafeImageSrc } = require('../../utils/imageUrl.js')
|
||||
const { resolveAvatarWithMbti } = require('../../utils/mbtiAvatar.js')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
@@ -31,6 +32,9 @@ Page({
|
||||
}
|
||||
this.setData({ loading: true })
|
||||
try {
|
||||
try {
|
||||
if (app.loadMbtiAvatarsMap) await app.loadMbtiAvatarsMap()
|
||||
} catch (_) {}
|
||||
const res = await app.request({ url: `/api/miniprogram/user/profile?userId=${userInfo.id}`, silent: true })
|
||||
if (res?.success && res.data) {
|
||||
const d = res.data
|
||||
@@ -38,10 +42,17 @@ Page({
|
||||
const phone = d.phone || ''
|
||||
const wechat = d.wechatId || wx.getStorageSync('user_wechat') || ''
|
||||
const av = d.avatar
|
||||
const safeAv = isSafeImageSrc(av) ? String(av).trim() : ''
|
||||
const displayAv = resolveAvatarWithMbti(
|
||||
safeAv,
|
||||
d.mbti,
|
||||
app.globalData.mbtiAvatarsMap || {},
|
||||
app.globalData.baseUrl || ''
|
||||
)
|
||||
this.setData({
|
||||
profile: {
|
||||
...d,
|
||||
avatar: isSafeImageSrc(av) ? String(av).trim() : '',
|
||||
avatar: displayAv,
|
||||
industry: e(d.industry),
|
||||
position: e(d.position),
|
||||
businessScale: e(d.businessScale || d.business_scale),
|
||||
|
||||
@@ -46,6 +46,8 @@ Page({
|
||||
|
||||
// 绑定用户列表
|
||||
showBindingList: true,
|
||||
showShareSection: false,
|
||||
showEarningsDetails: false,
|
||||
activeTab: 'active',
|
||||
activeBindings: [],
|
||||
convertedBindings: [],
|
||||
@@ -63,10 +65,13 @@ Page({
|
||||
posterReferralLink: '',
|
||||
posterNickname: '',
|
||||
posterNicknameInitial: '',
|
||||
/** 推广海报推荐人头像:优先微信头像,否则按 MBTI 用后台映射 */
|
||||
posterDisplayAvatar: '',
|
||||
posterCaseCount: 62
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
onLoad(options) {
|
||||
this._focusTarget = (options && options.focus) || ""
|
||||
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
|
||||
this.initData()
|
||||
// 启用分享到朋友圈(需同时有 onShareAppMessage 和 onShareTimeline;menus 在 Android 支持,iOS 为 Beta)
|
||||
@@ -207,12 +212,37 @@ Page({
|
||||
|
||||
// 隐藏加载提示
|
||||
wx.hideLoading()
|
||||
this._applyFocusTarget()
|
||||
} else {
|
||||
// 未登录时也隐藏loading
|
||||
this.setData({ isLoading: false })
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
toggleShareSection() {
|
||||
this.setData({ showShareSection: !this.data.showShareSection })
|
||||
},
|
||||
|
||||
toggleEarningsDetails() {
|
||||
this.setData({ showEarningsDetails: !this.data.showEarningsDetails })
|
||||
},
|
||||
|
||||
_applyFocusTarget() {
|
||||
const t = this._focusTarget || ''
|
||||
if (!t) return
|
||||
if (t === 'bindings') {
|
||||
this.setData({ showBindingList: true, activeTab: 'active', currentBindings: this.data.activeBindings })
|
||||
setTimeout(() => wx.pageScrollTo({ selector: '#binding-card', duration: 220 }), 80)
|
||||
this._focusTarget = ''
|
||||
return
|
||||
}
|
||||
if (t === 'earnings') {
|
||||
setTimeout(() => wx.pageScrollTo({ selector: '#earnings-card', duration: 220 }), 80)
|
||||
this._focusTarget = ''
|
||||
return
|
||||
}
|
||||
},
|
||||
// 切换Tab
|
||||
switchTab(e) {
|
||||
const tab = e.currentTarget.dataset.tab
|
||||
@@ -342,11 +372,20 @@ Page({
|
||||
|
||||
console.log('[Poster] 小程序码已保存到本地:', filePath)
|
||||
|
||||
try {
|
||||
if (app.loadMbtiAvatarsMap) await app.loadMbtiAvatarsMap()
|
||||
} catch (_) {}
|
||||
const u = this.data.userInfo || app.globalData.userInfo || {}
|
||||
const posterDisplayAvatar = app.resolveAvatarWithMbti
|
||||
? app.resolveAvatarWithMbti(u.avatar, u.mbti)
|
||||
: ''
|
||||
|
||||
this.setData({
|
||||
posterQrSrc: filePath,
|
||||
posterReferralLink: '', // 小程序版本不再使用 H5 链接
|
||||
posterNickname: nickname,
|
||||
posterNicknameInitial: (nickname || '用').charAt(0),
|
||||
posterDisplayAvatar,
|
||||
isGeneratingPoster: false
|
||||
})
|
||||
wx.hideLoading()
|
||||
@@ -354,7 +393,7 @@ Page({
|
||||
console.error('[Poster] 生成二维码失败:', e)
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '生成失败', icon: 'none' })
|
||||
this.setData({ showPosterModal: false, isGeneratingPoster: false, posterQrSrc: '', posterReferralLink: '' })
|
||||
this.setData({ showPosterModal: false, isGeneratingPoster: false, posterQrSrc: '', posterReferralLink: '', posterDisplayAvatar: '' })
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
</view>
|
||||
|
||||
<!-- 收益卡片 - 对齐 Next.js -->
|
||||
<view class="earnings-card">
|
||||
<view class="earnings-bg"></view>
|
||||
<view id="earnings-card" class="earnings-card">
|
||||
<view class="earnings-main">
|
||||
<view class="earnings-header">
|
||||
<view class="earnings-left">
|
||||
@@ -87,7 +86,7 @@
|
||||
</view>
|
||||
|
||||
<!-- 绑定用户列表 -->
|
||||
<view class="binding-card">
|
||||
<view id="binding-card" class="binding-card">
|
||||
<view class="binding-header" bindtap="toggleBindingList">
|
||||
<view class="binding-title">
|
||||
<image class="binding-icon-img" src="/assets/icons/users.svg" mode="aspectFit"></image>
|
||||
@@ -161,9 +160,14 @@
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<!-- 分享按钮 - 1:1 对齐 Next.js -->
|
||||
<view class="share-section">
|
||||
<view class="share-item" bindtap="generatePoster">
|
||||
<!-- 分享按钮(可折叠,默认收起) -->
|
||||
<view class="section-collapse-card">
|
||||
<view class="section-collapse-header" bindtap="toggleShareSection">
|
||||
<text class="section-collapse-title">分享推广</text>
|
||||
<icon name="{{showShareSection ? 'chevron-up' : 'chevron-down'}}" size="28" color="rgba(255,255,255,0.6)"></icon>
|
||||
</view>
|
||||
<view class="share-section" wx:if="{{showShareSection}}">
|
||||
<view class="share-item" bindtap="generatePoster">
|
||||
<view class="share-icon poster">
|
||||
<image class="icon-share-btn" src="/assets/icons/image.svg" mode="aspectFit"></image>
|
||||
</view>
|
||||
@@ -196,13 +200,15 @@
|
||||
<image class="share-arrow-icon" src="/assets/icons/arrow-right.svg" mode="aspectFit"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 收益明细 - 增强版 -->
|
||||
<view class="earnings-detail-card" wx:if="{{earningsDetails.length > 0}}">
|
||||
<view class="detail-header">
|
||||
<text class="detail-title">收益明细</text>
|
||||
<!-- 收益明细(可折叠,默认收起) -->
|
||||
<view class="section-collapse-card earnings-detail-card" wx:if="{{earningsDetails.length > 0}}">
|
||||
<view class="section-collapse-header" bindtap="toggleEarningsDetails">
|
||||
<text class="section-collapse-title">收益明细</text>
|
||||
<icon name="{{showEarningsDetails ? 'chevron-up' : 'chevron-down'}}" size="28" color="rgba(255,255,255,0.6)"></icon>
|
||||
</view>
|
||||
<view class="detail-list">
|
||||
<view class="detail-list" wx:if="{{showEarningsDetails}}">
|
||||
<view class="detail-item" wx:for="{{earningsDetails}}" wx:key="id">
|
||||
<!-- 买家头像 -->
|
||||
<view class="detail-avatar-wrap">
|
||||
@@ -297,7 +303,8 @@
|
||||
<!-- 推荐人 -->
|
||||
<view class="poster-recommender">
|
||||
<view class="poster-avatar">
|
||||
<text class="poster-avatar-text">{{posterNicknameInitial}}</text>
|
||||
<image wx:if="{{posterDisplayAvatar}}" class="poster-avatar-img" src="{{posterDisplayAvatar}}" mode="aspectFill" />
|
||||
<text wx:else class="poster-avatar-text">{{posterNicknameInitial}}</text>
|
||||
</view>
|
||||
<text class="poster-recommender-text">{{posterNickname}} 推荐你来读</text>
|
||||
</view>
|
||||
|
||||
@@ -1,28 +1,39 @@
|
||||
/* ???????? - 1:1??Web?? */
|
||||
@import './earnings-detail-styles.wxss';
|
||||
|
||||
/* 分销中心 - 主结构样式 */
|
||||
.page { min-height: 100vh; background: #000; padding-bottom: 64rpx; }
|
||||
|
||||
/* ??? */
|
||||
.nav-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 100; background: rgba(0,0,0,0.9); backdrop-filter: blur(40rpx); display: flex; align-items: center; justify-content: space-between; padding: 0 32rpx; height: 88rpx; }
|
||||
.nav-bar {
|
||||
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
|
||||
background: rgba(0,0,0,0.92);
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 0 32rpx; height: 88rpx;
|
||||
border-bottom: 1rpx solid rgba(255,255,255,0.06);
|
||||
}
|
||||
.nav-left { display: flex; gap: 16rpx; align-items: center; }
|
||||
.nav-back { width: 64rpx; height: 64rpx; background: #1c1c1e; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
|
||||
.nav-icon { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); }
|
||||
.nav-icon { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(70%); }
|
||||
.nav-title { font-size: 34rpx; font-weight: 600; color: #fff; flex: 1; text-align: center; }
|
||||
.nav-right-placeholder { width: 144rpx; }
|
||||
.nav-btn { width: 64rpx; height: 64rpx; background: #1c1c1e; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
|
||||
|
||||
.content { padding: 24rpx; width: 100%; box-sizing: border-box; }
|
||||
|
||||
/* ?????? */
|
||||
.expiring-banner { display: flex; align-items: center; gap: 24rpx; padding: 24rpx; background: rgba(255,165,0,0.1); border: 2rpx solid rgba(255,165,0,0.3); border-radius: 24rpx; margin-bottom: 24rpx; }
|
||||
.expiring-banner {
|
||||
display: flex; align-items: center; gap: 24rpx; padding: 24rpx;
|
||||
background: rgba(255,165,0,0.1); border: 2rpx solid rgba(255,165,0,0.3);
|
||||
border-radius: 24rpx; margin-bottom: 24rpx;
|
||||
}
|
||||
.banner-icon { width: 80rpx; height: 80rpx; background: rgba(255,165,0,0.2); border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.icon-bell-warning { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(64%) sepia(89%) saturate(1363%) hue-rotate(4deg) brightness(101%) contrast(102%); }
|
||||
.banner-content { flex: 1; }
|
||||
.banner-title { font-size: 28rpx; font-weight: 500; color: #fff; display: block; }
|
||||
.banner-desc { font-size: 24rpx; color: rgba(255,165,0,0.8); margin-top: 4rpx; display: block; }
|
||||
|
||||
/* ???? - ?? Next.js */
|
||||
.earnings-card { position: relative; background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; padding: 48rpx; margin-bottom: 24rpx; overflow: hidden; width: 100%; box-sizing: border-box; }
|
||||
.earnings-bg { position: absolute; top: 0; right: 0; width: 256rpx; height: 256rpx; background: rgba(0,206,209,0.15); border-radius: 50%; filter: blur(100rpx); }
|
||||
.earnings-card {
|
||||
position: relative; background: rgba(28, 28, 30, 0.8); border: 2rpx solid rgba(255,255,255,0.1);
|
||||
border-radius: 32rpx; padding: 48rpx; margin-bottom: 24rpx; overflow: hidden;
|
||||
width: 100%; box-sizing: border-box;
|
||||
}
|
||||
.earnings-main { position: relative; }
|
||||
.earnings-header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 32rpx; }
|
||||
.earnings-left { display: flex; align-items: center; gap: 16rpx; }
|
||||
@@ -34,26 +45,17 @@
|
||||
.earnings-right { text-align: right; }
|
||||
.earnings-value { font-size: 60rpx; font-weight: 700; color: #fff; display: block; line-height: 1; }
|
||||
.pending-text { font-size: 24rpx; color: rgba(255,255,255,0.5); margin-top: 8rpx; display: block; }
|
||||
|
||||
.withdraw-btn { padding: 28rpx; background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); color: #fff; font-size: 32rpx; font-weight: 600; text-align: center; border-radius: 24rpx; box-shadow: 0 8rpx 24rpx rgba(0,206,209,0.3); }
|
||||
.withdraw-btn.btn-disabled { background: rgba(0,206,209,0.2); color: rgba(255,255,255,0.3); box-shadow: none; }
|
||||
.withdraw-btn { padding: 28rpx; background: linear-gradient(135deg, #00CED1 0%, #20B2AA 100%); color: #fff; font-size: 32rpx; font-weight: 600; text-align: center; border-radius: 24rpx; }
|
||||
.withdraw-btn.btn-disabled { background: rgba(0,206,209,0.2); color: rgba(255,255,255,0.3); }
|
||||
.wechat-tip { display: block; font-size: 24rpx; color: rgba(255,165,0,0.9); margin-top: 16rpx; text-align: center; }
|
||||
.withdraw-records-link { display: block; margin-top: 16rpx; text-align: center; font-size: 26rpx; color: #00CED1; }
|
||||
|
||||
/* ???? - ?? Next.js 4??? */
|
||||
.stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8rpx; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
|
||||
.stat-card { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 24rpx; padding: 24rpx 12rpx; text-align: center; }
|
||||
.stat-card { background: rgba(28, 28, 30, 0.8); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 24rpx; padding: 24rpx 12rpx; text-align: center; }
|
||||
.stat-value { font-size: 40rpx; font-weight: 700; color: #fff; display: block; }
|
||||
.stat-value.orange { color: #FFA500; }
|
||||
.stat-label { font-size: 20rpx; color: rgba(255,255,255,0.6); margin-top: 8rpx; display: block; }
|
||||
|
||||
/* ????? */
|
||||
.visit-stat { display: flex; align-items: center; justify-content: center; gap: 12rpx; padding: 20rpx 32rpx; background: rgba(255,255,255,0.05); border-radius: 16rpx; margin-bottom: 24rpx; }
|
||||
.visit-label { font-size: 24rpx; color: rgba(255,255,255,0.5); }
|
||||
.visit-value { font-size: 32rpx; font-weight: 700; color: #00CED1; }
|
||||
.visit-tip { font-size: 24rpx; color: rgba(255,255,255,0.5); }
|
||||
|
||||
/* ???? - ?? Next.js */
|
||||
.rules-card { background: rgba(0,206,209,0.05); border: 2rpx solid rgba(0,206,209,0.2); border-radius: 24rpx; padding: 32rpx; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
|
||||
.rules-header { display: flex; align-items: center; gap: 16rpx; margin-bottom: 16rpx; }
|
||||
.rules-icon { width: 64rpx; height: 64rpx; background: rgba(0,206,209,0.2); border-radius: 16rpx; display: flex; align-items: center; justify-content: center; }
|
||||
@@ -63,240 +65,89 @@
|
||||
.rule-item { font-size: 24rpx; color: rgba(255,255,255,0.6); line-height: 2; display: block; margin-bottom: 4rpx; }
|
||||
.rule-item .gold { color: #FFD700; font-weight: 500; }
|
||||
.rule-item .brand { color: #00CED1; font-weight: 500; }
|
||||
.rule-item .orange { color: #FFA500; font-weight: 500; }
|
||||
|
||||
/* ?????? - ?? Next.js */
|
||||
.binding-card { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; overflow: hidden; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
|
||||
.binding-card { background: rgba(28, 28, 30, 0.8); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; overflow: hidden; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
|
||||
.binding-header { display: flex; align-items: center; justify-content: space-between; padding: 28rpx 32rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
|
||||
.binding-title { display: flex; align-items: center; gap: 12rpx; }
|
||||
.binding-icon-img { width: 40rpx; height: 40rpx; display: block; filter: brightness(0) saturate(100%) invert(72%) sepia(54%) saturate(2933%) hue-rotate(134deg) brightness(101%) contrast(101%); }
|
||||
.title-text { font-size: 30rpx; font-weight: 600; color: #fff; }
|
||||
.binding-count { font-size: 26rpx; color: rgba(255,255,255,0.5); }
|
||||
.toggle-icon { font-size: 24rpx; color: rgba(255,255,255,0.5); }
|
||||
|
||||
/* Tab?? */
|
||||
.binding-tabs { display: flex; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
|
||||
.tab-item { flex: 1; padding: 24rpx 0; text-align: center; font-size: 26rpx; color: rgba(255,255,255,0.5); position: relative; }
|
||||
.tab-item.tab-active { color: #00CED1; }
|
||||
.tab-item.tab-active::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 80rpx; height: 4rpx; background: #00CED1; border-radius: 4rpx; }
|
||||
|
||||
/* ???? */
|
||||
.binding-list { max-height: 640rpx; overflow-y: auto; }
|
||||
.empty-state { padding: 80rpx 0; text-align: center; }
|
||||
.empty-icon { font-size: 64rpx; display: block; margin-bottom: 16rpx; }
|
||||
.empty-text { font-size: 26rpx; color: rgba(255,255,255,0.5); }
|
||||
|
||||
/* ?????? - ??? */
|
||||
.binding-item { display: flex; align-items: center; padding: 24rpx 32rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); gap: 24rpx; }
|
||||
.binding-item:last-child { border-bottom: none; }
|
||||
|
||||
/* ?? */
|
||||
.user-avatar { width: 88rpx; height: 88rpx; border-radius: 50%; background: rgba(0,206,209,0.2); display: flex; align-items: center; justify-content: center; font-size: 32rpx; font-weight: 600; color: #00CED1; flex-shrink: 0; }
|
||||
.user-avatar.avatar-converted { background: rgba(76,175,80,0.2); color: #4CAF50; }
|
||||
.user-avatar.avatar-expired { background: rgba(158,158,158,0.2); color: #9E9E9E; }
|
||||
|
||||
/* ???? */
|
||||
.user-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4rpx; }
|
||||
.user-name { font-size: 28rpx; color: #fff; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.user-time { font-size: 22rpx; color: rgba(255,255,255,0.5); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
|
||||
/* ???? */
|
||||
.user-status { flex-shrink: 0; text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: 4rpx; min-width: 100rpx; }
|
||||
.status-amount { font-size: 28rpx; color: #4CAF50; font-weight: 600; white-space: nowrap; }
|
||||
.status-order { font-size: 20rpx; color: rgba(255,255,255,0.5); white-space: nowrap; }
|
||||
.status-time { font-size: 20rpx; color: rgba(255,255,255,0.5); white-space: nowrap; }
|
||||
|
||||
/* ???? */
|
||||
.status-order, .status-time { font-size: 20rpx; color: rgba(255,255,255,0.5); white-space: nowrap; }
|
||||
.status-tag { font-size: 24rpx; font-weight: 600; padding: 6rpx 16rpx; border-radius: 16rpx; white-space: nowrap; }
|
||||
.status-tag.tag-green { background: rgba(76,175,80,0.2); color: #4CAF50; }
|
||||
.status-tag.tag-orange { background: rgba(255,165,0,0.2); color: #FFA500; }
|
||||
.status-tag.tag-red { background: rgba(244,67,54,0.2); color: #F44336; }
|
||||
.status-tag.tag-gray { background: rgba(158,158,158,0.2); color: #9E9E9E; }
|
||||
|
||||
/* ????? - ?? Next.js */
|
||||
.invite-card { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; padding: 40rpx; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
|
||||
.invite-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 24rpx; }
|
||||
.invite-title { font-size: 30rpx; font-weight: 600; color: #fff; }
|
||||
.invite-code-box { background: rgba(0,206,209,0.2); padding: 12rpx 24rpx; border-radius: 16rpx; }
|
||||
.invite-code { font-size: 28rpx; font-weight: 600; color: #00CED1; font-family: 'Courier New', monospace; letter-spacing: 2rpx; }
|
||||
.invite-tip { font-size: 24rpx; color: rgba(255,255,255,0.6); line-height: 1.5; display: block; }
|
||||
.invite-tip .gold { color: #FFD700; }
|
||||
.invite-tip .brand { color: #00CED1; }
|
||||
|
||||
/* ?????? - ??? */
|
||||
.earnings-detail-card { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; overflow: hidden; margin-bottom: 24rpx; width: 100%; box-sizing: border-box; }
|
||||
.detail-header { padding: 40rpx 40rpx 24rpx; border-bottom: 2rpx solid rgba(255,255,255,0.05); }
|
||||
.detail-title { font-size: 30rpx; font-weight: 600; color: #fff; }
|
||||
.detail-list { max-height: 480rpx; overflow-y: auto; padding: 16rpx 0; }
|
||||
|
||||
/* ??????? */
|
||||
.earnings-detail-card .detail-item { display: flex; align-items: center; gap: 24rpx; padding: 24rpx 40rpx; background: transparent; border-bottom: 2rpx solid rgba(255,255,255,0.03); }
|
||||
.earnings-detail-card .detail-item:last-child { border-bottom: none; }
|
||||
.earnings-detail-card .detail-item:active { background: rgba(255, 255, 255, 0.05); }
|
||||
|
||||
/* ???? */
|
||||
.earnings-detail-card .detail-avatar-wrap { width: 88rpx; height: 88rpx; flex-shrink: 0; }
|
||||
.earnings-detail-card .detail-avatar { width: 100%; height: 100%; border-radius: 50%; border: 2rpx solid rgba(56, 189, 172, 0.2); }
|
||||
.earnings-detail-card .detail-avatar-text { width: 100%; height: 100%; border-radius: 50%; background: linear-gradient(135deg, #38bdac 0%, #2da396 100%); display: flex; align-items: center; justify-content: center; font-size: 36rpx; font-weight: 700; color: #ffffff; }
|
||||
|
||||
/* ???? */
|
||||
.earnings-detail-card .detail-content { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 8rpx; }
|
||||
.earnings-detail-card .detail-top { display: flex; align-items: center; justify-content: space-between; gap: 16rpx; }
|
||||
.earnings-detail-card .detail-buyer { font-size: 28rpx; font-weight: 500; color: #ffffff; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.earnings-detail-card .detail-amount { font-size: 32rpx; font-weight: 700; color: #38bdac; flex-shrink: 0; white-space: nowrap; }
|
||||
|
||||
/* ???? */
|
||||
.earnings-detail-card .detail-product { display: flex; align-items: baseline; gap: 4rpx; font-size: 24rpx; color: rgba(255, 255, 255, 0.6); min-width: 0; overflow: hidden; }
|
||||
.earnings-detail-card .detail-book { color: rgba(255, 255, 255, 0.7); font-weight: 500; max-width: 50%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.earnings-detail-card .detail-chapter { color: rgba(255, 255, 255, 0.5); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.earnings-detail-card .detail-time { font-size: 22rpx; color: rgba(255, 255, 255, 0.4); }
|
||||
|
||||
/* ???? - ?? Next.js */
|
||||
.share-section { display: flex; flex-direction: column; gap: 12rpx; width: 100%; margin-bottom: 24rpx; }
|
||||
.share-item { display: flex; align-items: center; background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 24rpx; padding: 32rpx; border: none; margin: 0; text-align: left; width: 100%; box-sizing: border-box; }
|
||||
.share-item { display: flex; align-items: center; background: rgba(28, 28, 30, 0.8); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 24rpx; padding: 32rpx; width: 100%; box-sizing: border-box; }
|
||||
.share-item::after { border: none; }
|
||||
.share-icon { width: 96rpx; height: 96rpx; border-radius: 20rpx; display: flex; align-items: center; justify-content: center; margin-right: 24rpx; flex-shrink: 0; }
|
||||
.share-icon.poster { background: rgba(103,58,183,0.2); }
|
||||
.share-icon.wechat { background: rgba(7,193,96,0.2); }
|
||||
.share-icon.link { background: rgba(158,158,158,0.2); }
|
||||
.icon-share-btn { width: 48rpx; height: 48rpx; display: block; }
|
||||
.share-icon.poster .icon-share-btn { filter: brightness(0) saturate(100%) invert(37%) sepia(73%) saturate(2296%) hue-rotate(252deg) brightness(96%) contrast(92%); }
|
||||
.share-icon.wechat .icon-share-btn { filter: brightness(0) saturate(100%) invert(58%) sepia(91%) saturate(1255%) hue-rotate(105deg) brightness(96%) contrast(97%); }
|
||||
.share-icon.link .icon-share-btn { filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); }
|
||||
.share-info { flex: 1; text-align: left; }
|
||||
.share-title { font-size: 28rpx; color: #fff; font-weight: 500; display: block; text-align: left; }
|
||||
.share-desc { font-size: 22rpx; color: rgba(255,255,255,0.5); margin-top: 4rpx; display: block; text-align: left; }
|
||||
.share-arrow-icon { width: 40rpx; height: 40rpx; display: block; flex-shrink: 0; filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); }
|
||||
.share-btn-wechat { line-height: normal; font-size: inherit; padding: 24rpx 32rpx !important; margin: 0 !important; width: 100% !important; }
|
||||
.share-title { font-size: 28rpx; color: #fff; font-weight: 500; display: block; }
|
||||
.share-desc { font-size: 22rpx; color: rgba(255,255,255,0.5); margin-top: 4rpx; display: block; }
|
||||
.share-arrow-icon { width: 40rpx; height: 40rpx; display: block; flex-shrink: 0; filter: brightness(0) saturate(100%) invert(60%); }
|
||||
|
||||
.empty-earnings { background: rgba(28, 28, 30, 0.8); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; padding: 64rpx 40rpx; text-align: center; margin-bottom: 24rpx; }
|
||||
.empty-icon-wrapper { width: 128rpx; height: 128rpx; border-radius: 50%; background: rgba(28, 28, 30, 0.8); display: flex; align-items: center; justify-content: center; margin: 0 auto 32rpx; }
|
||||
.empty-gift-icon { width: 64rpx; height: 64rpx; display: block; filter: brightness(0) saturate(100%) invert(60%); }
|
||||
.empty-title { font-size: 30rpx; font-weight: 500; color: #fff; display: block; margin-bottom: 16rpx; }
|
||||
.empty-desc { font-size: 26rpx; color: rgba(255,255,255,0.6); display: block; line-height: 1.5; }
|
||||
|
||||
/* ?????????????? + ???? + ???????? */
|
||||
/* ???????? backdrop-filter??????????????? */
|
||||
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; z-index: 1000; padding: 32rpx; box-sizing: border-box; }
|
||||
|
||||
.poster-dialog { width: 686rpx; border-radius: 24rpx; overflow: hidden; position: relative; background: transparent; }
|
||||
.poster-close { position: absolute; top: 20rpx; right: 20rpx; width: 56rpx; height: 56rpx; border-radius: 28rpx; background: rgba(0,0,0,0.25); color: rgba(255,255,255,0.9); display: flex; align-items: center; justify-content: center; z-index: 5; font-size: 28rpx; }
|
||||
|
||||
/* ???? */
|
||||
.poster-close { position: absolute; top: 20rpx; right: 20rpx; width: 56rpx; height: 56rpx; border-radius: 28rpx; background: rgba(0,0,0,0.25); display: flex; align-items: center; justify-content: center; z-index: 5; }
|
||||
.poster-card { position: relative; background: linear-gradient(135deg, #0a1628 0%, #0f2137 50%, #1a3a5c 100%); color: #fff; padding: 44rpx 40rpx 36rpx; }
|
||||
.poster-inner { position: relative; z-index: 2; display: flex; flex-direction: column; align-items: center; text-align: center; }
|
||||
|
||||
/* ???? */
|
||||
/* ???????? filter: blur ??????????????? + ???? */
|
||||
.poster-glow { position: absolute; width: 320rpx; height: 320rpx; border-radius: 50%; opacity: 0.6; z-index: 1; }
|
||||
.poster-glow-left { top: -120rpx; left: -160rpx; background: rgba(0,206,209,0.12); box-shadow: 0 0 140rpx 40rpx rgba(0,206,209,0.18); }
|
||||
.poster-glow-right { bottom: -140rpx; right: -160rpx; background: rgba(255,215,0,0.10); box-shadow: 0 0 160rpx 50rpx rgba(255,215,0,0.14); }
|
||||
.poster-ring { position: absolute; width: 520rpx; height: 520rpx; border-radius: 50%; border: 2rpx solid rgba(0,206,209,0.06); left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 1; }
|
||||
|
||||
/* ???? */
|
||||
.poster-badges { display: flex; gap: 16rpx; margin-bottom: 24rpx; }
|
||||
.poster-badge { padding: 10rpx 22rpx; font-size: 20rpx; font-weight: 700; border-radius: 999rpx; border: 2rpx solid transparent; }
|
||||
.poster-badge-gold { background: rgba(255,215,0,0.18); color: #FFD700; border-color: rgba(255,215,0,0.28); }
|
||||
.poster-badge-brand { background: rgba(0,206,209,0.18); color: #00CED1; border-color: rgba(0,206,209,0.28); }
|
||||
|
||||
/* ?? */
|
||||
.poster-title { margin-bottom: 8rpx; }
|
||||
.poster-title-line1 { display: block; font-size: 44rpx; font-weight: 900; line-height: 1.1; color: #00CED1; }
|
||||
.poster-title-line2 { display: block; font-size: 44rpx; font-weight: 900; line-height: 1.1; color: #fff; margin-top: 6rpx; }
|
||||
.poster-subtitle { display: block; font-size: 22rpx; color: rgba(255,255,255,0.6); margin-bottom: 28rpx; }
|
||||
|
||||
/* ???? */
|
||||
.poster-stats { width: 100%; display: flex; gap: 16rpx; justify-content: center; margin-bottom: 24rpx; }
|
||||
.poster-stat { flex: 1; max-width: 190rpx; background: rgba(255,255,255,0.05); border: 2rpx solid rgba(255,255,255,0.10); border-radius: 16rpx; padding: 18rpx 10rpx; }
|
||||
.poster-stat-value { display: block; font-size: 44rpx; font-weight: 900; line-height: 1; }
|
||||
.poster-stat-label { display: block; font-size: 20rpx; color: rgba(255,255,255,0.5); margin-top: 8rpx; }
|
||||
.poster-stat-gold { color: #FFD700; }
|
||||
.poster-stat-brand { color: #00CED1; }
|
||||
.poster-stat-pink { color: #E91E63; }
|
||||
|
||||
/* ?? */
|
||||
.poster-tags { display: flex; flex-wrap: wrap; justify-content: center; gap: 10rpx; margin: 0 24rpx 26rpx; }
|
||||
.poster-tag { font-size: 20rpx; color: rgba(255,255,255,0.7); background: rgba(255,255,255,0.05); border: 2rpx solid rgba(255,255,255,0.10); border-radius: 12rpx; padding: 6rpx 14rpx; }
|
||||
|
||||
/* ??? */
|
||||
.poster-recommender { display: flex; align-items: center; gap: 12rpx; background: rgba(0,206,209,0.10); border: 2rpx solid rgba(0,206,209,0.20); border-radius: 999rpx; padding: 12rpx 22rpx; margin-bottom: 22rpx; }
|
||||
.poster-avatar { width: 44rpx; height: 44rpx; border-radius: 22rpx; background: rgba(0,206,209,0.30); display: flex; align-items: center; justify-content: center; }
|
||||
.poster-avatar-text { font-size: 20rpx; font-weight: 800; color: #00CED1; }
|
||||
.poster-recommender-text { font-size: 22rpx; color: #00CED1; }
|
||||
|
||||
/* ??? */
|
||||
.poster-discount { width: 100%; background: linear-gradient(90deg, rgba(255,215,0,0.10) 0%, rgba(233,30,99,0.10) 100%); border: 2rpx solid rgba(255,215,0,0.20); border-radius: 18rpx; padding: 18rpx 18rpx; margin-bottom: 26rpx; }
|
||||
.poster-discount-text { font-size: 22rpx; color: rgba(255,255,255,0.80); }
|
||||
.poster-discount-highlight { color: #00CED1; font-weight: 800; }
|
||||
|
||||
/* ??? */
|
||||
.poster-qr-wrap { background: #fff; padding: 14rpx; border-radius: 16rpx; margin-bottom: 12rpx; box-shadow: 0 16rpx 40rpx rgba(0,0,0,0.35); }
|
||||
.poster-qr-img { width: 240rpx; height: 240rpx; display: block; }
|
||||
.poster-qr-tip { font-size: 20rpx; color: rgba(255,255,255,0.40); margin-bottom: 8rpx; }
|
||||
.poster-code { font-size: 22rpx; font-family: monospace; letter-spacing: 2rpx; color: rgba(0,206,209,0.80); }
|
||||
|
||||
/* ??????? */
|
||||
.poster-footer { background: #fff; padding: 28rpx 28rpx 32rpx; display: flex; flex-direction: column; gap: 18rpx; }
|
||||
.poster-footer-tip { font-size: 22rpx; color: rgba(0,0,0,0.55); text-align: center; }
|
||||
.poster-footer-btn { height: 72rpx; border-radius: 18rpx; border: 2rpx solid rgba(0,0,0,0.15); display: flex; align-items: center; justify-content: center; font-size: 28rpx; color: rgba(0,0,0,0.75); background: #fff; }
|
||||
|
||||
| ||||