diff --git a/app.js b/app.js index 2c5c3b4..eec3c68 100644 --- a/app.js +++ b/app.js @@ -488,7 +488,15 @@ App({ console.log('从query获取推荐码(ref):', referralCode) } - if (!referralCode && options.scene) { + const queryScene = options && options.query ? options.query.scene : null + if (!referralCode && queryScene) { + referralCode = this.parseSceneReferralCode(queryScene) + if (referralCode) { + console.log('从query.scene获取推荐码:', referralCode) + } + } + + if (!referralCode && typeof options.scene === 'string') { referralCode = this.parseSceneReferralCode(options.scene) if (referralCode) { console.log('从scene获取推荐码:', referralCode) @@ -521,7 +529,7 @@ App({ try { const decoded = decodeURIComponent(scene) - const match = decoded.match(/r=([A-Z0-9]+)/) + const match = decoded.match(/r=([A-Za-z0-9]+)/) return match ? match[1] : null } catch (error) { console.error('解析scene失败:', error) @@ -534,10 +542,12 @@ App({ * 用户转发小程序时的默认配置 */ onShareAppMessage() { + const referralCode = wx.getStorageSync('referralCode') || '' + const path = referralCode ? `/pages/index/index?referralCode=${referralCode}` : '/pages/index/index' return { title: '欢迎来到心伴俱乐部', desc: '随时可聊 一直陪伴', - path: '/pages/index/index', + path, imageUrl: '/images/icon-heart-new.png' } }, @@ -546,9 +556,12 @@ App({ * 全局分享到朋友圈配置 */ onShareTimeline() { + const referralCode = wx.getStorageSync('referralCode') || '' + const query = referralCode ? `referralCode=${referralCode}` : '' return { title: '欢迎来到心伴俱乐部 - 随时可聊 一直陪伴', - imageUrl: '/images/icon-heart-new.png' + imageUrl: '/images/icon-heart-new.png', + query } } }) diff --git a/app.json b/app.json index fe8181e..228261e 100644 --- a/app.json +++ b/app.json @@ -27,6 +27,7 @@ "pages/edit-profile/edit-profile", "pages/customer-management/customer-management", "pages/withdraw/withdraw", + "pages/certification/certification", "pages/commission/commission", "pages/promote/promote", "pages/backpack/backpack", diff --git a/config/index.js b/config/index.js index 020486a..b6e54ef 100644 --- a/config/index.js +++ b/config/index.js @@ -24,7 +24,8 @@ const ENV = { API_BASE_URL: 'https://ai-c.maimanji.com/api', WS_URL: 'wss://ai-c.maimanji.com', DEBUG: false, - TEST_MODE: false // 关闭测试模式,使用真实微信支付(后端测试支付接口有数据库错误) + TEST_MODE: false, // 关闭测试模式,使用真实微信支付(后端测试支付接口有数据库错误) + PAYMENT_CHANNEL: 'huifu' // 支付渠道:huifu=汇付天下, wechat=官方微信支付 } } diff --git a/images/certification/camera.svg b/images/certification/camera.svg new file mode 100644 index 0000000..9ccbfbb --- /dev/null +++ b/images/certification/camera.svg @@ -0,0 +1,3 @@ + + + diff --git a/pages/certification/certification.js b/pages/certification/certification.js new file mode 100644 index 0000000..8dd752f --- /dev/null +++ b/pages/certification/certification.js @@ -0,0 +1,151 @@ +const { request, uploadFile, getBaseUrl } = require('../../utils_new/request'); + +Page({ + data: { + statusBarHeight: 20, + navBarHeight: 44, + totalNavHeight: 64, + realName: '', + phone: '', + idCardNumber: '', + idCardFront: '', + submitting: false + }, + + onLoad() { + const sys = wx.getSystemInfoSync(); + const menu = wx.getMenuButtonBoundingClientRect(); + const statusBarHeight = sys.statusBarHeight || 20; + const navBarHeight = menu.height + (menu.top - statusBarHeight) * 2; + this.setData({ + statusBarHeight, + navBarHeight, + totalNavHeight: statusBarHeight + navBarHeight + }); + this.loadProfile(); + }, + + async loadProfile() { + try { + const res = await request({ url: '/api/user/certification', method: 'GET' }); + if (res.data && res.data.success && res.data.data) { + const { realName, phone, idCardFront, idCardNumber } = res.data.data; + this.setData({ + realName: realName || '', + phone: phone || '', + idCardNumber: idCardNumber || '', + idCardFront: idCardFront || '' + }); + } + } catch (err) { + console.error('Load certification info failed', err); + } + }, + + onBack() { + wx.navigateBack(); + }, + + onInput(e) { + const field = e.currentTarget.dataset.field; + this.setData({ + [field]: e.detail.value + }); + }, + + chooseImage() { + wx.chooseMedia({ + count: 1, + mediaType: ['image'], + sourceType: ['album', 'camera'], + success: async (res) => { + const tempFilePath = res.tempFiles[0].tempFilePath; + this.setData({ idCardFront: tempFilePath }); + + try { + wx.showLoading({ title: '上传中...' }); + const uploadRes = await uploadFile({ + url: '/api/upload', + filePath: tempFilePath, + name: 'file', + formData: { type: 'certification' } + }); + + if (uploadRes && uploadRes.data) { + let data = uploadRes.data; + if (typeof data === 'string') { + try { + data = JSON.parse(data); + } catch (e) { + console.error('Parse upload response failed:', e); + } + } + + if (data.code === 0 || data.success) { + const url = data.data.url; + // 如果返回的是相对路径,需要拼接域名 + const fullUrl = url.startsWith('http') ? url : getBaseUrl() + url; + this.setData({ idCardFront: fullUrl }); + } else { + wx.showToast({ title: '上传失败: ' + (data.message || data.error || '未知错误'), icon: 'none' }); + } + } + } catch (err) { + console.error('Upload failed', err); + wx.showToast({ title: '上传失败,请重试', icon: 'none' }); + } finally { + wx.hideLoading(); + } + } + }); + }, + + async submit() { + const { realName, phone, idCardNumber, idCardFront } = this.data; + + if (!realName.trim()) { + wx.showToast({ title: '请输入真实姓名', icon: 'none' }); + return; + } + if (!phone.trim()) { + wx.showToast({ title: '请输入手机号码', icon: 'none' }); + return; + } + if (!idCardNumber.trim()) { + wx.showToast({ title: '请输入身份证号', icon: 'none' }); + return; + } + if (!idCardFront) { + wx.showToast({ title: '请上传身份证正面', icon: 'none' }); + return; + } + + this.setData({ submitting: true }); + + try { + const res = await request({ + url: '/api/user/certification', + method: 'POST', + data: { + realName, + phone, + idCardNumber, + idCardFront + } + }); + + if (res.data && res.data.success) { + wx.showToast({ title: '提交成功', icon: 'success' }); + setTimeout(() => { + wx.navigateBack(); + }, 1500); + } else { + throw new Error(res.data?.error || '提交失败'); + } + } catch (err) { + wx.showToast({ title: err.message || '提交失败', icon: 'none' }); + } finally { + this.setData({ submitting: false }); + } + } +}); diff --git a/pages/certification/certification.json b/pages/certification/certification.json new file mode 100644 index 0000000..0b8c0ec --- /dev/null +++ b/pages/certification/certification.json @@ -0,0 +1,6 @@ +{ + "navigationBarTitleText": "实名认证", + "usingComponents": { + "app-icon": "/components/icon/icon" + } +} diff --git a/pages/certification/certification.wxml b/pages/certification/certification.wxml new file mode 100644 index 0000000..95560f3 --- /dev/null +++ b/pages/certification/certification.wxml @@ -0,0 +1,91 @@ + + + + + + 返回 + + 实名认证 + + + + + + + + 真实姓名 + + + + + + + + 手机号码 + + + + + + + + 身份证号 + + + + + + + + 身份证正面 + + + + + + + 点击上传身份证人像面 + + + + + + + + + + + + + 我们严格保护您的隐私安全,实名信息仅用于提现身份核验,不会泄露给任何第三方。 + + + + diff --git a/pages/certification/certification.wxss b/pages/certification/certification.wxss new file mode 100644 index 0000000..4f67bac --- /dev/null +++ b/pages/certification/certification.wxss @@ -0,0 +1,170 @@ +.page { + min-height: 100vh; + background: linear-gradient(180deg, #F8F5FF 0%, #FFFFFF 100%); + padding-bottom: env(safe-area-inset-bottom); +} + +.unified-header { + position: fixed; + top: 0; + left: 0; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + padding: 0 32rpx; + background-color: transparent; + z-index: 100; + box-sizing: border-box; +} + +.unified-header-left { + display: flex; + align-items: center; + position: absolute; + left: 32rpx; + bottom: 0; + z-index: 101; +} + +.unified-header-title { + font-size: 34rpx; + font-weight: 600; + color: #333; + text-align: center; + position: absolute; + bottom: 0; + left: 0; + right: 0; + pointer-events: none; +} + +.container { + padding: 32rpx; + display: flex; + flex-direction: column; + gap: 32rpx; +} + +.form-group { + display: flex; + flex-direction: column; + gap: 16rpx; +} + +.label { + font-family: Arial; + font-weight: 700; + font-size: 36rpx; + color: #364153; +} + +.input-wrapper { + background-color: #F5F7FA; + border-radius: 28rpx; + padding: 24rpx 32rpx; + display: flex; + align-items: center; +} + +.input { + flex: 1; + font-size: 36rpx; + color: #333; + height: 48rpx; + line-height: 48rpx; +} + +.placeholder { + color: rgba(10, 10, 10, 0.5); + font-size: 36rpx; +} + +.upload-box { + background-color: #F5F7FA; + border: 2rpx dashed #D1D5DC; + border-radius: 28rpx; + height: 340rpx; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; + position: relative; +} + +.upload-placeholder { + display: flex; + flex-direction: column; + align-items: center; + gap: 24rpx; +} + +.icon-plus { + width: 128rpx; + height: 128rpx; + background-color: #FFFFFF; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1); +} + +.camera-icon { + width: 64rpx; + height: 64rpx; +} + +.upload-text { + font-size: 32rpx; + color: #6A7282; +} + +.preview-image { + width: 100%; + height: 100%; +} + +.submit-btn-wrapper { + margin-top: 32rpx; +} + +.submit-btn { + width: 100%; + height: 104rpx; + border-radius: 52rpx; + background: linear-gradient(135deg, #B06AB3 0%, #9B4D9E 100%); + color: #ffffff; + font-size: 36rpx; + font-weight: 900; + box-shadow: 0 20rpx 40rpx rgba(176, 106, 179, 0.3); + display: flex; + align-items: center; + justify-content: center; + margin-top: 64rpx; + letter-spacing: 2rpx; + transition: opacity 0.3s; +} + +.submit-btn[disabled] { + opacity: 0.6; + box-shadow: none; + background: linear-gradient(135deg, #B06AB3 0%, #9B4D9E 100%); +} + +.privacy-note { + background-color: #F9FAFB; + border-radius: 28rpx; + padding: 32rpx; + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 16rpx; +} + +.privacy-text { + flex: 1; + font-size: 28rpx; + color: #99A1AF; + line-height: 1.5; +} diff --git a/pages/chat-detail/chat-detail.js b/pages/chat-detail/chat-detail.js index aca181b..c76639c 100644 --- a/pages/chat-detail/chat-detail.js +++ b/pages/chat-detail/chat-detail.js @@ -838,7 +838,7 @@ Page({ // 根据消息类型添加额外字段 if (msg.message_type === 'image' && msg.image_url) { - baseMessage.imageUrl = msg.image_url + baseMessage.imageUrl = imageUrl.getFullImageUrl(msg.image_url) } else if (msg.message_type === 'voice' && msg.voice_url) { baseMessage.audioUrl = msg.voice_url baseMessage.duration = msg.voice_duration @@ -1826,13 +1826,15 @@ Page({ throw new Error('图片上传失败') } - const imageUrl = uploadRes.data.url - console.log('[chat-detail] 图片上传成功:', imageUrl) + const uploadedUrl = uploadRes.data.url + // 转换图片URL为完整地址 + const fullImageUrl = imageUrl.getFullImageUrl(uploadedUrl) + console.log('[chat-detail] 图片上传成功:', uploadedUrl, '完整地址:', fullImageUrl) // 2. 更新本地消息,移除上传中状态 const messages = this.data.messages.map(msg => { if (msg.id === newId) { - return { ...msg, imageUrl: imageUrl, uploading: false } + return { ...msg, imageUrl: fullImageUrl, uploading: false } } return msg }) @@ -1843,7 +1845,7 @@ Page({ await api.chat.sendImage({ character_id: this.data.characterId, conversation_id: this.data.conversationId, - image_url: imageUrl + image_url: fullImageUrl }) console.log('[chat-detail] 图片消息已保存到数据库') } catch (err) { diff --git a/pages/support/support.js b/pages/support/support.js index c3561b8..4c94e28 100644 --- a/pages/support/support.js +++ b/pages/support/support.js @@ -116,7 +116,7 @@ Page({ isMe: msg.senderType === 'user', text: msg.type === 'text' ? msg.content : (msg.type === 'image' ? '[图片]' : (msg.type === 'voice' ? '[语音]' : msg.content)), type: msg.type || 'text', - imageUrl: msg.type === 'image' ? msg.content : '', + imageUrl: msg.type === 'image' ? imageUrl.getFullImageUrl(msg.content) : '', audioUrl: msg.type === 'voice' ? msg.content : '', duration: msg.duration || 0, time: util.formatTime(new Date(msg.createdAt), 'HH:mm'), @@ -451,7 +451,8 @@ Page({ try { const uploadRes = await api.uploadFile(filePath, 'uploads') if (uploadRes.success && uploadRes.data && uploadRes.data.url) { - await this.sendMessage(uploadRes.data.url, 'image') + const fullUrl = imageUrl.getFullImageUrl(uploadRes.data.url) + await this.sendMessage(fullUrl, 'image') } else { throw new Error('Upload failed') } diff --git a/pages/withdraw/withdraw.js b/pages/withdraw/withdraw.js index c6d74af..4007a60 100644 --- a/pages/withdraw/withdraw.js +++ b/pages/withdraw/withdraw.js @@ -7,14 +7,22 @@ Page({ totalNavHeight: 64, balance: '0.00', amount: '', - withdrawType: 'wechat', - withdrawTypeText: '微信', + withdrawType: 'bank', + withdrawTypeText: '银行卡', + cardHolder: '', + bankName: '', + cardNumber: '', submitting: false, records: [], withdrawConfig: { minWithdrawAmount: 1 }, - showRulesModal: false + showRulesModal: false, + isVerified: false, + showBankModal: false, + tempCardHolder: '', + tempBankName: '', + tempCardNumber: '' }, onOpenRules() { this.setData({ showRulesModal: true }); @@ -38,10 +46,142 @@ Page({ navBarHeight, totalNavHeight: statusBarHeight + navBarHeight }); - this.load(); this.fetchConfig(); this.fetchRecords(); }, + onShow() { + this.load(); + this.checkCertification(); + this.fetchBankCards(); // 尝试获取已绑定的银行卡 + + // Load cached bank info + const cachedInfo = wx.getStorageSync('last_withdraw_bank_info'); + if (cachedInfo) { + this.setData({ + cardHolder: cachedInfo.name || cachedInfo.cardHolder || '', + bankName: cachedInfo.bank || cachedInfo.bankName || '', + cardNumber: cachedInfo.account || cachedInfo.cardNumber || '' + }); + } + }, + + // 获取用户已绑定的银行卡列表 + async fetchBankCards() { + try { + const res = await request({ url: '/api/user/bank-cards', method: 'GET' }); + if (res.data && res.data.success) { + const cards = res.data.data || []; + // 筛选出非默认卡或未被禁用的卡 (这里假设后端返回 active 状态) + // 如果后端支持多张卡,这里取第一张有效卡,或者根据 isDefault 排序 + if (cards.length > 0) { + const defaultCard = cards.find(c => c.isDefault) || cards[0]; + // 更新页面状态,视为已加载到缓存 + this.setData({ + cardHolder: defaultCard.cardHolder, + bankName: defaultCard.bankName, + // 注意:如果后端返回的是脱敏卡号,这里直接展示。提现时可能需要完整卡号 + // 如果后端返回完整卡号,则正常使用。 + // 假设后端返回完整卡号用于回显 + cardNumber: defaultCard.cardNumber + }); + + // 同时更新本地缓存,保持一致 + wx.setStorageSync('last_withdraw_bank_info', { + name: defaultCard.cardHolder, + bank: defaultCard.bankName, + account: defaultCard.cardNumber + }); + } else { + // 如果后端返回空列表,说明用户没有绑定银行卡 + // 此时应清除本地缓存的脏数据,确保 UI 显示“点击绑定” + this.setData({ + cardHolder: '', + bankName: '', + cardNumber: '' + }); + wx.removeStorageSync('last_withdraw_bank_info'); + } + } + } catch (err) { + console.log('Fetch bank cards failed', err); + } + }, + + onCardHolder(e) { this.setData({ cardHolder: e.detail.value }); }, + onBankName(e) { this.setData({ bankName: e.detail.value }); }, + onCardNumber(e) { this.setData({ cardNumber: e.detail.value }); }, + + // Bank Info Modal + onEditBankCard() { + // Check certification status before binding card + if (!this.data.isVerified) { + wx.showModal({ + title: '提示', + content: '绑定银行卡前请先完成实名认证', + confirmText: '去认证', + success: (res) => { + if (res.confirm) { + wx.navigateTo({ url: '/pages/certification/certification' }); + } + } + }); + return; + } + + this.setData({ + showBankModal: true, + tempCardHolder: this.data.cardHolder, + tempBankName: this.data.bankName, + tempCardNumber: this.data.cardNumber + }); + }, + onCloseBankModal() { + this.setData({ showBankModal: false }); + }, + onTempCardHolder(e) { this.setData({ tempCardHolder: e.detail.value }); }, + onTempBankName(e) { this.setData({ tempBankName: e.detail.value }); }, + onTempCardNumber(e) { this.setData({ tempCardNumber: e.detail.value }); }, + + confirmBankInfo() { + const { tempCardHolder, tempBankName, tempCardNumber } = this.data; + if (!tempCardHolder.trim()) { + wx.showToast({ title: '请输入持卡人姓名', icon: 'none' }); + return; + } + if (!tempBankName.trim()) { + wx.showToast({ title: '请输入银行名称', icon: 'none' }); + return; + } + if (!tempCardNumber.trim()) { + wx.showToast({ title: '请输入银行卡号', icon: 'none' }); + return; + } + + this.setData({ + cardHolder: tempCardHolder, + bankName: tempBankName, + cardNumber: tempCardNumber, + showBankModal: false + }); + + // Auto save to cache when confirmed + wx.setStorageSync('last_withdraw_bank_info', { + name: tempCardHolder, + bank: tempBankName, + account: tempCardNumber + }); + }, + + async checkCertification() { + try { + const res = await request({ url: '/api/user/certification', method: 'GET' }); + if (res.data && res.data.success && res.data.data) { + this.setData({ isVerified: res.data.data.status === 'approved' }); + } + } catch (err) { + console.error('Check certification status failed', err); + } + }, onBack() { wx.navigateBack({ delta: 1 }); }, @@ -49,8 +189,13 @@ Page({ try { const res = await request({ url: '/api/withdraw/config', method: 'GET' }); if (res.data && res.data.code === 0 && res.data.data) { + // 优先读取后端返回的配置 + // 确保 minWithdrawAmount 被正确解析为数字 + const minAmount = Number(res.data.data.minWithdrawAmount); this.setData({ - withdrawConfig: res.data.data + withdrawConfig: { + minWithdrawAmount: !isNaN(minAmount) ? minAmount : 1 + } }); } } catch (err) { @@ -75,6 +220,15 @@ Page({ 'rejected': '已拒绝' }; item.statusText = statusMap[item.status] || '未知'; + + // 类型映射 + const typeMap = { + 'wechat': '微信提现', + 'alipay': '支付宝提现', + 'bank': '银行卡提现' + }; + item.typeText = typeMap[item.withdrawType] || '余额提现'; + return item; }); this.setData({ records }); @@ -104,6 +258,22 @@ Page({ }, async submit() { if (this.data.submitting) return; + + // Check certification status + if (!this.data.isVerified) { + wx.showModal({ + title: '提示', + content: '提现前请先完成实名认证', + confirmText: '去认证', + success: (res) => { + if (res.confirm) { + wx.navigateTo({ url: '/pages/certification/certification' }); + } + } + }); + return; + } + const amountNum = Number(this.data.amount || 0); const minAmount = this.data.withdrawConfig.minWithdrawAmount || 0; @@ -117,6 +287,20 @@ Page({ return; } + let accountInfo = {}; + if (this.data.withdrawType === 'bank') { + const { cardHolder, bankName, cardNumber } = this.data; + if (!cardHolder.trim() || !bankName.trim() || !cardNumber.trim()) { + this.onEditBankCard(); // Open modal if info missing + return; + } + accountInfo = { + name: cardHolder, + bank: bankName, + account: cardNumber + }; + } + this.setData({ submitting: true }); try { const res = await request({ @@ -126,11 +310,19 @@ Page({ action: 'withdraw', amount: amountNum, withdrawType: this.data.withdrawType, - accountInfo: {} + accountInfo: accountInfo, + // 如果后续支持选择已绑定银行卡,可在此传递 bankCardId + // bankCardId: this.data.selectedCardId } }); const body = res.data || {}; if (!body.success) throw new Error(body.error || '提交失败'); + + // Cache successful bank info + if (this.data.withdrawType === 'bank') { + wx.setStorageSync('last_withdraw_bank_info', accountInfo); + } + wx.showToast({ title: '提交申请成功', icon: 'success' }); this.setData({ amount: '' }); this.load(); diff --git a/pages/withdraw/withdraw.wxml b/pages/withdraw/withdraw.wxml index 7ff5b58..483dd85 100644 --- a/pages/withdraw/withdraw.wxml +++ b/pages/withdraw/withdraw.wxml @@ -39,17 +39,33 @@ 提现方式 - - - - - - 微信零钱 - - + + + + + + + 银行卡 + + 点击绑定 + + + + + + + + + + {{bankName}} + {{cardNumber}} + + + 修改 + - + @@ -60,6 +76,38 @@ + + + + + 填写银行卡信息 + + + + + + + 持卡人姓名 + + + + + + 银行名称 + + + + + + 银行卡号 + + + + + + + + @@ -68,7 +116,7 @@ - 微信提现 + {{item.typeText}} {{item.timeStr}} @@ -102,10 +150,10 @@ 提现规则说明 - 1. 最低提现金额:单笔提现金额不低于 ¥100.00。 + 1. 最低提现金额:单笔提现金额不低于 ¥{{withdrawConfig.minWithdrawAmount || 100}}。 2. 每日提现次数:每日最多可发起 3 次提现申请。 3. 可提现余额:仅限“已结算”状态的收入可提现,待结算金额暂不支持。 - 4. 提现服务费:每笔提现收取 1% 服务费(最低 ¥1.00),由第三方支付平台收取。 + 4. 提现服务费:目前平台提现免收服务费,全额到账。 5. 审核时间:提现申请将在 24 小时 内完成审核并安排打款。 6. 到账时间:审核通过后,预计 24 小时内到账,具体以银行或微信到账通知为准。 7. 账户要求:提现账户需为 本人实名认证 的微信或银行卡。 @@ -125,7 +173,7 @@ · 提现服务费如何计算? - 每笔提现收取 5% 作为服务费,在提现金额中自动扣除。 + 目前平台处于推广期,提现免收服务费。 · 提现未到账怎么办? diff --git a/pages/withdraw/withdraw.wxss b/pages/withdraw/withdraw.wxss index b48a5db..35ace91 100644 --- a/pages/withdraw/withdraw.wxss +++ b/pages/withdraw/withdraw.wxss @@ -97,6 +97,118 @@ transition: all 0.3s; } +/* 银行卡展示区域 */ +.bank-info-display { + margin-top: -24rpx; + margin-bottom: 24rpx; + padding-top: 32rpx; + border-top: 2rpx dashed #F3F4F6; +} + +.bank-card-preview { + display: flex; + align-items: center; + background: #F9FAFB; + border-radius: 24rpx; + padding: 24rpx; + border: 2rpx solid #F3F4F6; +} + +.bank-card-icon { + width: 64rpx; + height: 64rpx; + background: #3B82F6; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin-right: 24rpx; +} + +.bank-card-details { + flex: 1; + display: flex; + flex-direction: column; +} + +.bank-name { + font-size: 30rpx; + font-weight: 700; + color: #111827; + margin-bottom: 4rpx; +} + +.bank-number { + font-size: 26rpx; + color: #6B7280; +} + +.bank-card-edit { + padding: 12rpx 24rpx; + background: #FFFFFF; + border-radius: 999rpx; + border: 2rpx solid #E5E7EB; +} + +.bank-card-edit text { + font-size: 24rpx; + color: #6B7280; + font-weight: 600; +} + +.bank-card-empty { + height: 112rpx; /* Match input-wrapper height */ + display: flex; + align-items: center; + padding: 0 32rpx; + background: #F9FAFB; + border: 2rpx dashed #D1D5DB; + border-radius: 32rpx; +} + +.bank-card-empty text { + font-size: 28rpx; + color: #6B7280; + font-weight: 600; +} + +/* 银行卡弹窗样式 */ +.bank-modal { + z-index: 1002; +} + +.bank-form { + padding-bottom: 40rpx; +} + +.confirm-btn { + margin-top: 48rpx; + width: 100%; + height: 96rpx; + border-radius: 48rpx; + background: #3B82F6; + color: #ffffff; + font-size: 34rpx; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; +} + +.input-wrapper.small-input { + height: 96rpx; + background: #F9FAFB; + border-radius: 24rpx; +} + +.input-text { + flex: 1; + height: 100%; + font-size: 32rpx; + color: #111827; + font-weight: 500; +} + .input-wrapper:focus-within { background: #FFFFFF; border-color: #B06AB3; @@ -156,6 +268,16 @@ justify-content: center; } +.bank-icon-box { + width: 64rpx; + height: 64rpx; + background: #3B82F6; /* Bank Blue */ + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; +} + .select-text { font-size: 30rpx; font-weight: 700; diff --git a/utils/auth.js b/utils/auth.js index ece4f29..0dcd454 100644 --- a/utils/auth.js +++ b/utils/auth.js @@ -153,9 +153,23 @@ function saveUserInfo(user, token, expiresAt = null) { // 登录成功后,检查并绑定推荐码 if (user && user.id && token) { checkAndBindReferralCode(user.id, token) + syncReferralCode() } } +function syncReferralCode() { + const existing = wx.getStorageSync('referralCode') + if (existing) return Promise.resolve(existing) + + return api.commission.getStats().then(res => { + const code = res && res.data ? (res.data.referralCode || '') : '' + if (res && res.success && code) { + wx.setStorageSync('referralCode', code) + } + return code + }).catch(() => '') +} + /** * 检查并绑定推荐码(佣金系统) * 在用户登录成功后自动调用 diff --git a/utils/imageUrl.js b/utils/imageUrl.js index 1d9c724..7ba0ce8 100644 --- a/utils/imageUrl.js +++ b/utils/imageUrl.js @@ -38,14 +38,16 @@ function getFullImageUrl(url, defaultImage = '') { if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('data:')) { try { const parsed = new URL(url) - if (parsed.pathname.startsWith('/uploads/')) { - parsed.pathname = `/api/uploads/${parsed.pathname.slice('/uploads/'.length)}` - return parsed.toString() - } - if (/^\/(avatars|characters|audio|documents|assets|interest-partners|exchange|products|temp)\//.test(parsed.pathname)) { - parsed.pathname = `/api/uploads${parsed.pathname}` - return parsed.toString() - } + // 移除对 /uploads/ 路径的特殊处理,直接使用原始路径 + // if (parsed.pathname.startsWith('/uploads/')) { + // parsed.pathname = `/api/uploads/${parsed.pathname.slice('/uploads/'.length)}` + // return parsed.toString() + // } + // 移除统一加 /api/uploads 前缀的逻辑,直接使用静态资源路径 + // if (/^\/(avatars|characters|audio|documents|assets|interest-partners|exchange|products|temp)\//.test(parsed.pathname)) { + // parsed.pathname = `/api/uploads${parsed.pathname}` + // return parsed.toString() + // } return url } catch (_) { return url @@ -58,13 +60,16 @@ function getFullImageUrl(url, defaultImage = '') { } let processedUrl = url - if (processedUrl.startsWith('/uploads/')) { - processedUrl = `/api/uploads/${processedUrl.slice('/uploads/'.length)}` - } else if (processedUrl.startsWith('uploads/')) { - processedUrl = `/api/uploads/${processedUrl.slice('uploads/'.length)}` - } else if (/^(\/)?(avatars|characters|audio|documents|assets|interest-partners|exchange|products|temp)\//.test(processedUrl)) { - processedUrl = processedUrl.startsWith('/') ? `/api/uploads${processedUrl}` : `/api/uploads/${processedUrl}` - } + // 移除对 /uploads/ 路径的特殊处理,直接使用原始路径 + // if (processedUrl.startsWith('/uploads/')) { + // processedUrl = `/api/uploads/${processedUrl.slice('/uploads/'.length)}` + // } else if (processedUrl.startsWith('uploads/')) { + // processedUrl = `/api/uploads/${processedUrl.slice('uploads/'.length)}` + // } else + // 移除统一加 /api/uploads 前缀的逻辑 + // if (/^(\/)?(avatars|characters|audio|documents|assets|interest-partners|exchange|products|temp)\//.test(processedUrl)) { + // processedUrl = processedUrl.startsWith('/') ? `/api/uploads${processedUrl}` : `/api/uploads/${processedUrl}` + // } // 其他相对路径,拼接服务器地址 // 确保URL以/开头 diff --git a/utils_new/payment.js b/utils_new/payment.js index b09f4a7..2de840c 100644 --- a/utils_new/payment.js +++ b/utils_new/payment.js @@ -1,10 +1,22 @@ const { request } = require('./request'); +const config = require('../config/index'); + +const getPaymentChannel = () => { + return config.PAYMENT_CHANNEL || 'wechat'; +}; const createVipOrder = async ({ planId, duration }) => { + const channel = getPaymentChannel(); const res = await request({ url: '/api/payment/vip', method: 'POST', - data: { planId, duration, paymentMethod: 'wechat' } + data: { + planId, + duration, + paymentMethod: channel, + paymentChannel: channel, + platform: 'miniapp' // [关键] 必须传这个,后端据此调用汇付的小程序支付接口 + } }); const body = res.data || {}; if (!body.success) throw new Error(body.error || '创建VIP订单失败'); @@ -12,10 +24,16 @@ const createVipOrder = async ({ planId, duration }) => { }; const createProductOrder = async ({ productId, referralCode }) => { + const channel = getPaymentChannel(); const res = await request({ url: `/api/products/${productId}/purchase`, method: 'POST', - data: { payment_method: 'wechat', ...(referralCode ? { referral_code: referralCode } : {}) } + data: { + payment_method: channel, + payment_channel: channel, + platform: 'miniapp', // [关键] 必须传这个,后端据此调用汇付的小程序支付接口 + ...(referralCode ? { referral_code: referralCode } : {}) + } }); const body = res.data || {}; if (!body.success) throw new Error(body.error || '创建订单失败'); diff --git a/utils_new/request.js b/utils_new/request.js index c3392a3..f8f80c2 100644 --- a/utils_new/request.js +++ b/utils_new/request.js @@ -51,7 +51,30 @@ const request = ({ url, method = 'GET', data, header = {} }) => { }); }; +const uploadFile = ({ url, filePath, name = 'file', formData = {}, header = {} }) => { + return new Promise((resolve, reject) => { + const baseUrl = getBaseUrl(); + wx.uploadFile({ + url: `${baseUrl}${url}`, + filePath, + name, + formData, + header: { + ...(getToken() ? { Authorization: `Bearer ${getToken()}` } : {}), + ...header + }, + success: (res) => { + resolve(res); + }, + fail: (err) => { + reject(err); + } + }); + }); +}; + module.exports = { request, + uploadFile, getBaseUrl };