初始提交:一场soul的创业实验-永平 网站与小程序

Made-with: Cursor
This commit is contained in:
卡若
2026-03-07 22:58:43 +08:00
commit b7c35a89b0
513 changed files with 89020 additions and 0 deletions

View File

@@ -0,0 +1,200 @@
/**
* Soul创业派对 - 超级个体/会员详情页
* 接口:优先 /api/miniprogram/vip/members?id=xxVIP回退 /api/miniprogram/users?id=xx任意用户
* 头像/昵称统一用用户资料nickname/avatar优先随「我的」修改实时生效
* mbti, region, industry, position, businessScale, skills,
* storyBestMonth→bestMonth, storyAchievement→achievement, storyTurning→turningPoint,
* helpOffer→canHelp, helpNeed→needHelp
*/
const app = getApp()
Page({
data: { statusBarHeight: 44, member: null, loading: true },
onLoad(options) {
wx.showShareMenu({ withShareTimeline: true })
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
if (options.id) this.loadMember(options.id)
},
async loadMember(id) {
try {
const res = await app.request({ url: `/api/miniprogram/vip/members?id=${id}`, silent: true })
if (res?.success && res.data) {
const d = Array.isArray(res.data) ? res.data[0] : res.data
if (d) { this.setData({ member: this.enrichAndFormat(d), loading: false }); return }
}
} catch (e) {}
try {
const dbRes = await app.request({ url: `/api/miniprogram/users?id=${id}`, silent: true })
if (dbRes?.success && dbRes.data) {
const u = Array.isArray(dbRes.data) ? dbRes.data[0] : dbRes.data
if (u) {
this.setData({ member: this.enrichAndFormat({
id: u.id, name: u.nickname || u.vipName || u.vip_name || '创业者',
avatar: u.avatar || u.vipAvatar || u.vip_avatar || '', isVip: !!(u.is_vip),
contactRaw: u.vipContact || u.vip_contact || u.phone || '',
wechatId: u.wechatId || u.wechat_id,
project: u.vipProject || u.vip_project || u.projectIntro || u.project_intro || '',
industry: u.industry, position: u.position, businessScale: u.businessScale || u.business_scale,
skills: u.skills, mbti: u.mbti, region: u.region,
storyBestMonth: u.storyBestMonth || u.story_best_month,
storyAchievement: u.storyAchievement || u.story_achievement,
storyTurning: u.storyTurning || u.story_turning,
helpOffer: u.helpOffer || u.help_offer,
helpNeed: u.helpNeed || u.help_need,
}), loading: false })
return
}
}
} catch (e) {}
this.setData({ loading: false })
},
// 将空值、「未填写」、纯空格均视为未填写(用于隐藏对应项)
_emptyIfPlaceholder(v) {
if (v == null || v === undefined) return ''
const s = String(v).trim()
return (s === '' || s === '未填写') ? '' : s
},
enrichAndFormat(raw) {
const e = (v) => this._emptyIfPlaceholder(v)
const merged = {
id: raw.id,
name: raw.nickname || raw.name || raw.vipName || raw.vip_name || '创业者',
avatar: raw.avatar || raw.vipAvatar || raw.vip_avatar || '',
isVip: !!(raw.isVip || raw.is_vip),
mbti: e(raw.mbti),
region: e(raw.region),
industry: e(raw.industry),
position: e(raw.position),
businessScale: e(raw.businessScale || raw.business_scale),
skills: e(raw.skills),
contactRaw: raw.contactRaw || raw.vipContact || raw.vip_contact || raw.phone || '',
wechatRaw: raw.wechatRaw || raw.wechatId || raw.wechat_id || '',
bestMonth: e(raw.bestMonth || raw.storyBestMonth || raw.story_best_month),
achievement: e(raw.achievement || raw.storyAchievement || raw.story_achievement),
turningPoint: e(raw.turningPoint || raw.storyTurning || raw.story_turning),
canHelp: e(raw.canHelp || raw.helpOffer || raw.help_offer),
needHelp: e(raw.needHelp || raw.helpNeed || raw.help_need),
project: e(raw.project || raw.vipProject || raw.vip_project || raw.projectIntro || raw.project_intro)
}
const contact = merged.contactRaw || ''
const wechat = merged.wechatRaw || ''
const isMatched = (app.globalData.matchedUsers || []).includes(merged.id)
const unlockData = this._getUnlockData(merged.id)
merged.contactDisplay = contact ? (contact.slice(0, 3) + '****' + (contact.length > 7 ? contact.slice(-2) : '')) : ''
merged.contactUnlocked = isMatched || unlockData.contact
merged.contactFull = contact
merged.wechatDisplay = wechat ? (wechat.slice(0, 4) + '****' + (wechat.length > 8 ? wechat.slice(-3) : '')) : ''
merged.wechatUnlocked = isMatched || unlockData.wechat
merged.wechatFull = wechat
return merged
},
_getUnlockData(memberId) {
const userId = app.globalData.userInfo?.id
if (!userId || !memberId) return { contact: false, wechat: false }
try {
const raw = wx.getStorageSync('member_unlocks_' + userId)
if (Array.isArray(raw) && raw.includes(memberId)) {
return { contact: true, wechat: true }
}
const data = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {}
const member = data[memberId]
return {
contact: !!(member && member.contact),
wechat: !!(member && member.wechat)
}
} catch (e) { return { contact: false, wechat: false } }
},
_addUnlock(memberId, field) {
const userId = app.globalData.userInfo?.id
if (!userId || !memberId || !field) return
let obj = wx.getStorageSync('member_unlocks_' + userId)
if (Array.isArray(obj)) {
obj = obj.reduce((o, id) => { o[id] = { contact: true, wechat: true }; return o }, {})
}
obj = obj && typeof obj === 'object' ? obj : {}
if (!obj[memberId]) obj[memberId] = {}
obj[memberId][field] = true
wx.setStorageSync('member_unlocks_' + userId, obj)
},
_hasUsedFreeForMember(memberId) {
const d = this._getUnlockData(memberId)
return d.contact || d.wechat
},
unlockField(e) {
const field = e.currentTarget.dataset.field
if (!field) return
const member = this.data.member
if (!member?.id || (field !== 'contact' && field !== 'wechat')) return
const isLoggedIn = app.globalData.isLoggedIn
if (!isLoggedIn) {
wx.showModal({
title: '需要登录',
content: '请先登录后再解锁超级个体联系方式',
confirmText: '去登录',
cancelText: '取消',
success: (res) => { if (res.confirm) wx.switchTab({ url: '/pages/my/my' }) }
})
return
}
const d = this._getUnlockData(member.id)
if (d[field]) return
const isVip = app.globalData.hasFullBook
const usedFree = this._hasUsedFreeForMember(member.id)
if (isVip || !usedFree) {
this._addUnlock(member.id, field)
const m = this.enrichAndFormat(member)
this.setData({ member: m })
wx.showToast({ title: field === 'contact' ? '已解锁联系方式' : '已解锁微信号', icon: 'success' })
return
}
wx.showModal({
title: '解锁' + (field === 'contact' ? '联系方式' : '微信号'),
content: '您的免费解锁次数已用完开通VIP会员¥1980/年)可无限解锁',
confirmText: '去开通',
cancelText: '取消',
success: (res) => { if (res.confirm) wx.navigateTo({ url: '/pages/vip/vip' }) }
})
},
copyContact() {
const c = this.data.member?.contactFull
if (!c) return
wx.setClipboardData({ data: c, success: () => wx.showToast({ title: '已复制', icon: 'success' }) })
},
copyWechat() {
const w = this.data.member?.wechatFull
if (!w) return
wx.setClipboardData({ data: w, success: () => wx.showToast({ title: '已复制', icon: 'success' }) })
},
goToMatch() { wx.switchTab({ url: '/pages/match/match' }) },
goToVip() { wx.navigateTo({ url: '/pages/vip/vip' }) },
goBack() { getApp().goBackOrToHome() },
onShareAppMessage() {
const ref = app.getMyReferralCode()
const id = this.data.member?.id
return {
title: 'Soul创业派对 - 创业者详情',
path: id && ref ? `/pages/member-detail/member-detail?id=${id}&ref=${ref}` : id ? `/pages/member-detail/member-detail?id=${id}` : ref ? `/pages/member-detail/member-detail?ref=${ref}` : '/pages/member-detail/member-detail'
}
},
onShareTimeline() {
const ref = app.getMyReferralCode()
const id = this.data.member?.id
const q = id ? (ref ? `id=${id}&ref=${ref}` : `id=${id}`) : (ref ? `ref=${ref}` : '')
return { title: 'Soul创业派对 - 创业者详情', query: q }
}
})

View File

@@ -0,0 +1 @@
{ "usingComponents": {}, "navigationStyle": "custom" }

View File

@@ -0,0 +1,150 @@
<!-- Soul创业派对 - 超级个体详情(按 enhanced_professional_profile 1:1 还原) -->
<view class="page">
<!-- 导航栏 -->
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
<view class="nav-back" bindtap="goBack">
<text class="nav-icon"></text>
</view>
<text class="nav-title">个人资料</text>
<view class="nav-placeholder"></view>
</view>
<view style="height: {{statusBarHeight + 44}}px;"></view>
<scroll-view scroll-y class="scroll-wrap" wx:if="{{member}}">
<!-- 顶部 profile 卡片 -->
<view class="card-profile">
<view class="profile-deco"></view>
<view class="profile-body">
<view class="avatar-outer">
<view class="avatar-wrap {{member.isVip ? 'vip-ring' : ''}}">
<image class="avatar-img" wx:if="{{member.avatar}}" src="{{member.avatar}}" mode="aspectFill"/>
<view class="avatar-ph" wx:else><text>{{member.name[0] || '创'}}</text></view>
</view>
<view class="vip-tag" wx:if="{{member.isVip}}">VIP</view>
</view>
<text class="profile-name">{{member.name}}</text>
<view class="profile-tags" wx:if="{{member.mbti || member.region}}">
<text class="tag tag-mbti" wx:if="{{member.mbti}}">{{member.mbti}}</text>
<text class="tag tag-region" wx:if="{{member.region}}"><text class="pin-icon">📍</text>{{member.region}}</text>
</view>
</view>
</view>
<!-- 基本信息(未填写行已隐藏) -->
<view class="card" wx:if="{{member.industry || member.position || member.businessScale || member.skills || member.contactRaw || member.contactDisplay || member.wechatRaw || member.wechatDisplay}}">
<view class="card-head">
<text class="card-icon">👤</text>
<text class="card-label">基本信息</text>
</view>
<view class="card-body">
<view class="field" wx:if="{{member.industry}}">
<text class="f-key">行业</text>
<text class="f-val">{{member.industry}}</text>
</view>
<view class="field" wx:if="{{member.position}}">
<text class="f-key">职位</text>
<text class="f-val">{{member.position}}</text>
</view>
<view class="field" wx:if="{{member.businessScale}}">
<text class="f-key">业务体量</text>
<text class="f-val">{{member.businessScale}}</text>
</view>
<view class="divider" wx:if="{{member.industry || member.position || member.businessScale}}"></view>
<view class="field" wx:if="{{member.skills}}">
<text class="f-key">我擅长</text>
<text class="f-val">{{member.skills}}</text>
</view>
<view class="field" wx:if="{{member.contactRaw || member.contactDisplay}}">
<text class="f-key">联系方式</text>
<view class="f-row">
<text class="f-val mono">{{member.contactUnlocked ? member.contactFull : (member.contactDisplay || member.contactRaw)}}</text>
<view class="icon-copy icon-eye-off" wx:if="{{member.contactRaw && !member.contactUnlocked}}" bindtap="unlockField" data-field="contact">
<image class="icon-img" src="/assets/icons/eye-off.svg" mode="aspectFit"/>
</view>
<view class="icon-copy" wx:elif="{{member.contactRaw && member.contactUnlocked}}" bindtap="copyContact">📋</view>
</view>
</view>
<view class="field" wx:if="{{member.wechatRaw || member.wechatDisplay}}">
<text class="f-key">微信号</text>
<view class="f-row">
<text class="f-val mono">{{member.wechatUnlocked ? member.wechatFull : (member.wechatDisplay || member.wechatRaw)}}</text>
<view class="icon-copy icon-eye-off" wx:if="{{member.wechatRaw && !member.wechatUnlocked}}" bindtap="unlockField" data-field="wechat">
<image class="icon-img" src="/assets/icons/eye-off.svg" mode="aspectFit"/>
</view>
<view class="icon-copy" wx:elif="{{member.wechatRaw && member.wechatUnlocked}}" bindtap="copyWechat">📋</view>
</view>
</view>
</view>
</view>
<!-- 个人故事(未填写行已隐藏) -->
<view class="card" wx:if="{{member.bestMonth || member.achievement || member.turningPoint}}">
<view class="card-head">
<text class="card-icon bulb">💡</text>
<text class="card-label">个人故事</text>
</view>
<view class="card-body">
<view class="story" wx:if="{{member.bestMonth}}">
<view class="story-head"><text class="story-icon">🏆</text><text class="story-q">最赚钱的一个月做的是什么</text></view>
<text class="story-a">{{member.bestMonth}}</text>
</view>
<view class="divider" wx:if="{{member.bestMonth}}"></view>
<view class="story" wx:if="{{member.achievement}}">
<view class="story-head"><text class="story-icon">⭐</text><text class="story-q">最有成就感的一件事</text></view>
<text class="story-a">{{member.achievement}}</text>
</view>
<view class="divider" wx:if="{{member.achievement}}"></view>
<view class="story" wx:if="{{member.turningPoint}}">
<view class="story-head"><text class="story-icon turn">🔄</text><text class="story-q">人生的转折点</text></view>
<text class="story-a">{{member.turningPoint}}</text>
</view>
</view>
</view>
<!-- 互助需求(未填写行已隐藏) -->
<view class="card" wx:if="{{member.canHelp || member.needHelp}}">
<view class="card-head">
<text class="card-icon">🤝</text>
<text class="card-label">互助需求</text>
</view>
<view class="card-body">
<view class="help-box help-give" wx:if="{{member.canHelp}}">
<text class="help-tag">我能帮你</text>
<text class="help-txt">{{member.canHelp}}</text>
</view>
<view class="help-box help-need" wx:if="{{member.needHelp}}">
<text class="help-tag need">我需要帮助</text>
<text class="help-txt">{{member.needHelp}}</text>
</view>
</view>
</view>
<!-- 项目介绍 -->
<view class="card" wx:if="{{member.project}}">
<view class="card-head">
<text class="card-icon rocket">🚀</text>
<text class="card-label">项目介绍</text>
</view>
<text class="proj-txt">{{member.project}}</text>
</view>
<!-- 底部按钮 -->
<view class="bottom-wrap">
<view class="btn-super" bindtap="goToVip">
<text>成为超级个体</text>
<text class="btn-arrow">→</text>
</view>
</view>
<view style="height:160rpx;"></view>
</scroll-view>
<!-- 加载和空状态 -->
<view class="state-wrap" wx:if="{{loading}}">
<view class="loading-dot"></view>
<text class="state-txt">加载中...</text>
</view>
<view class="state-wrap" wx:if="{{!loading && !member}}">
<text class="state-emoji">👤</text>
<text class="state-txt">暂无该超级个体信息</text>
</view>
</view>

View File

@@ -0,0 +1,172 @@
/* Soul创业派对 - 个人资料页enhanced_professional_profile 1:1 还原) */
.page { background: #050B14; min-height: 100vh; color: #fff; }
/* 导航栏 */
.nav-bar {
position: fixed; top: 0; left: 0; right: 0; z-index: 999;
display: flex; align-items: center; justify-content: space-between;
padding: 0 32rpx; height: 44px;
background: rgba(5, 11, 20, 0.9);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
border-bottom: 1rpx solid rgba(255, 255, 255, 0.05);
}
.nav-back { width: 80rpx; height: 80rpx; display: flex; align-items: center; justify-content: flex-start; }
.nav-icon { font-size: 44rpx; color: #5EEAD4; font-weight: 300; }
.nav-title { font-size: 34rpx; font-weight: 700; color: #fff; letter-spacing: 2rpx; }
.nav-right { display: flex; align-items: center; gap: 16rpx; }
.nav-icon-wrap { padding: 8rpx; }
.nav-icon-dot { font-size: 28rpx; color: rgba(255,255,255,0.8); }
.scroll-wrap { height: calc(100vh - 88px); }
/* ===== 顶部 Profile 卡片 ===== */
.card-profile {
position: relative; margin: 32rpx 32rpx 0;
padding: 64rpx 40rpx 48rpx;
border-radius: 32rpx;
background: #0F1720;
border: 1rpx solid rgba(255, 255, 255, 0.08);
overflow: hidden;
}
.profile-deco {
position: absolute; top: 0; left: 0; right: 0; height: 128rpx;
background: linear-gradient(180deg, rgba(30, 58, 69, 0.3) 0%, transparent 100%);
pointer-events: none;
}
.profile-body { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; }
.avatar-outer {
position: relative;
width: 176rpx; height: 176rpx;
margin-bottom: 32rpx;
}
.avatar-wrap {
position: relative;
width: 100%; height: 100%;
border-radius: 50%;
overflow: hidden;
border: 2rpx solid rgba(255, 255, 255, 0.1);
box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.4);
}
.avatar-wrap.vip-ring {
border: 4rpx solid transparent;
background: linear-gradient(135deg, #F59E0B, #5EEAD4, #F59E0B);
background-size: 200% 200%;
animation: vipGlow 4s ease infinite;
}
@keyframes vipGlow {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
.avatar-img { width: 100%; height: 100%; object-fit: cover; }
.avatar-ph {
width: 100%; height: 100%;
background: #17212F;
display: flex; align-items: center; justify-content: center;
font-size: 56rpx; color: #5EEAD4; font-weight: 700;
}
.vip-tag {
position: absolute; bottom: -4rpx; right: -4rpx;
background: linear-gradient(135deg, #F59E0B, #e8920d);
color: #000; font-size: 20rpx; font-weight: 800;
padding: 6rpx 14rpx; border-radius: 16rpx;
z-index: 2;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.3);
}
.profile-name { font-size: 40rpx; font-weight: 700; color: #fff; margin-bottom: 24rpx; letter-spacing: 2rpx; }
.profile-tags { display: flex; align-items: center; justify-content: center; gap: 24rpx; flex-wrap: wrap; }
.tag { font-size: 24rpx; font-weight: 500; padding: 8rpx 24rpx; border-radius: 999rpx; }
.tag-mbti { background: #134E4A; color: #5EEAD4; border: 1rpx solid rgba(94, 234, 212, 0.2); }
.tag-region { background: #1F2937; color: #D1D5DB; border: 1rpx solid rgba(255, 255, 255, 0.1); display: flex; align-items: center; gap: 8rpx; }
.pin-icon { color: #EF4444; font-size: 22rpx; }
/* ===== 通用卡片 ===== */
.card {
margin: 32rpx;
padding: 40rpx 40rpx;
border-radius: 32rpx;
background: #0F1720;
border: 1rpx solid rgba(255, 255, 255, 0.08);
}
.card-head { display: flex; align-items: center; gap: 20rpx; margin-bottom: 40rpx; }
.card-icon { font-size: 40rpx; }
.card-icon.bulb { filter: sepia(1) saturate(3) hue-rotate(15deg); }
.card-icon.rocket { opacity: 0.9; }
.card-label { font-size: 30rpx; font-weight: 700; color: #fff; letter-spacing: 1rpx; }
.card-body { }
.field { margin-bottom: 32rpx; }
.field:last-child { margin-bottom: 0; }
.f-key { display: block; font-size: 26rpx; color: #94A3B8; margin-bottom: 12rpx; }
.f-val { font-size: 30rpx; font-weight: 500; color: #fff; line-height: 1.6; }
.f-val.mono { font-family: ui-monospace, monospace; letter-spacing: 2rpx; }
.f-row { display: flex; align-items: center; gap: 16rpx; }
.icon-copy { font-size: 36rpx; color: #94A3B8; opacity: 0.6; padding: 8rpx; }
.icon-eye-off { display: flex; align-items: center; justify-content: center; }
.icon-eye-off .icon-img { width: 40rpx; height: 40rpx; }
.divider { height: 1rpx; background: rgba(255, 255, 255, 0.05); margin: 32rpx 0; }
/* ===== 个人故事 ===== */
.story { margin-bottom: 32rpx; }
.story:last-child { margin-bottom: 0; }
.story-head { display: flex; align-items: center; gap: 12rpx; margin-bottom: 12rpx; }
.story-icon { font-size: 32rpx; }
.story-icon.turn { opacity: 0.9; }
.story-q { font-size: 26rpx; font-weight: 500; color: #94A3B8; }
.story-a { display: block; font-size: 28rpx; color: #E5E7EB; line-height: 1.7; }
/* ===== 互助需求 ===== */
.help-box {
padding: 32rpx;
border-radius: 24rpx;
margin-bottom: 24rpx;
background: #17212F;
border: 1rpx solid rgba(255, 255, 255, 0.05);
}
.help-box:last-child { margin-bottom: 0; }
.help-tag {
display: inline-block;
font-size: 22rpx; font-weight: 600;
padding: 6rpx 16rpx; border-radius: 12rpx;
margin-bottom: 16rpx;
}
.help-give .help-tag { color: #5EEAD4; background: #112D2A; }
.help-need .help-tag { color: #F59E0B; background: #2D1F0D; }
.help-txt { display: block; font-size: 26rpx; color: #fff; line-height: 1.6; letter-spacing: 1rpx; }
/* ===== 项目介绍 ===== */
.proj-txt { font-size: 28rpx; color: #E5E7EB; line-height: 1.7; }
/* ===== 底部按钮 ===== */
.bottom-wrap {
padding: 48rpx 32rpx 0;
}
.btn-super {
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
width: 100%;
padding: 32rpx 0;
border-radius: 999rpx;
background: transparent;
border: 1rpx solid rgba(245, 158, 11, 0.3);
color: #F59E0B;
font-size: 30rpx; font-weight: 500;
letter-spacing: 2rpx;
}
.btn-arrow { font-size: 36rpx; font-weight: 300; }
/* ===== 状态 ===== */
.state-wrap { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 60vh; gap: 24rpx; }
.state-txt { font-size: 28rpx; color: #64748B; }
.state-emoji { font-size: 96rpx; }
.loading-dot {
width: 56rpx; height: 56rpx;
border-radius: 50%;
border: 4rpx solid rgba(94, 234, 212, 0.2);
border-top-color: #5EEAD4;
animation: spin 1s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }