Merge branch 'yongxu-dev' into devlop
# Conflicts: # miniprogram/pages/profile-edit/profile-edit.js # miniprogram/pages/profile-edit/profile-edit.wxml # miniprogram/pages/settings/settings.js # miniprogram/utils/ruleEngine.js # soul-admin/src/pages/distribution/DistributionPage.tsx # soul-admin/src/pages/users/UsersPage.tsx # soul-api/.env.production # soul-api/.gitignore # soul-api/internal/handler/db_ckb_leads.go # soul-api/internal/handler/miniprogram.go # soul-api/internal/handler/referral.go # 开发文档/1、需求/archive/链接人与事-存客宝同步-需求规划.md # 开发文档/1、需求/archive/链接人与事-实现方案.md
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Soul创业派对 - 资料编辑完整版(comprehensive_profile_editor_v1_1)
|
||||
* 卡若创业派对 - 资料编辑完整版(comprehensive_profile_editor_v1_1)
|
||||
* 温馨提示、头像、基本信息、核心联系方式、个人故事、互助需求、项目介绍
|
||||
*
|
||||
* 接口约定(/api/miniprogram/user/profile):
|
||||
@@ -9,6 +9,7 @@
|
||||
* 表单展示:普通用户仅展示 温馨提示、头像、昵称、MBTI、地区、行业、业务体量、职位、核心联系方式;VIP 展示全部
|
||||
*/
|
||||
const app = getApp()
|
||||
const { toAvatarPath } = require('../../utils/util.js')
|
||||
|
||||
const MBTI_OPTIONS = ['INTJ', 'INFP', 'INTP', 'ENTP', 'ENFP', 'ENTJ', 'ENFJ', 'INFJ', 'ISTJ', 'ISFJ', 'ESTJ', 'ESFJ', 'ISTP', 'ISFP', 'ESTP', 'ESFP']
|
||||
|
||||
@@ -18,6 +19,7 @@ Page({
|
||||
isVip: false,
|
||||
avatar: '',
|
||||
nickname: '',
|
||||
shareCardPath: '', // 分享名片封面图(预生成)
|
||||
mbti: '',
|
||||
mbtiIndex: 0,
|
||||
region: '',
|
||||
@@ -37,11 +39,22 @@ Page({
|
||||
showMbtiPicker: false,
|
||||
saving: false,
|
||||
loading: true,
|
||||
showAvatarModal: false,
|
||||
showPrivacyModal: false,
|
||||
nicknameInputFocus: false,
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.setData({ statusBarHeight: app.globalData.statusBarHeight || 44 })
|
||||
onLoad(options) {
|
||||
this.setData({
|
||||
statusBarHeight: app.globalData.statusBarHeight || 44,
|
||||
fromVip: options?.from === 'vip',
|
||||
})
|
||||
wx.showShareMenu({ withShareTimeline: true })
|
||||
// 从朋友圈/分享打开且带 id:跳转到名片详情(member-detail)
|
||||
if (options?.id) {
|
||||
const ref = options.ref ? `&ref=${options.ref}` : ''
|
||||
wx.redirectTo({ url: `/pages/member-detail/member-detail?id=${options.id}${ref}` })
|
||||
return
|
||||
}
|
||||
this.loadProfile()
|
||||
},
|
||||
|
||||
@@ -85,6 +98,7 @@ Page({
|
||||
projectIntro: v('projectIntro'),
|
||||
loading: false,
|
||||
})
|
||||
setTimeout(() => this.generateShareCard(), 200)
|
||||
} else {
|
||||
this.setData({ loading: false })
|
||||
}
|
||||
@@ -95,6 +109,191 @@ Page({
|
||||
|
||||
goBack() { getApp().goBackOrToHome() },
|
||||
|
||||
onNicknameAreaTouch() {
|
||||
if (typeof wx.requirePrivacyAuthorize !== 'function') return
|
||||
wx.requirePrivacyAuthorize({
|
||||
success: () => { this.setData({ nicknameInputFocus: true }) },
|
||||
fail: () => {},
|
||||
})
|
||||
},
|
||||
onNicknameBlur() { this.setData({ nicknameInputFocus: false }) },
|
||||
preventMove() {},
|
||||
handleAgreePrivacy() {
|
||||
if (app._privacyResolve) {
|
||||
app._privacyResolve({ buttonId: 'agree-btn', event: 'agree' })
|
||||
app._privacyResolve = null
|
||||
}
|
||||
this.setData({ showPrivacyModal: false, nicknameInputFocus: true })
|
||||
},
|
||||
handleDisagreePrivacy() {
|
||||
if (app._privacyResolve) {
|
||||
app._privacyResolve({ event: 'disagree' })
|
||||
app._privacyResolve = null
|
||||
}
|
||||
this.setData({ showPrivacyModal: false })
|
||||
},
|
||||
|
||||
// 生成分享名片封面图(参考:头像左+昵称右,分隔线,四栏信息 5:4)
|
||||
async generateShareCard() {
|
||||
const { avatar, nickname, region, mbti, industry, position } = this.data
|
||||
const userId = app.globalData.userInfo?.id
|
||||
if (!userId) return
|
||||
try {
|
||||
const ctx = wx.createCanvasContext('shareCardCanvas', this)
|
||||
const w = 500
|
||||
const h = 400
|
||||
const pad = 32
|
||||
// 背景(深灰卡片感)
|
||||
const grd = ctx.createLinearGradient(0, 0, w, h)
|
||||
grd.addColorStop(0, '#1E293B')
|
||||
grd.addColorStop(1, '#0F172A')
|
||||
ctx.setFillStyle(grd)
|
||||
ctx.fillRect(0, 0, w, h)
|
||||
// 顶部区域:左头像 + 右昵称
|
||||
const avatarSize = 100
|
||||
const avatarX = pad + 10
|
||||
const avatarY = 50
|
||||
const avatarRadius = avatarSize / 2
|
||||
const rightStart = avatarX + avatarSize + 28
|
||||
const drawAvatar = () => new Promise((resolve) => {
|
||||
if (avatar && avatar.startsWith('http')) {
|
||||
wx.downloadFile({
|
||||
url: avatar,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
ctx.save()
|
||||
ctx.beginPath()
|
||||
ctx.arc(avatarX + avatarRadius, avatarY + avatarRadius, avatarRadius, 0, Math.PI * 2)
|
||||
ctx.clip()
|
||||
ctx.drawImage(res.tempFilePath, avatarX, avatarY, avatarSize, avatarSize)
|
||||
ctx.restore()
|
||||
} else {
|
||||
this.drawAvatarPlaceholder(ctx, avatarX, avatarY, avatarSize, nickname)
|
||||
}
|
||||
resolve()
|
||||
},
|
||||
fail: () => {
|
||||
this.drawAvatarPlaceholder(ctx, avatarX, avatarY, avatarSize, nickname)
|
||||
resolve()
|
||||
},
|
||||
})
|
||||
} else {
|
||||
this.drawAvatarPlaceholder(ctx, avatarX, avatarY, avatarSize, nickname)
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
await drawAvatar()
|
||||
ctx.setStrokeStyle('rgba(94,234,212,0.5)')
|
||||
ctx.setLineWidth(2)
|
||||
ctx.beginPath()
|
||||
ctx.arc(avatarX + avatarRadius, avatarY + avatarRadius, avatarRadius, 0, Math.PI * 2)
|
||||
ctx.stroke()
|
||||
// 右侧:昵称 + 个人名片
|
||||
const displayName = (nickname || '').trim() || '创业者'
|
||||
ctx.setFillStyle('#ffffff')
|
||||
ctx.setFontSize(26)
|
||||
ctx.setTextAlign('left')
|
||||
ctx.fillText(displayName, rightStart, avatarY + 36)
|
||||
ctx.setFillStyle('#94A3B8')
|
||||
ctx.setFontSize(13)
|
||||
ctx.fillText('个人名片', rightStart, avatarY + 62)
|
||||
// 分隔线
|
||||
const divY = 168
|
||||
ctx.setStrokeStyle('rgba(255,255,255,0.08)')
|
||||
ctx.setLineWidth(1)
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(pad, divY)
|
||||
ctx.lineTo(w - pad, divY)
|
||||
ctx.stroke()
|
||||
// 底部四栏:地区 | MBTI,行业 | 职位
|
||||
const labelGray = '#64748B'
|
||||
const valueWhite = '#F1F5F9'
|
||||
const rowH = 52
|
||||
const colW = (w - pad * 2) / 2
|
||||
const truncate = (text, maxW) => {
|
||||
if (!text) return ''
|
||||
ctx.setFontSize(15)
|
||||
let m = ctx.measureText(text)
|
||||
if (m.width <= maxW) return text
|
||||
for (let i = text.length - 1; i > 0; i--) {
|
||||
const t = text.slice(0, i) + '…'
|
||||
if (ctx.measureText(t).width <= maxW) return t
|
||||
}
|
||||
return text[0] + '…'
|
||||
}
|
||||
const maxValW = colW - 16
|
||||
const items = [
|
||||
{ label: '地区', value: truncate((region || '').trim() || '未填写', maxValW), x: pad },
|
||||
{ label: 'MBTI', value: (mbti || '').trim() || '未填写', x: pad + colW },
|
||||
{ label: '行业', value: truncate((industry || '').trim() || '未填写', maxValW), x: pad },
|
||||
{ label: '职位', value: truncate((position || '').trim() || '未填写', maxValW), x: pad + colW },
|
||||
]
|
||||
items.forEach((item, i) => {
|
||||
const row = Math.floor(i / 2)
|
||||
const baseY = divY + 36 + row * rowH
|
||||
ctx.setFillStyle(labelGray)
|
||||
ctx.setFontSize(12)
|
||||
ctx.fillText(item.label, item.x, baseY - 8)
|
||||
ctx.setFillStyle(valueWhite)
|
||||
ctx.setFontSize(15)
|
||||
ctx.fillText(item.value, item.x, baseY + 14)
|
||||
})
|
||||
ctx.draw(true, () => {
|
||||
wx.canvasToTempFilePath({
|
||||
canvasId: 'shareCardCanvas',
|
||||
destWidth: 500,
|
||||
destHeight: 400,
|
||||
success: (res) => {
|
||||
this.setData({ shareCardPath: res.tempFilePath })
|
||||
},
|
||||
}, this)
|
||||
})
|
||||
} catch (e) {
|
||||
console.warn('[ShareCard] 生成失败:', e)
|
||||
}
|
||||
},
|
||||
|
||||
drawAvatarPlaceholder(ctx, x, y, size, nickname) {
|
||||
ctx.setFillStyle('rgba(94,234,212,0.2)')
|
||||
ctx.beginPath()
|
||||
ctx.arc(x + size / 2, y + size / 2, size / 2, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
ctx.setFillStyle('#5EEAD4')
|
||||
ctx.setFontSize(size * 0.42)
|
||||
ctx.setTextAlign('center')
|
||||
ctx.fillText((nickname || '?')[0], x + size / 2, y + size / 2 + size * 0.14)
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
const ref = app.getMyReferralCode()
|
||||
const userId = app.globalData.userInfo?.id
|
||||
const nickname = (this.data.nickname || '').trim() || '我'
|
||||
const path = userId
|
||||
? (ref ? `/pages/member-detail/member-detail?id=${userId}&ref=${ref}` : `/pages/member-detail/member-detail?id=${userId}`)
|
||||
: (ref ? `/pages/profile-edit/profile-edit?ref=${ref}` : '/pages/profile-edit/profile-edit')
|
||||
const result = {
|
||||
title: `${nickname}为您分享名片`,
|
||||
path,
|
||||
}
|
||||
if (this.data.shareCardPath) result.imageUrl = this.data.shareCardPath
|
||||
return result
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
const ref = app.getMyReferralCode()
|
||||
const userId = app.globalData.userInfo?.id
|
||||
const nickname = (this.data.nickname || '').trim() || '我'
|
||||
const query = userId
|
||||
? (ref ? `id=${userId}&ref=${ref}` : `id=${userId}`)
|
||||
: (ref ? `ref=${ref}` : '')
|
||||
const result = {
|
||||
title: `${nickname}为您分享名片`,
|
||||
query: query || '',
|
||||
}
|
||||
if (this.data.shareCardPath) result.imageUrl = this.data.shareCardPath
|
||||
return result
|
||||
},
|
||||
|
||||
onNicknameInput(e) { this.setData({ nickname: e.detail.value }) },
|
||||
onNicknameChange(e) { this.setData({ nickname: e.detail.value }) },
|
||||
onRegionInput(e) { this.setData({ region: e.detail.value }) },
|
||||
@@ -116,76 +315,9 @@ Page({
|
||||
this.setData({ mbtiIndex: i, mbti: MBTI_OPTIONS[i] })
|
||||
},
|
||||
|
||||
// 点击头像:选择微信头像或从相册选择
|
||||
onAvatarTap() {
|
||||
wx.showActionSheet({
|
||||
itemList: ['使用微信头像', '从相册选择'],
|
||||
success: (res) => {
|
||||
if (res.tapIndex === 0) {
|
||||
this.setData({ showAvatarModal: true })
|
||||
} else if (res.tapIndex === 1) {
|
||||
this.chooseAvatarFromAlbum()
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
closeAvatarModal() {
|
||||
this.setData({ showAvatarModal: false })
|
||||
},
|
||||
|
||||
// 从相册/相机选择头像
|
||||
chooseAvatarFromAlbum() {
|
||||
wx.chooseMedia({
|
||||
count: 1,
|
||||
mediaType: ['image'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: async (res) => {
|
||||
const tempPath = res.tempFiles[0].tempFilePath
|
||||
wx.showLoading({ title: '上传中...', mask: true })
|
||||
try {
|
||||
const uploadRes = await new Promise((resolve, reject) => {
|
||||
wx.uploadFile({
|
||||
url: app.globalData.baseUrl + '/api/miniprogram/upload',
|
||||
filePath: tempPath,
|
||||
name: 'file',
|
||||
formData: { folder: 'avatars' },
|
||||
success: (r) => {
|
||||
try {
|
||||
const data = JSON.parse(r.data)
|
||||
if (data.success) resolve(data)
|
||||
else reject(new Error(data.error || '上传失败'))
|
||||
} catch { reject(new Error('解析失败')) }
|
||||
},
|
||||
fail: reject,
|
||||
})
|
||||
})
|
||||
const rawUrl1 = uploadRes.data.url || ''
|
||||
const avatarUrl = rawUrl1.startsWith('http://') || rawUrl1.startsWith('https://') ? rawUrl1 : app.globalData.baseUrl + rawUrl1
|
||||
this.setData({ avatar: avatarUrl })
|
||||
await app.request({
|
||||
url: '/api/miniprogram/user/profile',
|
||||
method: 'POST',
|
||||
data: { userId: app.globalData.userInfo?.id, avatar: avatarUrl },
|
||||
})
|
||||
if (app.globalData.userInfo) {
|
||||
app.globalData.userInfo.avatar = avatarUrl
|
||||
wx.setStorageSync('userInfo', app.globalData.userInfo)
|
||||
}
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '头像已更新', icon: 'success' })
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: e.message || '上传失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
// 微信原生 chooseAvatar 回调:使用当前微信头像
|
||||
// 微信原生 chooseAvatar 回调(点击头像直接弹出原生选择器:用微信头像/从相册选择/拍照)
|
||||
async onChooseAvatar(e) {
|
||||
const tempAvatarUrl = e.detail?.avatarUrl
|
||||
this.setData({ showAvatarModal: false })
|
||||
if (!tempAvatarUrl) return
|
||||
wx.showLoading({ title: '上传中...', mask: true })
|
||||
|
||||
@@ -209,13 +341,16 @@ Page({
|
||||
})
|
||||
})
|
||||
|
||||
const rawUrl2 = uploadRes.data.url || ''
|
||||
const avatarUrl = rawUrl2.startsWith('http://') || rawUrl2.startsWith('https://') ? rawUrl2 : app.globalData.baseUrl + rawUrl2
|
||||
let avatarUrl = uploadRes.data?.url || uploadRes.url
|
||||
if (avatarUrl && !avatarUrl.startsWith('http')) {
|
||||
avatarUrl = app.globalData.baseUrl + avatarUrl
|
||||
}
|
||||
this.setData({ avatar: avatarUrl })
|
||||
const avatarToSave = toAvatarPath(avatarUrl)
|
||||
await app.request({
|
||||
url: '/api/miniprogram/user/profile',
|
||||
method: 'POST',
|
||||
data: { userId: app.globalData.userInfo?.id, avatar: avatarUrl },
|
||||
data: { userId: app.globalData.userInfo?.id, avatar: avatarToSave },
|
||||
})
|
||||
if (app.globalData.userInfo) {
|
||||
app.globalData.userInfo.avatar = avatarUrl
|
||||
@@ -223,6 +358,7 @@ Page({
|
||||
}
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '头像已更新', icon: 'success' })
|
||||
setTimeout(() => this.generateShareCard(), 200)
|
||||
} catch (err) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: err.message || '上传失败,请重试', icon: 'none' })
|
||||
@@ -235,20 +371,32 @@ Page({
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const s = (v) => (v || '').toString().trim()
|
||||
const isVip = this.data.isVip
|
||||
// 手机号必填,格式校验(支持带空格/连字符输入)
|
||||
const phoneRaw = s(this.data.phone)
|
||||
if (!phoneRaw) {
|
||||
wx.showToast({ title: '请输入手机号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const phoneNum = phoneRaw.replace(/\D/g, '')
|
||||
if (!/^1[3-9]\d{9}$/.test(phoneNum)) {
|
||||
wx.showToast({ title: '请输入正确的11位手机号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const phoneToSave = phoneNum
|
||||
this.setData({ saving: true })
|
||||
try {
|
||||
const s = (v) => (v || '').toString().trim()
|
||||
const isVip = this.data.isVip
|
||||
const payload = {
|
||||
userId,
|
||||
avatar: s(this.data.avatar),
|
||||
avatar: toAvatarPath(s(this.data.avatar)),
|
||||
nickname: s(this.data.nickname),
|
||||
mbti: s(this.data.mbti),
|
||||
region: s(this.data.region),
|
||||
industry: s(this.data.industry),
|
||||
businessScale: s(this.data.businessScale),
|
||||
position: s(this.data.position),
|
||||
phone: s(this.data.phone),
|
||||
phone: phoneToSave,
|
||||
wechatId: s(this.data.wechatId),
|
||||
}
|
||||
const showHelp = isVip || this.data.helpOffer || this.data.helpNeed
|
||||
@@ -271,7 +419,7 @@ Page({
|
||||
this.setData({ saving: false })
|
||||
return
|
||||
}
|
||||
await app.request({
|
||||
const res = await app.request({
|
||||
url: '/api/miniprogram/user/profile',
|
||||
method: 'POST',
|
||||
data: payload,
|
||||
@@ -279,7 +427,7 @@ Page({
|
||||
wx.showToast({ title: '保存成功', icon: 'success' })
|
||||
if (app.globalData.userInfo) {
|
||||
if (payload.nickname) app.globalData.userInfo.nickname = payload.nickname
|
||||
if (payload.avatar) app.globalData.userInfo.avatar = payload.avatar
|
||||
if (res?.data?.avatar) app.globalData.userInfo.avatar = res.data.avatar
|
||||
wx.setStorageSync('userInfo', app.globalData.userInfo)
|
||||
}
|
||||
setTimeout(() => getApp().goBackOrToHome(), 800)
|
||||
|
||||
@@ -9,36 +9,39 @@
|
||||
|
||||
<view class="loading" wx:if="{{loading}}">加载中...</view>
|
||||
<scroll-view wx:else class="scroll-main" scroll-y>
|
||||
<!-- 温馨提示 -->
|
||||
<view class="tip-card">
|
||||
<!-- 温馨提示:from=vip 时强化权益说明 -->
|
||||
<view class="tip-card {{fromVip ? 'tip-card-highlight' : ''}}">
|
||||
<icon name="info" size="36" color="#00CED1" customClass="tip-icon"></icon>
|
||||
<text class="tip-text">温馨提示:需完善手机号和微信号才能使用提现和找伙伴功能</text>
|
||||
<text class="tip-text">{{fromVip ? '恭喜成为VIP!完善资料后即可使用找伙伴、提现等功能,手机号必填' : '温馨提示:手机号必填,微信号建议填写,以便使用提现和找伙伴功能'}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 头像 -->
|
||||
<!-- 头像:点击直接弹出微信原生选择器(用微信头像/从相册选择/拍照) -->
|
||||
<view class="avatar-section">
|
||||
<view class="avatar-wrap" bindtap="onAvatarTap">
|
||||
<view class="avatar-inner">
|
||||
<image wx:if="{{avatar}}" class="avatar-img" src="{{avatar}}" mode="aspectFill"/>
|
||||
<view wx:else class="avatar-placeholder">{{nickname ? nickname[0] : '?'}}</view>
|
||||
<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"/>
|
||||
<view wx:else class="avatar-placeholder">{{nickname ? nickname[0] : '?'}}</view>
|
||||
</view>
|
||||
<view class="avatar-camera"><icon name="camera" size="48" color="#ffffff"></icon></view>
|
||||
</view>
|
||||
<view class="avatar-camera"><icon name="camera" size="48" color="#ffffff"></icon></view>
|
||||
</view>
|
||||
<text class="avatar-change">更换头像</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<view class="section">
|
||||
<view class="form-row">
|
||||
<text class="form-label">昵称</text>
|
||||
<view class="form-input-wrap">
|
||||
<view class="form-input-wrap" catchtouchstart="onNicknameAreaTouch">
|
||||
<input
|
||||
class="form-input-inner"
|
||||
type="nickname"
|
||||
placeholder="请输入昵称"
|
||||
value="{{nickname}}"
|
||||
focus="{{nicknameInputFocus}}"
|
||||
bindinput="onNicknameInput"
|
||||
bindchange="onNicknameChange"
|
||||
bindblur="onNicknameBlur"
|
||||
maxlength="20"
|
||||
/>
|
||||
</view>
|
||||
@@ -84,8 +87,8 @@
|
||||
<text>核心联系方式</text>
|
||||
</view>
|
||||
<view class="form-row">
|
||||
<text class="form-label">手机号</text>
|
||||
<view class="form-input-wrap"><input class="form-input-inner" type="tel" placeholder="请输入手机号" value="{{phone}}" bindinput="onPhoneInput"/></view>
|
||||
<text class="form-label">手机号<text class="required-mark">*</text></text>
|
||||
<view class="form-input-wrap"><input class="form-input-inner" type="tel" placeholder="请输入手机号(必填)" value="{{phone}}" bindinput="onPhoneInput"/></view>
|
||||
</view>
|
||||
<view class="form-row">
|
||||
<text class="form-label">微信号</text>
|
||||
@@ -146,14 +149,16 @@
|
||||
<view class="bottom-space"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 头像弹窗:通过 button 获取微信头像 -->
|
||||
<view class="modal-overlay" wx:if="{{showAvatarModal}}" bindtap="closeAvatarModal">
|
||||
<view class="modal-content avatar-modal" catchtap="stopPropagation">
|
||||
<view class="modal-close" bindtap="closeAvatarModal"><icon name="x" size="36" color="#8e8e93"></icon></view>
|
||||
<text class="avatar-modal-title">使用微信头像</text>
|
||||
<text class="avatar-modal-desc">点击下方按钮,一键同步当前微信头像</text>
|
||||
<button class="btn-choose-avatar" open-type="chooseAvatar" bindchooseavatar="onChooseAvatar">使用微信头像</button>
|
||||
<view class="avatar-modal-cancel" bindtap="closeAvatarModal">取消</view>
|
||||
<!-- 分享名片 canvas(隐藏,用于生成分享图 5:4) -->
|
||||
<canvas canvas-id="shareCardCanvas" class="share-card-canvas" style="width: 500px; height: 400px;"></canvas>
|
||||
|
||||
<!-- 隐私授权弹窗(昵称需授权后方可唤起微信昵称选择器) -->
|
||||
<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-btn" class="privacy-btn" open-type="agreePrivacyAuthorization" bindagreeprivacyauthorization="handleAgreePrivacy">同意</button>
|
||||
<view class="privacy-cancel" bindtap="handleDisagreePrivacy">拒绝</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -26,10 +26,20 @@
|
||||
background: rgba(94,234,212,0.08); border: 1rpx solid rgba(94,234,212,0.25);
|
||||
border-radius: 24rpx; margin-bottom: 48rpx;
|
||||
}
|
||||
.tip-card-highlight {
|
||||
background: rgba(94,234,212,0.12); border-color: rgba(94,234,212,0.4);
|
||||
}
|
||||
.tip-icon { font-size: 40rpx; color: #5EEAD4; flex-shrink: 0; }
|
||||
.tip-text { font-size: 26rpx; color: rgba(94,234,212,0.95); line-height: 1.6; }
|
||||
|
||||
.avatar-section { display: flex; flex-direction: column; align-items: center; margin-bottom: 48rpx; }
|
||||
/* 头像按钮:透明无边框,点击直接弹出微信原生选择器 */
|
||||
.avatar-wrap-btn {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
padding: 0; margin: 0; background: transparent; border: none;
|
||||
width: 192rpx; height: 192rpx; border-radius: 50%; overflow: visible;
|
||||
}
|
||||
.avatar-wrap-btn::after { border: none; }
|
||||
.avatar-wrap {
|
||||
position: relative; width: 192rpx; height: 192rpx; border-radius: 50%;
|
||||
border: 4rpx solid #5EEAD4; box-shadow: 0 0 30rpx rgba(94,234,212,0.3);
|
||||
@@ -62,6 +72,7 @@
|
||||
.form-row-2 { display: flex; gap: 24rpx; }
|
||||
.form-row-2 .form-item { flex: 1; min-width: 0; }
|
||||
.form-label { display: block; font-size: 24rpx; color: #94A3B8; margin-bottom: 12rpx; margin-left: 8rpx; }
|
||||
.required-mark { color: #F87171; margin-left: 4rpx; }
|
||||
|
||||
/* input/textarea 用 view 包裹,padding 写在 view 上 */
|
||||
.form-input-wrap {
|
||||
@@ -70,10 +81,37 @@
|
||||
border-radius: 24rpx;
|
||||
box-sizing: border-box; min-width: 0; width: 100%;
|
||||
}
|
||||
.form-input-suffix { position: relative; padding-right: 64rpx; }
|
||||
/* 地区等带后缀图标的输入框:与 MBTI 同高,图标垂直居中 */
|
||||
.form-input-suffix {
|
||||
position: relative;
|
||||
padding-right: 64rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 88rpx;
|
||||
}
|
||||
.form-input-suffix .form-input-inner {
|
||||
flex: 1;
|
||||
min-height: 40rpx;
|
||||
line-height: 40rpx;
|
||||
}
|
||||
.form-input-suffix .form-suffix {
|
||||
position: absolute; right: 24rpx; top: 50%; transform: translateY(-50%);
|
||||
font-size: 32rpx; color: #94A3B8;
|
||||
position: absolute;
|
||||
right: 24rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 32rpx;
|
||||
color: #94A3B8;
|
||||
pointer-events: none;
|
||||
}
|
||||
/* form-row-2 下两列统一最小高度,与 MBTI 一致 */
|
||||
.form-row-2 .form-input-wrap,
|
||||
.form-row-2 .form-picker {
|
||||
min-height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.form-row-2 .form-picker {
|
||||
color: #fff;
|
||||
}
|
||||
.form-input-inner {
|
||||
width: 100%; max-width: 100%; font-size: 28rpx; color: #fff; background: transparent;
|
||||
@@ -175,9 +213,123 @@
|
||||
.btn-choose-avatar::after {
|
||||
border: none;
|
||||
}
|
||||
.btn-choose-album {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
margin-top: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(94, 234, 212, 0.15);
|
||||
color: #5EEAD4;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 44rpx;
|
||||
border: 2rpx solid #5EEAD4;
|
||||
}
|
||||
.avatar-modal-cancel {
|
||||
margin-top: 24rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
/* 昵称隐私弹窗 */
|
||||
.privacy-mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.privacy-modal {
|
||||
width: 560rpx;
|
||||
padding: 48rpx 40rpx;
|
||||
background: #1E293B;
|
||||
border-radius: 24rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.privacy-modal .privacy-title { font-size: 34rpx; font-weight: 600; color: #fff; margin-bottom: 24rpx; }
|
||||
.privacy-modal .privacy-desc { font-size: 28rpx; color: #94A3B8; line-height: 1.6; margin-bottom: 40rpx; }
|
||||
.privacy-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
background: #5EEAD4;
|
||||
color: #0F172A;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 44rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 隐私授权弹窗 */
|
||||
.privacy-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.privacy-modal {
|
||||
width: 580rpx;
|
||||
background: #1E293B;
|
||||
border-radius: 24rpx;
|
||||
padding: 40rpx 32rpx 32rpx;
|
||||
}
|
||||
.privacy-modal-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #F8FAFC;
|
||||
margin-bottom: 24rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.privacy-modal-desc {
|
||||
font-size: 28rpx;
|
||||
color: #94A3B8;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
.privacy-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #5EEAD4;
|
||||
color: #0F172A;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 44rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.privacy-btn:last-of-type {
|
||||
margin-bottom: 0;
|
||||
background: transparent;
|
||||
color: #94A3B8;
|
||||
border: 2rpx solid #475569;
|
||||
}
|
||||
|
||||
/* 昵称隐私授权弹窗(解决 errno:104) */
|
||||
.privacy-mask {
|
||||
position: fixed; inset: 0; background: rgba(0,0,0,0.7); z-index: 9999;
|
||||
display: flex; align-items: center; justify-content: center; padding: 48rpx;
|
||||
}
|
||||
.privacy-modal {
|
||||
width: 100%; max-width: 600rpx; background: #1E293B; border-radius: 24rpx; padding: 48rpx;
|
||||
}
|
||||
.privacy-title { font-size: 36rpx; font-weight: 700; display: block; margin-bottom: 16rpx; }
|
||||
.privacy-desc { font-size: 28rpx; color: #94A3B8; line-height: 1.5; display: block; margin-bottom: 32rpx; }
|
||||
.privacy-btn { width: 100%; height: 88rpx; line-height: 88rpx; background: #5EEAD4; color: #050B14; font-size: 30rpx; font-weight: 600; border-radius: 44rpx; border: none; }
|
||||
.privacy-btn::after { border: none; }
|
||||
.privacy-cancel { text-align: center; margin-top: 24rpx; font-size: 28rpx; color: #94A3B8; }
|
||||
|
||||
Reference in New Issue
Block a user