1540 lines
72 KiB
Vue
1540 lines
72 KiB
Vue
<template>
|
||
<view data-component-name="diy-recommend-code" style="width:100%;box-sizing:border-box;overflow:hidden;">
|
||
<!-- ============ 未登录:占位引导卡片(全部内联样式,匹配参考图) ============ -->
|
||
<view v-if="!storeToken" @tap="goLogin"
|
||
style="width:100%;box-sizing:border-box;margin-top:24rpx;display:flex;align-items:center;background:linear-gradient(135deg,#FFF5F5 0%,#FFF0F0 35%,#FFEDED 65%,#FFE8E8 100%);border-radius:32rpx;padding:36rpx 28rpx;position:relative;overflow:hidden;box-shadow:0 4rpx 16rpx rgba(255,107,107,0.1);">
|
||
<!-- 背景装饰blob -->
|
||
<view style="position:absolute;top:-60rpx;left:-50rpx;width:240rpx;height:240rpx;border-radius:50%;background:rgba(255,107,107,0.05);z-index:0;"></view>
|
||
<view style="position:absolute;bottom:-80rpx;right:-50rpx;width:260rpx;height:260rpx;border-radius:50%;background:rgba(255,193,7,0.04);z-index:0;"></view>
|
||
<view style="position:absolute;top:30rpx;right:80rpx;width:100rpx;height:100rpx;border-radius:50%;background:rgba(255,152,0,0.05);z-index:0;"></view>
|
||
<!-- 左侧:3D钥匙+礼物装饰区 -->
|
||
<view style="flex-shrink:0;width:200rpx;height:200rpx;position:relative;z-index:1;display:flex;align-items:center;justify-content:center;">
|
||
<!-- 圆形底座 -->
|
||
<view style="position:absolute;bottom:0;width:170rpx;height:170rpx;border-radius:50%;background:radial-gradient(circle at 40% 35%,rgba(255,255,255,0.95) 0%,rgba(255,240,230,0.6) 50%,rgba(255,220,210,0.15) 100%);box-shadow:0 6rpx 24rpx rgba(255,107,107,0.12);"></view>
|
||
<!-- 钥匙+礼物组合 -->
|
||
<view style="position:relative;z-index:2;display:flex;align-items:center;justify-content:center;">
|
||
<view style="transform:rotate(-25deg);margin-right:-6rpx;">
|
||
<text style="font-size:76rpx;line-height:1;">🔑</text>
|
||
</view>
|
||
<view style="margin-left:-6rpx;">
|
||
<text style="font-size:68rpx;line-height:1;">🎁</text>
|
||
</view>
|
||
</view>
|
||
<!-- 闪光装饰 -->
|
||
<text style="position:absolute;top:6rpx;right:14rpx;font-size:26rpx;z-index:3;">✨</text>
|
||
<text style="position:absolute;top:54rpx;left:6rpx;font-size:18rpx;z-index:3;opacity:0.7;">✨</text>
|
||
<text style="position:absolute;bottom:24rpx;right:20rpx;font-size:20rpx;z-index:3;opacity:0.5;">⭐</text>
|
||
</view>
|
||
<!-- 右侧:标题+副标题+特性标签+按钮 -->
|
||
<view style="flex:1;margin-left:20rpx;display:flex;flex-direction:column;position:relative;z-index:1;">
|
||
<!-- 主标题 -->
|
||
<text style="font-size:30rpx;font-weight:700;color:#1A1A1A;line-height:1.4;">{{ value.loginTipText || '登录后查看我的推荐码' }}</text>
|
||
<!-- 特性标签行 -->
|
||
<view style="display:flex;align-items:center;gap:14rpx;margin-top:14rpx;">
|
||
<view style="display:flex;align-items:center;gap:4rpx;">
|
||
<text style="font-size:22rpx;">💎</text>
|
||
<text style="font-size:21rpx;color:#666;">专属推荐码</text>
|
||
</view>
|
||
<view style="display:flex;align-items:center;gap:4rpx;">
|
||
</view>
|
||
</view>
|
||
<!-- 去登录按钮 -->
|
||
<view style="margin-top:18rpx;display:flex;align-items:center;gap:6rpx;width:fit-content;background:linear-gradient(90deg,#FF6B6B 0%,#FF8E8E 100%);border-radius:32rpx;padding:14rpx 36rpx;box-shadow:0 4rpx 12rpx rgba(255,107,107,0.3);">
|
||
<text style="font-size:26rpx;color:#FFFFFF;font-weight:600;">{{ value.loginBtnText || '去登录' }}</text>
|
||
<text style="font-size:24rpx;color:#FFFFFF;">›</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<!-- ============ 已登录:推荐码卡片(后端驱动,3 套预设 + consent 遮罩) ============ -->
|
||
<view v-else>
|
||
<!-- ============ Consent 同意遮罩(全部内联样式,匹配样式4参考图) ============ -->
|
||
<view v-if="showConsent" style="width:100%;box-sizing:border-box;margin-top:24rpx;background:#FFFFFF;border-radius:32rpx;padding:28rpx 28rpx;position:relative;overflow:hidden;box-shadow:0 4rpx 16rpx rgba(0,0,0,0.06);">
|
||
<!-- 标题行 + 待获取标签 -->
|
||
<view style="display:flex;align-items:center;gap:12rpx;position:relative;z-index:1;">
|
||
<text :style="{ fontSize: px2rpx(value.titleFontSize || 17) + 'rpx', fontWeight: '800', color: value.titleColor || '#1A1A1A' }">{{ value.title || '我的推荐码' }}</text>
|
||
<view style="display:inline-flex;align-items:center;background:rgba(255,152,0,0.12);border-radius:16rpx;padding:4rpx 14rpx;gap:4rpx;">
|
||
<text style="font-size:18rpx;color:#FF8F00;">✓</text>
|
||
<text style="font-size:20rpx;color:#FF8F00;font-weight:600;">待获取推荐码</text>
|
||
</view>
|
||
</view>
|
||
<!-- 推荐码占位+按钮区域:浅暖色背景 -->
|
||
<view style="margin-top:24rpx;background:linear-gradient(135deg,#FFF8F0 0%,#FFF5F0 50%,#FFF0EB 100%);border-radius:16rpx;padding:28rpx 24rpx 24rpx;position:relative;overflow:hidden;">
|
||
<!-- 右上角礼物装饰 -->
|
||
<view style="position:absolute;top:-10rpx;right:-6rpx;z-index:2;">
|
||
<text style="font-size:72rpx;">🎁</text>
|
||
<text style="position:absolute;top:-4rpx;left:-10rpx;font-size:14rpx;">✨</text>
|
||
</view>
|
||
<!-- 推荐码占位文字居中 -->
|
||
<view style="text-align:center;position:relative;z-index:1;">
|
||
<text style="font-size:48rpx;font-weight:900;color:#FFCCC0;letter-spacing:2rpx;font-family:'Courier New',monospace;">待获取</text>
|
||
</view>
|
||
<!-- 描述文字 -->
|
||
<view style="margin-top:12rpx;text-align:center;position:relative;z-index:1;">
|
||
<text style="font-size:22rpx;color:#999;line-height:1.5;">{{ value.consentText || '您的推荐码将用于企业入驻时识别推荐人关系,请确认后获取。' }}</text>
|
||
</view>
|
||
<!-- 同意并获取推荐码按钮:红橙渐变,小圆角,全宽 -->
|
||
<view @tap="confirmConsent" :style="loadingCode ? 'margin-top:16rpx;height:76rpx;display:flex;justify-content:center;align-items:center;background:linear-gradient(90deg,#FF6B35 0%,#FF4444 100%);border-radius:16rpx;opacity:0.7;pointer-events:none;position:relative;z-index:1;' : 'margin-top:16rpx;height:76rpx;display:flex;justify-content:center;align-items:center;background:linear-gradient(90deg,#FF6B35 0%,#FF4444 100%);border-radius:16rpx;box-shadow:0 4rpx 12rpx rgba(255,68,68,0.25);position:relative;z-index:1;'">
|
||
<text style="font-size:27rpx;font-weight:700;color:#FFFFFF;">{{ loadingCode ? '提交中...' : (value.consentButtonText || '同意并获取推荐码') }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- ============ 加载中占位(首次加载 / consent 提交中 / regenerate 中) ============ -->
|
||
<view v-else-if="loadingCode && !code" class="loading-wrap" :style="cardBaseStyle">
|
||
<text class="loading-text" :style="{ color: value.titleColor || '#999999' }">推荐码加载中...</text>
|
||
</view>
|
||
|
||
<!-- ============ 推荐码卡片:仅在非 consent 且非首次加载时渲染 ============ -->
|
||
<block v-if="!showConsent && !(loadingCode && !code)">
|
||
<!-- ============ 预设 1:Gradient 渐变风格(默认)全部内联样式 ============ -->
|
||
<view v-if="stylePreset === 'gradient'" :style="{ width: '100%', boxSizing: 'border-box', marginTop: '24rpx', background: '#FFFFFF', borderRadius: px2rpx(value.borderRadius || 8) + 'rpx', padding: px2rpx(value.padding || 16) + 'rpx ' + px2rpx(value.padding || 16) + 'rpx', position: 'relative', overflow: 'hidden', boxShadow: '0 4rpx 16rpx rgba(0,0,0,0.06)' }">
|
||
<!-- 标题行 + 已获得推荐码标签 -->
|
||
<view style="display:flex;align-items:center;gap:12rpx;position:relative;z-index:1;">
|
||
<text :style="{ fontSize: px2rpx(value.titleFontSize || 17) + 'rpx', fontWeight: '800', color: value.titleColor || '#1A1A1A' }">{{ value.title || '我的推荐码' }}</text>
|
||
<view style="display:inline-flex;align-items:center;background:#52C41A;border-radius:16rpx;padding:4rpx 14rpx;gap:4rpx;">
|
||
<text style="font-size:18rpx;color:#FFFFFF;">✓</text>
|
||
<text style="font-size:20rpx;color:#FFFFFF;font-weight:600;">已获得推荐码</text>
|
||
</view>
|
||
</view>
|
||
<!-- 推荐码+复制按钮区域:浅米黄色背景,圆角紧凑 -->
|
||
<view style="margin-top:24rpx;background:#FFF8F0;border-radius:16rpx;padding:32rpx 20rpx 24rpx;position:relative;z-index:1;">
|
||
<!-- 推荐码居中显示 -->
|
||
<view style="text-align:center;">
|
||
<text :style="{ fontSize: px2rpx(Math.max(16, Math.min(48, (Number(value.codeFontSize) || 28) * 0.93))) + 'rpx', fontWeight: '900', color: value.codeColor || '#DC143C', letterSpacing: '2rpx', fontFamily: 'Courier New, monospace' }">{{ code || value.placeholderCode || 'ABCD1234' }}</text>
|
||
</view>
|
||
<!-- 复制推荐码按钮:白色小按钮,红边框,在推荐码下方居中 -->
|
||
<view v-if="value.showCopyButton !== false" style="margin-top:16rpx;display:flex;justify-content:center;">
|
||
<view @tap="copyRecommendCode" :style="{ width: 'fit-content', height: '60rpx', display: 'flex', justifyContent: 'center', alignItems: 'center', background: '#FFFFFF', border: '2rpx solid ' + (value.codeColor || '#DC143C'), borderRadius: '30rpx', padding: '0 32rpx', gap: '8rpx' }">
|
||
<text style="font-size:22rpx;">📋</text>
|
||
<text :style="{ fontSize: '24rpx', color: value.codeColor || '#DC143C', fontWeight: '600' }">{{ nc.copyButtonText || '复制推荐码' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<!-- 立即分享链接按钮:黄橙渐变,胶囊形圆角,紧凑布局
|
||
固定走 linear-gradient,不读 diyData.buttonBgColor(避免被后端默认色覆盖)
|
||
H5 端用 view 触发(避免 button 默认样式),小程序/APP 端保留 button 以触发 open-type=share -->
|
||
<button v-if="value.showShareButton !== false && !isH5Platform" open-type="share" @tap="onShareClick" :style="{ marginTop: '16rpx', height: '80rpx', width: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center', background: 'linear-gradient(90deg,#FFC107 0%,#FF8F00 100%)', borderRadius: '40rpx', boxShadow: '0 4rpx 12rpx rgba(255,143,0,0.3)', position: 'relative', zIndex: '1', border: 'none', lineHeight: '1' }">
|
||
<text :style="{ fontSize: '28rpx', fontWeight: '700', color: '#FFFFFF' }">📤 {{ value.buttonText || '立即分享' }}</text>
|
||
</button>
|
||
<view v-else-if="value.showShareButton !== false" @tap="onShareClick" :style="{ marginTop: '16rpx', height: '80rpx', width: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center', background: 'linear-gradient(90deg,#FFC107 0%,#FF8F00 100%)', borderRadius: '40rpx', boxShadow: '0 4rpx 12rpx rgba(255,143,0,0.3)', position: 'relative', zIndex: '1', lineHeight: '1', boxSizing: 'border-box' }">
|
||
<text :style="{ fontSize: '28rpx', fontWeight: '700', color: '#FFFFFF' }">📤 {{ nc.buttonText || '立即分享' }}</text>
|
||
</view>
|
||
<!-- showLink -->
|
||
<view v-if="isFeatureOn(value.showLink)" style="margin-top:20rpx;text-align:center;position:relative;z-index:1;">
|
||
<text @tap="copyLink" style="font-size:22rpx;color:#999;text-decoration:underline;">{{ formatShareLink }}</text>
|
||
</view>
|
||
<!-- showQR -->
|
||
<view v-if="isFeatureOn(value.showQR)" style="margin-top:20rpx;display:flex;flex-direction:column;align-items:center;position:relative;z-index:1;">
|
||
<image :src="qrcodeRenderUrl" mode="aspectFit" style="width:200rpx;height:200rpx;" />
|
||
<text style="font-size:22rpx;color:#999;margin-top:12rpx;">{{ value.posterQrTip || '扫码进入并填写入驻信息' }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- ============ 预设 2:Minimal 极简风格(全部内联样式) ============ -->
|
||
<view v-if="stylePreset === 'minimal'" :style="{ width: '100%', boxSizing: 'border-box', marginTop: '24rpx', background: 'linear-gradient(90deg,#FFFFFF 0%,#FFF8F8 30%,#FFF5F5 60%,#FFF0F0 100%)', borderRadius: px2rpx(value.borderRadius || 8) + 'rpx', padding: px2rpx(value.padding || 16) + 'rpx ' + px2rpx(value.padding || 16) + 'rpx', position: 'relative', overflow: 'hidden', boxShadow: '0 4rpx 16rpx rgba(255,107,107,0.08)' }">
|
||
<!-- 背景礼物装饰大图1(右上角,移至顶部) -->
|
||
<view style="position:absolute;top:-20rpx;right:-30rpx;z-index:0;">
|
||
<text style="font-size:200rpx;opacity:0.15;">🎁</text>
|
||
</view>
|
||
<!-- 背景礼物装饰大图2(左下角) -->
|
||
<view style="position:absolute;bottom:-40rpx;left:-30rpx;z-index:0;">
|
||
<text style="font-size:180rpx;opacity:0.12;">🎁</text>
|
||
</view>
|
||
<!-- 背景散落小礼物装饰 -->
|
||
<view style="position:absolute;top:120rpx;left:30rpx;z-index:0;">
|
||
<text style="font-size:60rpx;opacity:0.15;">🎁</text>
|
||
</view>
|
||
<view style="position:absolute;top:200rpx;right:60rpx;z-index:0;">
|
||
<text style="font-size:48rpx;opacity:0.18;">🎀</text>
|
||
</view>
|
||
<view style="position:absolute;bottom:160rpx;right:20rpx;z-index:0;">
|
||
<text style="font-size:52rpx;opacity:0.15;">🎁</text>
|
||
</view>
|
||
<!-- 标题行 + 已获得标签 -->
|
||
<view style="display:flex;align-items:center;gap:12rpx;position:relative;z-index:1;">
|
||
<text :style="{ fontSize: px2rpx(value.titleFontSize || 17) + 'rpx', fontWeight: '800', color: value.titleColor || '#1A1A1A' }">{{ value.title || '我的推荐码' }}</text>
|
||
<view style="display:inline-flex;align-items:center;background:rgba(255,107,107,0.15);border-radius:16rpx;padding:4rpx 14rpx;">
|
||
<text style="font-size:20rpx;color:#FF6B6B;font-weight:600;">已获得</text>
|
||
</view>
|
||
</view>
|
||
<!-- 描述文字 -->
|
||
<view style="margin-top:8rpx;padding-right:70rpx;position:relative;z-index:1;">
|
||
<text style="font-size:23rpx;color:#888;line-height:1.5;">分享给好友,邀请注册即可获得奖励</text>
|
||
</view>
|
||
<!-- 推荐码红色虚线框:推荐码居中,圆角更圆,更紧凑 -->
|
||
<view style="margin-top:20rpx;background:rgba(255,255,255,0.7);border:2rpx dashed #FF6B6B;border-radius:32rpx;padding:12rpx 16rpx;position:relative;z-index:1;">
|
||
<view style="text-align:center;">
|
||
<text :style="{ fontSize: px2rpx(Math.max(16, Math.min(48, (Number(value.codeFontSize) || 28) * 0.86))) + 'rpx', fontWeight: '800', color: value.codeColor || '#FF4444', letterSpacing: '2rpx', fontFamily: 'Courier New, monospace' }">{{ code || value.placeholderCode || 'ABCD1234' }}</text>
|
||
</view>
|
||
</view>
|
||
<!-- 复制推荐码按钮:白色小按钮,在虚线框下方、立即分享链接上方 -->
|
||
<view v-if="value.showCopyButton !== false" style="margin-top:14rpx;display:flex;justify-content:center;position:relative;z-index:1;">
|
||
<view @tap="copyRecommendCode" :style="{ width: 'fit-content', height: '60rpx', display: 'flex', justifyContent: 'center', alignItems: 'center', background: '#FFFFFF', border: '2rpx solid ' + (value.codeColor || '#FF6B6B'), borderRadius: '30rpx', padding: '0 32rpx', gap: '8rpx' }">
|
||
<text style="font-size:22rpx;">📋</text>
|
||
<text :style="{ fontSize: '24rpx', color: value.codeColor || '#FF6B6B', fontWeight: '600' }">{{ nc.copyButtonText || '复制推荐码' }}</text>
|
||
</view>
|
||
</view>
|
||
<!-- 立即分享链接按钮:红橙渐变胶囊全宽铺满,触发微信原生分享
|
||
固定走 linear-gradient,不读 diyData.buttonBgColor
|
||
H5 端用 view 触发(避免 button 默认样式),小程序/APP 端保留 button 触发 open-type=share -->
|
||
<button v-if="value.showShareButton !== false && !isH5Platform" open-type="share" @tap="onShareClick" :style="{ marginTop: '16rpx', height: '76rpx', width: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center', background: 'linear-gradient(90deg,#FF6B6B 0%,#FF4444 100%)', borderRadius: '38rpx', boxShadow: '0 4rpx 12rpx rgba(255,68,68,0.3)', position: 'relative', zIndex: '1', border: 'none', lineHeight: '1' }">
|
||
<text :style="{ fontSize: '27rpx', fontWeight: '700', color: '#FFFFFF' }">📤 {{ value.buttonText || '立即分享' }}</text>
|
||
</button>
|
||
<view v-else-if="value.showShareButton !== false" @tap="onShareClick" :style="{ marginTop: '16rpx', height: '76rpx', width: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center', background: 'linear-gradient(90deg,#FF6B6B 0%,#FF4444 100%)', borderRadius: '38rpx', boxShadow: '0 4rpx 12rpx rgba(255,68,68,0.3)', position: 'relative', zIndex: '1', lineHeight: '1', boxSizing: 'border-box' }">
|
||
<text :style="{ fontSize: '27rpx', fontWeight: '700', color: '#FFFFFF' }">📤 {{ nc.buttonText || '立即分享' }}</text>
|
||
</view>
|
||
<!-- showLink -->
|
||
<view v-if="isFeatureOn(value.showLink)" style="margin-top:16rpx;text-align:center;position:relative;z-index:1;">
|
||
<text @tap="copyLink" style="font-size:22rpx;color:#999;text-decoration:underline;">{{ formatShareLink }}</text>
|
||
</view>
|
||
<!-- showQR -->
|
||
<view v-if="isFeatureOn(value.showQR)" style="margin-top:16rpx;display:flex;flex-direction:column;align-items:center;position:relative;z-index:1;">
|
||
<image :src="qrcodeRenderUrl" mode="aspectFit" style="width:200rpx;height:200rpx;" />
|
||
<text style="font-size:22rpx;color:#999;margin-top:8rpx;">{{ value.posterQrTip || '扫码进入并填写入驻信息' }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- ============ 预设 3:Card 卡片风格(全部内联样式) ============ -->
|
||
<view v-if="stylePreset === 'card'" :style="{ width: '100%', boxSizing: 'border-box', marginTop: '24rpx', background: 'linear-gradient(180deg,#FFF9F5 0%,#FFF5F0 100%)', borderRadius: px2rpx(value.borderRadius || 8) + 'rpx', padding: px2rpx(value.padding || 16) + 'rpx ' + px2rpx(value.padding || 16) + 'rpx', position: 'relative', overflow: 'hidden' }">
|
||
<!-- 右上角礼物装饰(开盖礼物盒) -->
|
||
<view style="position:absolute;top:16rpx;right:20rpx;z-index:2;">
|
||
<text style="font-size:52rpx;">🎁</text>
|
||
</view>
|
||
<!-- 标题行 + 已生效标签 -->
|
||
<view style="display:flex;align-items:center;gap:12rpx;position:relative;z-index:1;">
|
||
<text :style="{ fontSize: px2rpx(value.titleFontSize || 17) + 'rpx', fontWeight: '800', color: value.titleColor || '#1A1A1A' }">{{ value.title || '我的推荐码' }}</text>
|
||
<view style="display:inline-flex;align-items:center;background:rgba(255,107,107,0.12);border-radius:16rpx;padding:4rpx 14rpx;">
|
||
<text style="font-size:20rpx;color:#FF6B6B;font-weight:600;">✓ 已生效</text>
|
||
</view>
|
||
</view>
|
||
<!-- 描述文字 -->
|
||
<view style="margin-top:8rpx;padding-right:70rpx;position:relative;z-index:1;">
|
||
<text style="font-size:23rpx;color:#999;line-height:1.5;">推荐码已生成,可用于邀请好友或企业入驻绑定推荐关系</text>
|
||
</view>
|
||
<!-- 推荐码红色虚线框:居中,圆角,紧凑 -->
|
||
<view style="margin-top:20rpx;display:flex;justify-content:center;position:relative;z-index:1;">
|
||
<view style="background:rgba(255,255,255,0.7);border:2rpx dashed #FF6B6B;border-radius:32rpx;padding:12rpx 32rpx;text-align:center;">
|
||
<text :style="{ fontSize: px2rpx(Math.max(16, Math.min(48, (Number(value.codeFontSize) || 28) * 0.71))) + 'rpx', fontWeight: '800', color: value.codeColor || '#FF4444', letterSpacing: '2rpx', fontFamily: 'Courier New, monospace' }">{{ code || value.placeholderCode || 'ABCD1234' }}</text>
|
||
</view>
|
||
</view>
|
||
<!-- 复制推荐码按钮:白色小按钮,在虚线框下方、立即分享链接上方,居中 -->
|
||
<view v-if="value.showCopyButton !== false" style="margin-top:14rpx;display:flex;justify-content:center;position:relative;z-index:1;">
|
||
<view @tap="copyRecommendCode" :style="{ width: 'fit-content', height: '60rpx', display: 'flex', justifyContent: 'center', alignItems: 'center', background: '#FFFFFF', border: '2rpx solid ' + (value.codeColor || '#FF6B6B'), borderRadius: '30rpx', padding: '0 32rpx', gap: '8rpx' }">
|
||
<text style="font-size:22rpx;">📋</text>
|
||
<text :style="{ fontSize: '24rpx', color: value.codeColor || '#FF6B6B', fontWeight: '600' }">{{ nc.copyButtonText || '复制推荐码' }}</text>
|
||
</view>
|
||
</view>
|
||
<!-- 立即分享链接按钮:Card 风格固定「白底红边框」设计,不读 buttonBgColor
|
||
H5 端改用 view 触发(避免 <button> 默认样式影响),小程序/APP 端保留 button 以触发 open-type=share -->
|
||
<button v-if="value.showShareButton !== false && !isH5Platform" open-type="share" @tap="onShareClick" :style="{ marginTop: '16rpx', height: '76rpx', width: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center', background: '#FFFFFF', border: '2rpx solid ' + (value.codeColor || '#FF6B6B'), borderRadius: '38rpx', position: 'relative', zIndex: '1', lineHeight: '1' }">
|
||
<text :style="{ fontSize: '27rpx', fontWeight: '600', color: value.codeColor || '#FF6B6B' }">📤 {{ value.buttonText || '立即分享' }}</text>
|
||
</button>
|
||
<view v-else-if="value.showShareButton !== false" @tap="onShareClick" :style="{ marginTop: '16rpx', height: '76rpx', width: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center', background: '#FFFFFF', border: '2rpx solid ' + (nc.codeColor || '#FF6B6B'), borderRadius: '38rpx', position: 'relative', zIndex: '1', lineHeight: '1', boxSizing: 'border-box' }">
|
||
<text :style="{ fontSize: '27rpx', fontWeight: '600', color: nc.codeColor || '#FF6B6B' }">📤 {{ nc.buttonText || '立即分享' }}</text>
|
||
</view>
|
||
<!-- showLink -->
|
||
<view v-if="isFeatureOn(value.showLink)" style="margin-top:16rpx;text-align:center;position:relative;z-index:1;">
|
||
<text @tap="copyLink" style="font-size:22rpx;color:#999;text-decoration:underline;">{{ formatShareLink }}</text>
|
||
</view>
|
||
<!-- showQR -->
|
||
<view v-if="isFeatureOn(value.showQR)" style="margin-top:16rpx;display:flex;flex-direction:column;align-items:center;position:relative;z-index:1;">
|
||
<image :src="qrcodeRenderUrl" mode="aspectFit" style="width:200rpx;height:200rpx;" />
|
||
<text style="font-size:22rpx;color:#999;margin-top:8rpx;">{{ value.posterQrTip || '扫码进入并填写入驻信息' }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- ============ 预设 4:Horizontal 横向风格(全部内联样式,匹配参考图) ============ -->
|
||
<view v-if="stylePreset === 'horizontal'" :style="{ width: '100%', boxSizing: 'border-box', marginTop: '24rpx', background: '#FFFFFF', borderRadius: px2rpx(value.borderRadius || 8) + 'rpx', padding: px2rpx(value.padding || 16) + 'rpx ' + px2rpx(value.padding || 16) + 'rpx', position: 'relative', overflow: 'hidden', boxShadow: '0 4rpx 16rpx rgba(0,0,0,0.06)' }">
|
||
<!-- 卡片顶部:主标题 -->
|
||
<text :style="{ fontSize: px2rpx(value.titleFontSize || 17) + 'rpx', fontWeight: '800', color: value.titleColor || '#1A1A1A', display: 'block' }">{{ value.title || '我的推荐码' }}</text>
|
||
<!-- 内容主体框:浅橙细边框,极浅米白底色 -->
|
||
<view style="margin-top:20rpx;background:#FFFCF8;border:2rpx solid #FFE0B2;border-radius:20rpx;padding:24rpx 24rpx 20rpx;position:relative;overflow:hidden;">
|
||
<!-- 2.1 状态标签(左上角) -->
|
||
<view style="display:inline-flex;align-items:center;background:rgba(255,152,0,0.1);border-radius:20rpx;padding:4rpx 14rpx;gap:4rpx;">
|
||
<view style="width:28rpx;height:28rpx;border-radius:50%;background:#FF9800;display:flex;align-items:center;justify-content:center;">
|
||
<text style="font-size:16rpx;color:#FFFFFF;">✓</text>
|
||
</view>
|
||
<text style="font-size:20rpx;color:#FF8F00;font-weight:600;">已获得推荐码</text>
|
||
</view>
|
||
<!-- 2.2 礼品装饰插画(容器右上角) -->
|
||
<view style="position:absolute;top:-8rpx;right:-4rpx;z-index:2;">
|
||
<text style="font-size:64rpx;">🎁</text>
|
||
<!-- 粉色、浅橙色飘散碎片装饰 -->
|
||
<text style="position:absolute;top:-2rpx;left:-12rpx;font-size:12rpx;">✨</text>
|
||
<text style="position:absolute;top:24rpx;left:-18rpx;font-size:10rpx;opacity:0.6;">✨</text>
|
||
<text style="position:absolute;top:-6rpx;right:10rpx;font-size:8rpx;opacity:0.5;">⭐</text>
|
||
</view>
|
||
<!-- 2.3 推荐码核心文本(左对齐) -->
|
||
<view style="margin-top:20rpx;position:relative;z-index:1;">
|
||
<text :style="{ fontSize: px2rpx(Math.max(16, Math.min(48, (Number(value.codeFontSize) || 28) * 1.07))) + 'rpx', fontWeight: '900', color: value.codeColor || '#FF4444', letterSpacing: '2rpx', fontFamily: 'Courier New, monospace' }">{{ code || value.placeholderCode || 'ABCD1234' }}</text>
|
||
</view>
|
||
<!-- 2.4 + 2.5 按钮行:左复制、右分享,上下错落 -->
|
||
<view style="margin-top:16rpx;display:flex;align-items:flex-end;justify-content:space-between;gap:16rpx;position:relative;z-index:1;">
|
||
<!-- 左下角:复制推荐码按钮(描边圆角胶囊) -->
|
||
<view v-if="value.showCopyButton !== false" @tap="copyRecommendCode" :style="{ height: '64rpx', display: 'flex', justifyContent: 'center', alignItems: 'center', background: '#FFFFFF', border: '2rpx solid ' + (value.codeColor || '#FFB74D'), borderRadius: '32rpx', padding: '0 24rpx', gap: '6rpx' }">
|
||
<text style="font-size:22rpx;">📋</text>
|
||
<text :style="{ fontSize: '24rpx', color: value.codeColor || '#FF8F00', fontWeight: '600' }">{{ nc.copyButtonText || '复制推荐码' }}</text>
|
||
</view>
|
||
<!-- 右下角:立即分享链接按钮(黄橙渐变胶囊,靠右,触发微信原生分享)
|
||
H5 端用 view 触发,小程序/APP 端保留 button 以触发 open-type=share -->
|
||
<button v-if="value.showShareButton !== false && !isH5Platform" open-type="share" @tap="onShareClick" :style="{ height: '72rpx', display: 'flex', justifyContent: 'center', alignItems: 'center', background: 'linear-gradient(90deg,#FFC107 0%,#FF8F00 100%)', borderRadius: '36rpx', padding: '0 40rpx', boxShadow: '0 4rpx 12rpx rgba(255,143,0,0.3)', marginBottom: '-4rpx', border: 'none', lineHeight: '1' }">
|
||
<text :style="{ fontSize: '27rpx', fontWeight: '700', color: '#FFFFFF' }">📤 {{ value.buttonText || '立即分享' }}</text>
|
||
</button>
|
||
<view v-else-if="value.showShareButton !== false" @tap="onShareClick" :style="{ height: '72rpx', display: 'flex', justifyContent: 'center', alignItems: 'center', background: 'linear-gradient(90deg,#FFC107 0%,#FF8F00 100%)', borderRadius: '36rpx', padding: '0 40rpx', boxShadow: '0 4rpx 12rpx rgba(255,143,0,0.3)', marginBottom: '-4rpx', lineHeight: '1', boxSizing: 'border-box' }">
|
||
<text :style="{ fontSize: '27rpx', fontWeight: '700', color: '#FFFFFF' }">📤 {{ nc.buttonText || '立即分享' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<!-- 换一个新码 -->
|
||
<view v-if="value.allowRegenerate === true" style="margin-top:16rpx;display:flex;justify-content:center;">
|
||
<view @tap="regenerate" style="background:rgba(255,68,68,0.06);border-radius:24rpx;padding:8rpx 24rpx;">
|
||
<text style="font-size:22rpx;color:#FF4444;font-weight:600;">{{ value.placeholderRegenerateText || '换一个新码' }}</text>
|
||
</view>
|
||
</view>
|
||
<!-- showLink -->
|
||
<view v-if="isFeatureOn(value.showLink)" style="margin-top:16rpx;text-align:center;">
|
||
<text @tap="copyLink" style="font-size:22rpx;color:#999;text-decoration:underline;">{{ formatShareLink }}</text>
|
||
</view>
|
||
<!-- showQR -->
|
||
<view v-if="isFeatureOn(value.showQR)" style="margin-top:16rpx;display:flex;flex-direction:column;align-items:center;">
|
||
<image :src="qrcodeRenderUrl" mode="aspectFit" style="width:200rpx;height:200rpx;" />
|
||
<text style="font-size:22rpx;color:#999;margin-top:8rpx;">{{ value.posterQrTip || '扫码进入并填写入驻信息' }}</text>
|
||
</view>
|
||
</view>
|
||
</block>
|
||
</view>
|
||
<!-- 登录组件占位(ns-login 全局 mixin 已注册) -->
|
||
<ns-login ref="nsLoginBox" :popup="true" @getUserInfo="onLoginSuccess"></ns-login>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
/* =========================== 剪贴板双端工具(和之前保持一致,改名为 rc 前缀) =========================== */
|
||
function rcCopyToClipboard(vm, text, cb) {
|
||
if (!text) { cb && cb(false, '内容为空'); return; }
|
||
// #ifdef H5
|
||
try {
|
||
if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
|
||
navigator.clipboard.writeText(text).then(
|
||
() => cb && cb(true, null),
|
||
() => rcFallbackCopy(text, cb)
|
||
);
|
||
return;
|
||
}
|
||
} catch (e) {}
|
||
rcFallbackCopy(text, cb);
|
||
return;
|
||
// #endif
|
||
|
||
// #ifndef H5
|
||
uni.setClipboardData({
|
||
data: text,
|
||
showToast: false,
|
||
success: () => cb && cb(true, null),
|
||
fail: () => cb && cb(false, '复制失败')
|
||
});
|
||
// #endif
|
||
}
|
||
function rcFallbackCopy(text, cb) {
|
||
try {
|
||
const ta = document.createElement('textarea');
|
||
ta.value = text;
|
||
ta.readOnly = true;
|
||
ta.style.position = 'absolute';
|
||
ta.style.left = '-9999px';
|
||
ta.style.top = '-9999px';
|
||
document.body.appendChild(ta);
|
||
ta.select();
|
||
ta.setSelectionRange(0, text.length);
|
||
const ok = document.execCommand && document.execCommand('copy');
|
||
document.body.removeChild(ta);
|
||
cb && cb(ok ? true : false, ok ? null : '复制失败');
|
||
} catch (e) {
|
||
cb && cb(false, '复制失败');
|
||
}
|
||
}
|
||
|
||
/* =========================== 组件本体 =========================== */
|
||
import DiyMinx from './minx.js';
|
||
import golbalConfig from '@/common/js/golbalConfig.js';
|
||
export default {
|
||
name: 'DiyRecommendCode',
|
||
mixins: [DiyMinx, golbalConfig],
|
||
props: {
|
||
value: {
|
||
type: Object,
|
||
default: () => ({
|
||
/* ============== 内容配置,diy_view.php 默认值对齐) ============== */
|
||
title: '我的推荐码',
|
||
buttonText: '立即分享',
|
||
copyButtonText: '复制推荐码',
|
||
showButton: true,
|
||
showShareButton: true,
|
||
showCopyButton: true,
|
||
allowRegenerate: true,
|
||
placeholderRegenerateText: '换一个新码',
|
||
requireConsent: true,
|
||
consentText: '您的推荐码将用于企业入驻来源追踪,我们会记录每一次通过您分享链接完成的入驻申请,用于您的推广收益结算。',
|
||
consentButtonText: '同意并获取推荐码',
|
||
|
||
/* ============== 风格与样式 ============== */
|
||
stylePreset: 'gradient',
|
||
bgColorStart: '#3B82F6',
|
||
bgColorEnd: '#1E40AF',
|
||
bgColor: '#FFFFFF',
|
||
borderColor: '#E5E7EB',
|
||
accentColor: '#FFD700',
|
||
textColor: '#FFFFFF',
|
||
titleColor: '#1A1A1A',
|
||
codeColor: '#E4393C',
|
||
buttonBgColor: '', // 默认空:让 Card 风格的立即分享按钮走「白底红边框」分支,匹配设计参考图
|
||
buttonTextColor: '#FFFFFF',
|
||
shareButtonBgColor: '#67C23A',
|
||
titleFontSize: 14,
|
||
codeFontSize: 28,
|
||
borderRadius: 8,
|
||
padding: 16,
|
||
showQR: false,
|
||
showLink: false,
|
||
|
||
/* ============== 兼容旧字段(不破坏之前商家已经配置过的卡片) ============== */
|
||
// 外间距
|
||
marginTop: 20, marginBottom: 20,
|
||
// 未登录引导
|
||
loginIcon: '🔑', loginTipText: '登录后查看我的推荐码', loginTipColor: '#666666',
|
||
loginBtnText: '去登录', loginBtnBg: '#E4393C', loginBtnTextColor: '#FFFFFF',
|
||
loginBg: '#FFF5F5', loginRadius: 20, loginPaddingY: 40, loginPaddingX: 30,
|
||
posterQrTip: '扫码进入并填写入驻信息'
|
||
})
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
/* 接口返回的核心数据(完全后端驱动,不自己生成) */
|
||
code: '',
|
||
shareLink: '',
|
||
qrcodeUrl: '',
|
||
/* H5 平台标识:用于在 H5 端用 view 替代 button,避免浏览器 <button> 默认样式覆盖 */
|
||
isH5Platform: false,
|
||
/* 状态机 */
|
||
showConsent: false,
|
||
loadingCode: false,
|
||
codeLoaded: false,
|
||
|
||
__lastConsentRequired: false,
|
||
|
||
__cid: 'rc_' + Date.now() + '_' + Math.floor(Math.random() * 10000)
|
||
};
|
||
},
|
||
computed: {
|
||
nc() {
|
||
const raw = (this.value && typeof this.value === 'object') ? this.value : {};
|
||
const def = this.$options.props.value.default.call(this);
|
||
// 深合并:遍历 default 的所有 key,raw 里有就用 raw 的,否则用 default 的
|
||
var merged = {};
|
||
var allKeys = Object.keys(def || {});
|
||
// 同时把 raw 里新增的字段(后端可能新增配置)也加进去
|
||
Object.keys(raw || {}).forEach(function(k){ if (allKeys.indexOf(k) === -1) allKeys.push(k); });
|
||
allKeys.forEach(function(k){
|
||
if (raw && raw[k] !== undefined && raw[k] !== null && raw[k] !== '') {
|
||
merged[k] = raw[k];
|
||
} else {
|
||
merged[k] = def && def[k] !== undefined ? def[k] : raw[k];
|
||
}
|
||
});
|
||
return merged;
|
||
},
|
||
stylePreset() {
|
||
const s = String(this.nc.stylePreset || 'gradient').toLowerCase();
|
||
if (['minimal', 'card', 'bookgold', 'horizontal'].includes(s)) return s;
|
||
return 'gradient';
|
||
},
|
||
requireConsentComputed() {
|
||
return this.isFeatureOn(this.nc.requireConsent);
|
||
},
|
||
|
||
wrapStyle() {
|
||
const v = this.nc;
|
||
return [
|
||
'width:100%;box-sizing:border-box;',
|
||
v.marginTop != null ? `margin-top:${this.px2rpx(v.marginTop)}rpx;` : '',
|
||
v.marginBottom != null ? `margin-bottom:${this.px2rpx(v.marginBottom)}rpx;` : ''
|
||
].join('');
|
||
},
|
||
cardBaseStyle() {
|
||
const v = this.nc;
|
||
return [
|
||
'width:100%;',
|
||
'margin-top:24rpx;',
|
||
`padding:${this.px2rpx(v.padding)}rpx;`,
|
||
`border-radius:${this.px2rpx(v.borderRadius)}rpx;`,
|
||
'box-sizing:border-box;'
|
||
].join('');
|
||
},
|
||
loginWrapStyle() {
|
||
const v = this.nc;
|
||
return [
|
||
v.loginBg ? `background:${v.loginBg};` : '',
|
||
v.loginRadius != null ? `border-radius:${this.px2rpx(v.loginRadius)}rpx;` : '',
|
||
v.loginPaddingY != null ? `padding-top:${this.px2rpx(v.loginPaddingY)}rpx;padding-bottom:${this.px2rpx(v.loginPaddingY)}rpx;` : '',
|
||
v.loginPaddingX != null ? `padding-left:${this.px2rpx(v.loginPaddingX)}rpx;padding-right:${this.px2rpx(v.loginPaddingX)}rpx;` : '',
|
||
'width:100%;display:flex;align-items:center;justify-content:center;flex-wrap:nowrap;box-sizing:border-box;'
|
||
].join('');
|
||
},
|
||
loginBtnStyle() {
|
||
const v = this.nc;
|
||
return [
|
||
v.loginBtnBg ? `background:${v.loginBtnBg};` : '',
|
||
v.loginBtnTextColor ? `color:${v.loginBtnTextColor};` : '',
|
||
'padding:12rpx 28rpx;border-radius:40rpx;font-size:26rpx;margin-left:20rpx;'
|
||
].join('');
|
||
},
|
||
/* ---------- 三套容器样式 ---------- */
|
||
gradientStyle() {
|
||
const v = this.nc;
|
||
// 奶油米色背景
|
||
return this.cardBaseStyle + [
|
||
`background:#FDF6ED;`,
|
||
'box-shadow:0 12rpx 36rpx rgba(92,61,30,0.08);'
|
||
].join('');
|
||
},
|
||
/* 预设1专用:复制链接按钮白色背景样式 */
|
||
gradientCopyLinkStyle() {
|
||
const v = this.nc;
|
||
return [
|
||
`background:#FFFFFF;`,
|
||
`color:#5C3D1E;`,
|
||
`border-radius:40rpx;`,
|
||
`padding:14rpx 56rpx;`,
|
||
'font-size:26rpx;font-weight:600;',
|
||
'box-shadow:0 4rpx 12rpx rgba(92,61,30,0.1);'
|
||
].join('');
|
||
},
|
||
bookGoldStyle() {
|
||
const v = this.nc;
|
||
return this.cardBaseStyle + [
|
||
`background:#FFFFFF;`,
|
||
'box-shadow:0 12rpx 36rpx rgba(166,139,106,0.15);',
|
||
'border-radius:24rpx;'
|
||
,`border:3rpx solid #0066FF;`
|
||
].join('');
|
||
},
|
||
bookGoldCodeBoxStyle() {
|
||
const v = this.nc;
|
||
return [
|
||
`background:rgba(255,255,255,0.12);`,
|
||
`border-radius:${this.px2rpx(v.borderRadius)}rpx;`,
|
||
'margin-top:28rpx;'
|
||
].join('');
|
||
},
|
||
bookGoldCopyBtnStyle() {
|
||
const v = this.nc;
|
||
return [
|
||
`background:#E62E34;`, // 按钮红色背景,匹配图中大红按钮
|
||
`color:#ffffff;`, // 白色文字
|
||
`border-radius:999rpx;`, // 极致胶囊圆角(超大圆角)
|
||
`font-size:24rpx;`,
|
||
`font-weight:bold;`, // 文字加粗,和原图一致
|
||
`padding:22rpx 70rpx;`, // 上下左右内边距撑开按钮大小
|
||
`border:none;` // 清除默认边框
|
||
].join('');
|
||
},
|
||
minimalStyle() {
|
||
const v = this.nc;
|
||
return this.cardBaseStyle + [
|
||
`background:${v.bgColor || '#FDF6ED'};`,
|
||
`border:2rpx solid ${v.borderColor || '#E8D5C0'};`
|
||
].join('');
|
||
},
|
||
minimalCodeBoxStyle() {
|
||
const v = this.nc;
|
||
return [
|
||
`border:2rpx dashed ${v.codeColor || '#6B4423'};`,
|
||
`border-radius:${this.px2rpx(Math.max(4, (Number(v.borderRadius) || 8) - 4))}rpx;`,
|
||
'margin-top:28rpx;',
|
||
'display:flex;flex-direction:row;align-items:center;flex-wrap:nowrap;'
|
||
].join('');
|
||
},
|
||
minimalCopyBtnStyle() {
|
||
const v = this.nc;
|
||
return [
|
||
`background:${v.buttonBgColor || '#6B4423'};`,
|
||
`color:${v.buttonTextColor || '#FFFFFF'};`,
|
||
`border-radius:${this.px2rpx(v.borderRadius)}rpx;`,
|
||
`font-size:24rpx;padding:14rpx 24rpx;`
|
||
].join('');
|
||
},
|
||
cardStyleBox() {
|
||
const v = this.nc;
|
||
return this.cardBaseStyle + [
|
||
`background:${v.bgColor || '#FFE4B5'};`,
|
||
`border:2rpx solid ${v.accentColor || '#F0C040'};`,
|
||
'box-shadow:0 10rpx 30rpx rgba(0,0,0,0.08);',
|
||
'position:relative;overflow:hidden;'
|
||
].join('');
|
||
},
|
||
/* ---------- 推荐码文字样式 ---------- */
|
||
codeTextStyle() {
|
||
const v = this.nc;
|
||
return [
|
||
`color:${v.codeColor || (this.stylePreset === 'horizontal' ? '#FFFFFF' : (this.stylePreset === 'card' ? '#B8860B' : '#5C3D1E'))};`,
|
||
`font-size:${this.px2rpx(v.codeFontSize)}rpx;`,
|
||
`letter-spacing:${this.px2rpx(1)}rpx;`,
|
||
'font-weight:800;',
|
||
"font-family:'Courier New', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;"
|
||
].join('');
|
||
},
|
||
/* ---------- 按钮样式 ---------- */
|
||
|
||
secondaryBtnStyle() {
|
||
const v = this.nc;
|
||
const sp = this.stylePreset;
|
||
const secBg = sp === 'gradient' ? 'rgba(92,61,30,0.08)' : (sp === 'bookgold' ? 'rgba(255,255,255,0.12)' : (sp === 'card' ? 'rgba(184,134,11,0.08)' : 'rgba(92,61,30,0.06)'));
|
||
const secColor = sp === 'gradient' ? '#5C3D1E' : (sp === 'bookgold' ? '#FFFFFF' : (sp === 'card' ? '#B8860B' : '#5C3D1E'));
|
||
return [
|
||
`background:${secBg};`,
|
||
`color:${secColor};`,
|
||
`border-radius:${this.px2rpx(v.borderRadius)}rpx;`,
|
||
`padding:${this.px2rpx(Math.round((Number(v.padding) || 16) * 0.55))}rpx ${this.px2rpx(Math.round((Number(v.padding) || 16) * 0.9))}rpx;`,
|
||
'font-size:24rpx;font-weight:600;'
|
||
].join('');
|
||
},
|
||
consentBtnStyle() {
|
||
const v = this.nc;
|
||
return [
|
||
'background:linear-gradient(135deg, #FF7530 0%, #FF2438 100%);',
|
||
'color:#FFFFFF;',
|
||
`border-radius:${this.px2rpx(Math.max(12, Number(v.borderRadius) || 8))}rpx;`,
|
||
`padding:${this.px2rpx(Math.round((Number(v.padding) || 16) * 0.9))}rpx ${this.px2rpx(Math.round((Number(v.padding) || 16) * 2))}rpx;`,
|
||
'font-size:28rpx;font-weight:800;box-shadow:0 6rpx 16rpx rgba(255,138,110,0.25);'
|
||
].join('');
|
||
},
|
||
/* ---------- 二维码渲染 URL ---------- */
|
||
qrcodeRenderUrl() {
|
||
// 优先用后端占位接口返回的真实 qrcode_url;没返回就 fallback 用公共 api 合成 share_link 的二维码
|
||
if (this.qrcodeUrl && String(this.qrcodeUrl).trim()) return this.qrcodeUrl;
|
||
const realUrl = this.formatShareLink;
|
||
if (!realUrl) return '';
|
||
const url = encodeURIComponent(realUrl);
|
||
return 'https://api.qrserver.com/v1/create-qr-code/?size=300x300&margin=0&data=' + url;
|
||
},
|
||
formatShareLink() {
|
||
// 读取后台配置完整带{code}的模板
|
||
const template = this.nc.customShareLink || "http://192.168.8.118/hwappx/common/pages_tool/form/formdata?id=13&uniacid=1&recommend_code={code}";
|
||
// 把模板里 {code} 整体替换成 {推荐码}
|
||
return template.replace(/\{code\}/g, this.code);
|
||
},
|
||
},
|
||
watch: {
|
||
'storeToken'(nv) {
|
||
try {
|
||
if (nv) {
|
||
this._resetAll();
|
||
var vm = this;
|
||
this.$nextTick(function(){ try { vm.loadCode(false); } catch(e){} });
|
||
} else {
|
||
this._resetAll();
|
||
}
|
||
} catch (e) {
|
||
try { console.error('[RecommendDiy][watch.storeToken] 错误(已拦截):', e && e.message ? e.message : e); } catch(_) {}
|
||
}
|
||
},
|
||
'memberInfo.member_id'(nv) {
|
||
try {
|
||
if (nv) {
|
||
this._resetAll();
|
||
var vm2 = this;
|
||
this.$nextTick(function(){ try { vm2.loadCode(false); } catch(e){} });
|
||
}
|
||
} catch (e) {
|
||
try { console.error('[RecommendDiy][watch.memberInfo.member_id] 错误(已拦截):', e && e.message ? e.message : e); } catch(_) {}
|
||
}
|
||
}
|
||
},
|
||
errorCaptured(err, vm, info) {
|
||
try { console.error('[RecommendDiy][' + this.__cid + '][errorCaptured] 组件内部错误(已拦截):', err && err.message ? err.message : err, 'info=', info); } catch (e) {}
|
||
return false;
|
||
},
|
||
created() {
|
||
try {
|
||
// H5 端检测:避开 <button> 默认样式影响(h5 端改用 view 触发)
|
||
try {
|
||
// #ifdef H5
|
||
this.isH5Platform = true;
|
||
// #endif
|
||
} catch (e) {}
|
||
if (this.storeToken) {
|
||
this._resetAll();
|
||
var vm = this;
|
||
this.$nextTick(function(){ try { vm.loadCode(false); } catch(e){} });
|
||
}
|
||
} catch (e) {
|
||
try { console.error('[RecommendDiy][' + this.__cid + '][created] 初始化失败(已拦截):', e && e.message ? e.message : e); } catch(_) {}
|
||
}
|
||
},
|
||
mounted() {
|
||
// ✅ 关键调试日志:页面打开后直接去 F12 搜 "[RecommendDiy][mounted 就能看到组件是否真的渲染了、value 配置是什么、走 v-if 哪个分支会命中
|
||
try {
|
||
var cid = this.__cid;
|
||
var rawVal = this.value || {};
|
||
var mergedVal = this.nc || {};
|
||
var info = {
|
||
cid: cid,
|
||
storeToken: !!this.storeToken,
|
||
memberId: (this.memberInfo && this.memberInfo.member_id) ? this.memberInfo.member_id : null,
|
||
rawValueKeys: Object.keys(rawVal),
|
||
mergedTitle: mergedVal.title,
|
||
stylePreset: mergedVal.stylePreset,
|
||
componentName: rawVal.componentName,
|
||
componentId: rawVal.id,
|
||
requireConsentComputed: this.requireConsentComputed
|
||
};
|
||
console.log('[RecommendDiy][' + cid + '][mounted] 组件已渲染 ✓ 配置=', JSON.stringify(info));
|
||
if (!this.storeToken) {
|
||
console.log('[RecommendDiy][' + cid + '][mounted] 当前未登录,渲染「登录引导卡片(未登录占位)');
|
||
} else {
|
||
console.log('[RecommendDiy][' + cid + '][mounted] 已登录,等待 loadCode 接口返回后渲染推荐码卡片');
|
||
}
|
||
} catch (e) {
|
||
try { console.error('[RecommendDiy][mounted] 日志输出失败:', e && e.message ? e.message : e); } catch(_) {}
|
||
}
|
||
},
|
||
/* 小程序分享钩子(uni-app 支持组件内定义,作为页面级钩子的补充) */
|
||
onShareAppMessage() {
|
||
const v = this.nc;
|
||
const title = v.shareTitle || v.title || '邀您入驻低空经济产业平台';
|
||
return {
|
||
title,
|
||
path: this.extractMiniProgramPath(),
|
||
imageUrl: v.shareImageUrl || ''
|
||
};
|
||
},
|
||
onShareTimeline() {
|
||
const v = this.nc;
|
||
// 从H5分享链接中提取完整查询参数,与分享给好友保持一致
|
||
var query = '';
|
||
try {
|
||
var shareLink = this.formatShareLink;
|
||
var queryIndex = shareLink.indexOf('?');
|
||
if (queryIndex >= 0) {
|
||
query = shareLink.substring(queryIndex + 1); // id=13&uniacid=1&recommend_code=XXX
|
||
}
|
||
} catch(e) {}
|
||
if (!query && this.code) query = 'recommend_code=' + encodeURIComponent(this.code);
|
||
return {
|
||
title: v.shareTitle || v.title || '邀您入驻低空经济产业平台',
|
||
query: query,
|
||
imageUrl: v.shareImageUrl || ''
|
||
};
|
||
},
|
||
methods: {
|
||
|
||
mainBtnBgStyle(isCopyMini) {
|
||
const v = this.nc;
|
||
const sp = this.stylePreset;
|
||
const presetBtnColors = { gradient: '#FF9933', minimal: '#6B4423', card: '#DAA520', bookgold: '#B8A08A' };
|
||
const defaultBtnBg = presetBtnColors[sp] || '#FF9933';
|
||
const bg = isCopyMini ? 'rgba(255,255,255,0.92)' : (v.buttonBgColor || defaultBtnBg);
|
||
const color = isCopyMini ? (v.buttonBgColor || defaultBtnBg) : (v.buttonTextColor || '#FFFFFF');
|
||
return [
|
||
`background:${bg};`,
|
||
`color:${color};`,
|
||
`border-radius:${this.px2rpx(v.borderRadius)}rpx;`,
|
||
`padding:${this.px2rpx(Math.round((Number(v.padding) || 16) * 0.6))}rpx ${this.px2rpx(Math.round((Number(v.padding) || 16) * 1.2))}rpx;`,
|
||
'font-weight:700;font-size:26rpx;'
|
||
].join('');
|
||
},
|
||
px2rpx(val) {
|
||
return Number(val || 0) * 2
|
||
},
|
||
isFeatureOn(v) {
|
||
if (v === false || v === 0 || v === '0' || v === 'false' || v === null || v === undefined) return false;
|
||
return !!v;
|
||
},
|
||
_unwrapRecommendResponse(res) {
|
||
const cid = this.__cid;
|
||
var out = { ok: false, businessCode: -1, msg: '', data: {}, raw: res };
|
||
try {
|
||
if (!res) return out;
|
||
var r = res;
|
||
// 支持一层 data 包裹(如外层 {code:0, data:{...}} → 再检查 data 里是不是还有 code/data 结构)
|
||
var attempts = [
|
||
function(){ return r; }, // 最外层:直接用 r
|
||
function(){ return (r && r.data && typeof r.data === 'object') ? r.data : null; }, // 下钻一层:r.data
|
||
function(){ return (r && r.data && r.data.data && typeof r.data.data === 'object') ? r.data.data : null; }, // 下钻两层
|
||
function(){ return (r && r.result && typeof r.result === 'object') ? r.result : null; } // 兼容 .result
|
||
];
|
||
for (var i = 0; i < attempts.length; i++) {
|
||
var obj = attempts[i]();
|
||
if (!obj || typeof obj !== 'object') continue;
|
||
var codeKey = null;
|
||
if (typeof obj.code === 'number' || typeof obj.code === 'string') codeKey = 'code';
|
||
else if (typeof obj.status === 'number' || typeof obj.status === 'string') codeKey = 'status';
|
||
var messageKey = null;
|
||
if (typeof obj.msg === 'string') messageKey = 'msg';
|
||
else if (typeof obj.message === 'string') messageKey = 'message';
|
||
else if (typeof obj.info === 'string') messageKey = 'info';
|
||
|
||
// 规则:如果该层有 code/status 字段(业务码),就算是业务层
|
||
if (codeKey) {
|
||
var codeVal = Number(obj[codeKey]) || 0;
|
||
out.businessCode = codeVal;
|
||
if (messageKey) out.msg = String(obj[messageKey]);
|
||
if (codeVal === 0 || codeVal === 200) {
|
||
out.ok = true;
|
||
// data 优先级:obj.data > obj.result > 整个 obj(防止把 {code,msg,data} 里的 data 当外层当 data 用)
|
||
var d = null;
|
||
if (obj.data && typeof obj.data === 'object') d = obj.data;
|
||
else if (obj.result && typeof obj.result === 'object') d = obj.result;
|
||
else d = obj;
|
||
out.data = d && typeof d === 'object' ? d : {};
|
||
// 特殊处理:如果 data 里又有 data(后端双 data 包裹),再解一层
|
||
if (out.data.data && typeof out.data.data === 'object' && !out.data.code && !out.data.share_link) {
|
||
out.data = out.data.data;
|
||
}
|
||
try { console.log('[RecommendDiy][' + cid + '][解包] 命中第 ' + (i+1) + ' 层结构,businessCode=' + codeVal + ', data keys=', Object.keys(out.data)); } catch(e){}
|
||
return out;
|
||
}
|
||
} else if (typeof obj.code === 'string' || typeof obj.share_link === 'string') {
|
||
out.ok = true;
|
||
out.businessCode = 0;
|
||
out.msg = 'ok';
|
||
out.data = obj;
|
||
try { console.log('[RecommendDiy][' + cid + '][解包] 裸对象结构,直接返回 data keys=', Object.keys(obj)); } catch(e){}
|
||
return out;
|
||
}
|
||
}
|
||
|
||
try { console.warn('[RecommendDiy][' + cid + '][解包] ⚠️ 未能识别后端响应结构,raw=', JSON.stringify(res).slice(0, 500)); } catch(e){}
|
||
} catch (e) {
|
||
try { console.error('[RecommendDiy][' + cid + '][解包] 异常:', e && e.message ? e.message : e); } catch(_){}
|
||
}
|
||
return out;
|
||
},
|
||
|
||
/* ======================== 接口调用(核心 3 个) ======================== */
|
||
|
||
/**
|
||
* 调接口 1:/recommend/api/recommend/getMyCode
|
||
* HTTP Method 兼容:先按文档调 GET;GET 没拿到业务 code===0 的数据,自动 fallback 用 POST
|
||
* consent:false=首次加载,true=用户点击了同意按钮
|
||
* 后端返回(经万能解包):data.consent_required=true 弹同意遮罩;否则 data.code + data.share_link + data.qrcode_url
|
||
*/
|
||
loadCode(consent) {
|
||
const vm = this;
|
||
const cid = this.__cid;
|
||
if (!this.storeToken) {
|
||
try { console.log('[RecommendDiy][' + cid + '][loadCode] 跳过:未登录'); } catch(e){}
|
||
return;
|
||
}
|
||
if (this.loadingCode) return;
|
||
this.loadingCode = true;
|
||
|
||
var reqData = consent ? { consent: 1 } : {};
|
||
console.log('[RecommendDiy][' + cid + '][loadCode] 开始请求,consent=' + !!consent + ',params=', JSON.stringify(reqData));
|
||
|
||
var attempts = [
|
||
{ method: 'GET', label: 'GET(文档标准)' },
|
||
{ method: 'POST', label: 'POST(兜底兼容)' }
|
||
];
|
||
var attemptIdx = 0;
|
||
var lastMsg = '推荐码获取失败,请稍后重试';
|
||
|
||
function doOneAttempt() {
|
||
if (attemptIdx >= attempts.length) {
|
||
// 所有尝试都失败了
|
||
vm.loadingCode = false;
|
||
uni.showToast && uni.showToast({ title: lastMsg, icon: 'none', duration: 2800 });
|
||
return;
|
||
}
|
||
var meta = attempts[attemptIdx];
|
||
attemptIdx += 1;
|
||
try { console.log('[RecommendDiy][' + cid + '][loadCode] 尝试 ' + attemptIdx + '/' + attempts.length + ':' + meta.label); } catch(e){}
|
||
vm.$api.sendRequest({
|
||
url: '/recommend/api/recommend/getMyCode',
|
||
method: meta.method,
|
||
data: reqData,
|
||
async: vm.$api && vm.$api.async === false ? false : true,
|
||
success: function(res){
|
||
try { console.log('[RecommendDiy][' + cid + '][loadCode][' + meta.label + '] 原始响应=', JSON.stringify(res).slice(0, 1200)); } catch(e){}
|
||
var unpacked = vm._unwrapRecommendResponse(res);
|
||
if (!unpacked.ok) {
|
||
// 本次尝试业务失败(可能是 method 不对,或者 token 不对,或者 code!=0)
|
||
lastMsg = unpacked.msg ? String(unpacked.msg) : ('推荐码获取失败(业务码=' + unpacked.businessCode + ')');
|
||
try { console.warn('[RecommendDiy][' + cid + '][loadCode][' + meta.label + '] 业务失败:businessCode=' + unpacked.businessCode + ',msg=' + unpacked.msg + ',准备尝试下一个 method'); } catch(e){}
|
||
|
||
if (attemptIdx < attempts.length) {
|
||
doOneAttempt();
|
||
} else {
|
||
vm.loadingCode = false;
|
||
uni.showToast && uni.showToast({ title: lastMsg, icon: 'none', duration: 2800 });
|
||
}
|
||
return;
|
||
}
|
||
// ✅ 业务成功
|
||
var d = unpacked.data || {};
|
||
// Case 1:需要同意 + 组件配置了开启同意机制 + 不是同意按钮触发的(还没同意)
|
||
if (d.consent_required && !consent && vm.requireConsentComputed) {
|
||
try { console.log('[RecommendDiy][' + cid + '][loadCode] 命中:需要同意授权 → 弹 consent 遮罩'); } catch(e){}
|
||
vm.__lastConsentRequired = true;
|
||
vm.showConsent = true;
|
||
vm.code = '';
|
||
vm.shareLink = '';
|
||
vm.qrcodeUrl = '';
|
||
vm.codeLoaded = true;
|
||
vm.loadingCode = false;
|
||
return;
|
||
}
|
||
// Case 2:正常拿到推荐码
|
||
try { console.log('[RecommendDiy][' + cid + '][loadCode] 成功 ✓ code=', d.code, 'share_link=', d.share_link, 'qrcode_url=', d.qrcode_url ? '有' : '无'); } catch(e){}
|
||
vm.__lastConsentRequired = false;
|
||
vm.showConsent = false;
|
||
vm.code = d.code ? String(d.code) : '';
|
||
vm.shareLink = d.share_link ? String(d.share_link) : '';
|
||
vm.qrcodeUrl = d.qrcode_url ? String(d.qrcode_url) : '';
|
||
// 兼容旧字段名:后端可能返回 recommend_code / recommendShareLink 等
|
||
if (!vm.code && d.recommend_code) vm.code = String(d.recommend_code);
|
||
if (!vm.code && d.invite_code) vm.code = String(d.invite_code);
|
||
if (!vm.shareLink && d.share_url) vm.shareLink = String(d.share_url);
|
||
if (!vm.shareLink && d.link) vm.shareLink = String(d.link);
|
||
// 持久化:Vuex + storage,供 util.js 现场 banner 跳转自动拼 recommend_code 参数用
|
||
vm._persistRecommendInfo();
|
||
vm.codeLoaded = true;
|
||
vm.loadingCode = false;
|
||
if (consent) {
|
||
uni.showToast && uni.showToast({ title: '获取成功', icon: 'success' });
|
||
}
|
||
// 拿到码后如果 showQR=true,顺便调 getMyQrcode(占位接口)
|
||
try { vm.loadQrcodeIfNeeded(); } catch(e){}
|
||
},
|
||
fail: function(err){
|
||
var failMsg = (err && err.errMsg) ? String(err.errMsg) : '';
|
||
try { console.error('[RecommendDiy][' + cid + '][loadCode][' + meta.label + '] 网络失败:', failMsg, 'raw err=', err); } catch(e){}
|
||
lastMsg = failMsg ? ('网络异常:' + failMsg.slice(0, 30)) : '网络异常,请稍后重试';
|
||
// GET 网络失败也尝试 POST(有些服务器接口路由不支持 GET,只支持 POST)
|
||
if (attemptIdx < attempts.length) {
|
||
doOneAttempt();
|
||
} else {
|
||
vm.loadingCode = false;
|
||
uni.showToast && uni.showToast({ title: lastMsg, icon: 'none', duration: 2800 });
|
||
}
|
||
}
|
||
});
|
||
}
|
||
doOneAttempt();
|
||
},
|
||
/** 用户点击同意按钮 → consent=1 再调一次 getMyCode 拿真码 */
|
||
confirmConsent() {
|
||
this.loadCode(true);
|
||
},
|
||
/**
|
||
* 接口 2:POST /recommend/api/recommend/regenerateCode
|
||
*/
|
||
regenerate() {
|
||
const vm = this;
|
||
const cid = this.__cid;
|
||
if (!this.code) return;
|
||
uni.showModal({
|
||
title: '换新码',
|
||
content: '换新码后旧码会立即失效,后续所有使用旧分享链接的入驻申请都不会归属到您,确认继续?',
|
||
confirmText: '确认换新',
|
||
cancelText: '取消',
|
||
success: (r) => {
|
||
if (!r.confirm) return;
|
||
if (vm.loadingCode) return;
|
||
vm.loadingCode = true;
|
||
vm.$api.sendRequest({
|
||
url: '/recommend/api/recommend/regenerateCode',
|
||
method: 'POST',
|
||
data: {},
|
||
success: (res) => {
|
||
try { console.log('[RecommendDiy][' + cid + '][regenerate] 原始响应=', JSON.stringify(res).slice(0, 1200)); } catch(e){}
|
||
var unpacked = vm._unwrapRecommendResponse(res);
|
||
if (!unpacked.ok) {
|
||
vm.loadingCode = false;
|
||
uni.showToast && uni.showToast({
|
||
title: unpacked.msg ? String(unpacked.msg) : '换新失败,请稍后重试',
|
||
icon: 'none',
|
||
duration: 2200
|
||
});
|
||
return;
|
||
}
|
||
const d = unpacked.data || {};
|
||
vm.code = d.code ? String(d.code) : (d.recommend_code ? String(d.recommend_code) : (d.invite_code ? String(d.invite_code) : ''));
|
||
vm.shareLink = d.share_link ? String(d.share_link) : (d.share_url ? String(d.share_url) : (d.link ? String(d.link) : ''));
|
||
vm._persistRecommendInfo();
|
||
const old = d.old_code ? String(d.old_code) : '';
|
||
try { console.log('[RecommendDiy][' + cid + '][regenerate] 成功 ✓ 新码=', vm.code, '旧码=', old); } catch(e){}
|
||
uni.showToast && uni.showToast({
|
||
title: old ? ('已换新码,旧码 ' + old + ' 已停用') : '换新成功',
|
||
icon: 'none',
|
||
duration: 2200
|
||
});
|
||
vm.loadingCode = false;
|
||
},
|
||
fail: (err) => {
|
||
var failMsg = (err && err.errMsg) ? String(err.errMsg) : '';
|
||
try { console.error('[RecommendDiy][' + cid + '][regenerate] 网络失败:', failMsg); } catch(e){}
|
||
vm.loadingCode = false;
|
||
uni.showToast && uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
|
||
}
|
||
});
|
||
}
|
||
});
|
||
},
|
||
/** 【接口 5:占位】调 getMyQrcode(可选兜底,失败不影响 showQR,本组件会 fallback 到 qrserver) */
|
||
loadQrcodeIfNeeded() {
|
||
const vm = this;
|
||
if (!this.isFeatureOn(this.nc.showQR)) return;
|
||
if (this.qrcodeUrl) return;
|
||
if (!this.code) return;
|
||
try {
|
||
this.$api.sendRequest({
|
||
url: '/recommend/api/recommend/getMyQrcode',
|
||
method: 'GET',
|
||
data: {},
|
||
success: (res) => {
|
||
if (res && res.code === 0 && res.data && res.data.qrcode_url) {
|
||
vm.qrcodeUrl = String(res.data.qrcode_url);
|
||
}
|
||
}
|
||
});
|
||
} catch (e) {}
|
||
},
|
||
|
||
onShareClick() {
|
||
console.log('[推荐码分享]点击分享按钮,完整推广链接:', this.formatShareLink,'当前推荐码:',this.code);
|
||
const vm = this;
|
||
const v = this.nc;
|
||
const shareUrl = this.formatShareLink; // 改用后台拼接链接
|
||
// #ifdef H5
|
||
try {
|
||
if (navigator && navigator.share && typeof navigator.share && shareUrl) {
|
||
navigator.share({
|
||
title: v.shareTitle || v.title || '邀您入驻低空经济产业平台',
|
||
text: v.shareDesc || '',
|
||
url: shareUrl
|
||
}).catch(() => vm.copyLink());
|
||
return;
|
||
}
|
||
} catch (e) {}
|
||
this.copyLink();
|
||
return;
|
||
// #endif
|
||
|
||
// #ifdef APP-PLUS
|
||
try {
|
||
if (plus && plus.share && shareUrl) {
|
||
plus.share.sendWithSystem({
|
||
title: v.shareTitle || v.title || '邀您入驻低空经济产业平台',
|
||
content: v.shareDesc || '',
|
||
href: shareUrl
|
||
}, function () {}, function () { vm.copyLink(); });
|
||
return;
|
||
}
|
||
} catch (e) {}
|
||
this.copyLink();
|
||
// #endif
|
||
|
||
// #ifdef MP-WEIXIN
|
||
// open-type="share" 会自动触发 onShareAppMessage 弹出微信原生分享面板
|
||
// 存储分享数据供页面级 onShareAppMessage 读取(组件级钩子会被页面级覆盖)
|
||
console.log('分享链接准备,原始配置', v);
|
||
console.log('生成的分享完整路径:', this.extractMiniProgramPath());
|
||
|
||
try {
|
||
var shareData = {
|
||
title: v.shareTitle || v.title || '邀您入驻低空经济产业平台',
|
||
path: this.extractMiniProgramPath(),
|
||
imageUrl: v.shareImageUrl || ''
|
||
};
|
||
uni.setStorageSync('_recommendShareData', JSON.stringify(shareData));
|
||
} catch(e) {}
|
||
return;
|
||
// #endif
|
||
},
|
||
|
||
/** 复制「推荐码」本身(点击推荐码盒子时) */
|
||
copyRecommendCode() {
|
||
if (!this.code) {
|
||
uni.showToast && uni.showToast({ title: '推荐码还未生成,请稍候', icon: 'none' });
|
||
return;
|
||
}
|
||
rcCopyToClipboard(this, this.code, (ok, err) => {
|
||
uni.showToast && uni.showToast({
|
||
title: ok ? '推荐码已复制' : (err || '复制失败'),
|
||
icon: ok ? 'success' : 'none'
|
||
});
|
||
});
|
||
},
|
||
|
||
|
||
copyLink() {
|
||
// 直接读取后台配置拼接后的完整链接
|
||
const targetUrl = this.formatShareLink;
|
||
if (!targetUrl) {
|
||
uni.showToast({ title: '推荐码加载完成后再复制', icon: 'none' });
|
||
return;
|
||
}
|
||
rcCopyToClipboard(this, targetUrl, (ok, err) => {
|
||
uni.showToast({
|
||
title: ok ? '推广链接已复制' : (err || '复制失败'),
|
||
icon: ok ? 'success' : 'none'
|
||
});
|
||
});
|
||
},
|
||
extractMiniProgramPath() {
|
||
// 从H5分享链接中提取完整查询参数,保持小程序分享与H5链接参数一致
|
||
// H5链接如: http://xxx/hwappx/common/pages_tool/form/formdata?id=13&uniacid=1&recommend_code=XXX
|
||
// 小程序路径如: /pages_tool/form/formdata?id=13&uniacid=1&recommend_code=XXX
|
||
const shareLink = this.formatShareLink;
|
||
try {
|
||
var queryIndex = shareLink.indexOf('?');
|
||
if (queryIndex >= 0) {
|
||
var query = shareLink.substring(queryIndex); // ?id=13&uniacid=1&recommend_code=XXX
|
||
return '/pages_tool/form/formdata' + query;
|
||
}
|
||
} catch(e) {}
|
||
// 兜底:只带recommend_code
|
||
if (!this.code) return '/pages_tool/form/formdata';
|
||
return '/pages_tool/form/formdata?recommend_code=' + encodeURIComponent(this.code);
|
||
},
|
||
_persistRecommendInfo() {
|
||
try {
|
||
const sInfo = (this.$store && this.$store.state && this.$store.state.memberInfo) || {};
|
||
let stored = {};
|
||
try { stored = uni.getStorageSync('memberInfo') || {}; } catch (e) { stored = {}; }
|
||
const merged = Object.assign({}, typeof sInfo === 'object' ? sInfo : {}, typeof stored === 'object' ? stored : {}, {
|
||
recommend_code: this.code || '',
|
||
recommend_share_link: this.shareLink || ''
|
||
});
|
||
if (this.$store && this.$store.commit) {
|
||
try { this.$store.commit('setMemberInfo', merged); } catch (e) {}
|
||
}
|
||
try { uni.setStorageSync('memberInfo', merged); } catch (e) {}
|
||
} catch (e) {}
|
||
},
|
||
|
||
/** 登出 / 换账号时清空所有缓存状态 */
|
||
_resetAll() {
|
||
this.code = '';
|
||
this.shareLink = '';
|
||
this.qrcodeUrl = '';
|
||
this.showConsent = false;
|
||
this.loadingCode = false;
|
||
this.codeLoaded = false;
|
||
this.__lastConsentRequired = false;
|
||
},
|
||
|
||
/** ns-login 登录成功回调 → 拉一次 getMyCode */
|
||
onLoginSuccess() {
|
||
this._resetAll();
|
||
this.$nextTick(() => this.loadCode(false));
|
||
},
|
||
|
||
/** 点击未登录卡片 → 弹出微信手机号授权框(与会员中心登录一致) */
|
||
goLogin() {
|
||
if (this.$refs.nsLoginBox && this.$refs.nsLoginBox.open) {
|
||
this.$refs.nsLoginBox.open();
|
||
} else {
|
||
// 降级:ns-login 未就绪时跳转登录页
|
||
uni.navigateTo({
|
||
url: '/pages_tool/login/login',
|
||
fail: () => {
|
||
uni.redirectTo({ url: '/pages_tool/login/login' });
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.diy-recommend-code {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
|
||
/* ===== 未登录占位卡片 ===== */
|
||
.invite-login-tip {
|
||
width: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-wrap: nowrap;
|
||
box-sizing: border-box;
|
||
user-select: none;
|
||
cursor: pointer;
|
||
-webkit-tap-highlight-color: transparent;
|
||
.tip-icon { font-size: 40rpx; margin-right: 16rpx; }
|
||
.tip-text { font-size: 28rpx; flex: 1; }
|
||
.tip-btn { font-weight: 500; }
|
||
}
|
||
|
||
/* ===== 同意遮罩:分享推荐码 邀请有礼 ===== */
|
||
.consent-overlay {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
background: linear-gradient(180deg, #FFF9F5 0%, #FFF0F0 100%);
|
||
border-radius: 24rpx;
|
||
padding: 40rpx 32rpx 36rpx;
|
||
position: relative;
|
||
overflow: hidden;
|
||
|
||
/* 顶部标签 */
|
||
.consent-tag {
|
||
display: inline-block;
|
||
background: rgba(255,107,107,0.1);
|
||
border-radius: 20rpx;
|
||
padding: 6rpx 20rpx;
|
||
text { font-size: 22rpx; color: #FF6B6B; font-weight: 600; }
|
||
}
|
||
|
||
/* 主标题区 */
|
||
.consent-hero {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-top: 28rpx;
|
||
.consent-hero-text {
|
||
flex: 1;
|
||
.hero-main { font-size: 40rpx; font-weight: 800; color: #1A1A1A; display: block; }
|
||
.hero-accent {
|
||
font-size: 40rpx; font-weight: 800;
|
||
background: linear-gradient(90deg, #FF6B6B 0%, #FF9933 100%);
|
||
-webkit-background-clip: text;
|
||
background-clip: text;
|
||
color: #FF6B6B;
|
||
}
|
||
}
|
||
.consent-gift {
|
||
flex-shrink: 0;
|
||
margin-left: 20rpx;
|
||
.gift-emoji { font-size: 80rpx; }
|
||
}
|
||
}
|
||
|
||
/* 描述文字 */
|
||
.consent-desc {
|
||
margin-top: 20rpx;
|
||
text { font-size: 25rpx; color: #888; line-height: 1.7; }
|
||
}
|
||
|
||
/* 特性图标行 */
|
||
.consent-features {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-top: 36rpx;
|
||
padding: 0 4rpx;
|
||
.feature-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
flex: 1;
|
||
.feature-icon {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text { font-size: 36rpx; }
|
||
}
|
||
.feature-icon-1 { background: rgba(255,107,107,0.12); }
|
||
.feature-icon-2 { background: rgba(255,153,51,0.12); }
|
||
.feature-icon-3 { background: rgba(255,153,51,0.12); }
|
||
.feature-label { font-size: 21rpx; color: #666; margin-top: 12rpx; }
|
||
}
|
||
}
|
||
|
||
/* 按钮 */
|
||
.consent-action {
|
||
margin-top: 40rpx;
|
||
.consent-btn {
|
||
width: 100%;
|
||
height: 88rpx;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
background: linear-gradient(90deg, #FF6B6B 0%, #FF9933 100%);
|
||
border-radius: 44rpx;
|
||
text { font-size: 30rpx; font-weight: 700; color: #FFFFFF; }
|
||
user-select: none; cursor: pointer;
|
||
transition: opacity 0.2s ease, transform 0.15s ease;
|
||
-webkit-tap-highlight-color: transparent;
|
||
&:active { opacity: 0.92; transform: scale(0.98); }
|
||
&.btn-loading { opacity: 0.7; pointer-events: none; }
|
||
}
|
||
}
|
||
}
|
||
.loading-wrap {
|
||
width: 100%;
|
||
background: #F9FAFB;
|
||
min-height: 240rpx;
|
||
display: flex; align-items: center; justify-content: center;
|
||
.loading-text { font-size: 28rpx; }
|
||
}
|
||
|
||
/* ===== 通用:推荐码卡片各组件 ===== */
|
||
.rc-header {
|
||
width: 100%;
|
||
.rc-title { font-size: 34rpx; font-weight: 800; display: block; }
|
||
}
|
||
.rc-code-box {
|
||
margin-top: 30rpx;
|
||
background: rgba(255,255,255,0.16);
|
||
border-radius: 20rpx;
|
||
padding: 26rpx 30rpx;
|
||
display: flex; align-items: center; justify-content: space-between; flex-wrap: nowrap;
|
||
cursor: pointer; user-select: none; -webkit-tap-highlight-color: transparent;
|
||
transition: opacity 0.15s ease;
|
||
&:active { opacity: 0.88; }
|
||
.rc-prefix { font-size: 24rpx; margin-right: 20rpx; }
|
||
.rc-code { flex: 1; word-break: break-all; }
|
||
.rc-copy {
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
padding: 12rpx 26rpx;
|
||
border-radius: 40rpx;
|
||
display: inline-flex; justify-content: center; align-items: center;
|
||
flex-shrink: 0;
|
||
}
|
||
}
|
||
.rc-action-bar {
|
||
margin-top: 36rpx;
|
||
display: flex;
|
||
flex-direction: row;
|
||
gap: 16rpx;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
}
|
||
.rc-btn {
|
||
box-sizing: border-box;
|
||
display: inline-flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
text-align: center;
|
||
border: none;
|
||
outline: none;
|
||
line-height: 1.35;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
-webkit-tap-highlight-color: transparent;
|
||
transition: opacity 0.2s ease, transform 0.15s ease;
|
||
&:active { opacity: 0.9; transform: scale(0.98); }
|
||
&.rc-btn-share {
|
||
min-width: 220rpx;
|
||
flex: 0 0 auto;
|
||
}
|
||
}
|
||
button.rc-btn {
|
||
margin: 0 !important;
|
||
padding-left: 0 !important;
|
||
padding-right: 0 !important;
|
||
background: transparent;
|
||
overflow: hidden;
|
||
&::after { border: none; }
|
||
}
|
||
|
||
/* ===== 通用:分享链接 / 二维码 ===== */
|
||
.rc-link-row {
|
||
margin-top: 28rpx;
|
||
width: 100%;
|
||
.rc-link-text {
|
||
display: block;
|
||
width: 100%;
|
||
font-size: 22rpx;
|
||
line-height: 1.5;
|
||
word-break: break-all;
|
||
cursor: pointer;
|
||
opacity: 0.82;
|
||
padding: 18rpx 22rpx;
|
||
border-radius: 16rpx;
|
||
background: rgba(255,255,255,0.08);
|
||
}
|
||
&.rc-link-minimal .rc-link-text {
|
||
color: #8B7355;
|
||
background: rgba(92,61,30,0.04);
|
||
opacity: 1;
|
||
}
|
||
&.rc-link-card .rc-link-text {
|
||
color: #B8860B;
|
||
background: rgba(184,134,11,0.06);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
.rc-qr-row {
|
||
margin-top: 36rpx;
|
||
display: flex; flex-direction: column; align-items: center; gap: 14rpx;
|
||
.rc-qr-img {
|
||
width: 300rpx; height: 300rpx;
|
||
display: block;
|
||
background: #ffffff;
|
||
border-radius: 16rpx;
|
||
box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.1);
|
||
}
|
||
.rc-qr-tip {
|
||
font-size: 24rpx;
|
||
font-weight: 500;
|
||
line-height: 1.4;
|
||
text-align: center;
|
||
max-width: 420rpx;
|
||
}
|
||
&.rc-qr-minimal-img, &.rc-qr-card-img { border: 2rpx solid #eee; }
|
||
&.rc-qr-minimal {
|
||
align-items: flex-start;
|
||
gap: 12rpx;
|
||
.rc-qr-minimal-img { border: 2rpx solid #E8D5C0; }
|
||
.rc-qr-tip-minimal { text-align: left; max-width: 500rpx; }
|
||
}
|
||
&.rc-qr-card {
|
||
gap: 20rpx;
|
||
.rc-qr-wrap-card {
|
||
border-radius: 20rpx;
|
||
border: 4rpx solid #F0C040;
|
||
padding: 16rpx;
|
||
background: #ffffff;
|
||
.rc-qr-card-img { width: 260rpx; height: 260rpx; border-radius: 12rpx; }
|
||
}
|
||
.rc-qr-tip-card { max-width: 480rpx; }
|
||
}
|
||
}
|
||
|
||
/* ===== 预设 1:gradient ===== */
|
||
.style-gradient {
|
||
box-sizing: border-box;
|
||
width: 100%;
|
||
.rc-header .rc-title { color: #5C3D1E; }
|
||
.rc-code-gradient {
|
||
background: transparent;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 36rpx 30rpx;
|
||
.rc-code-gradient-text {
|
||
text-align: center;
|
||
letter-spacing: 4rpx;
|
||
}
|
||
}
|
||
.rc-copy-link-gradient {
|
||
margin-top: 24rpx;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
width: 100%;
|
||
}
|
||
.rc-link-row .rc-link-text {
|
||
background: rgba(92,61,30,0.08);
|
||
color: #5C3D1E;
|
||
}
|
||
}
|
||
|
||
/* ===== 预设 2:minimal ===== */
|
||
.style-minimal {
|
||
box-sizing: border-box;
|
||
width: 100%;
|
||
.rc-code-minimal {
|
||
display: flex !important;
|
||
flex-direction: row !important;
|
||
align-items: center !important;
|
||
justify-content: space-between !important;
|
||
flex-wrap: nowrap !important;
|
||
background: transparent;
|
||
border-style: dashed;
|
||
padding: 22rpx 26rpx;
|
||
gap: 16rpx;
|
||
|
||
.rc-code-minimal-text {
|
||
text-align: left;
|
||
letter-spacing: 4rpx;
|
||
line-height: 1;
|
||
flex: 1;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
.rc-copy-minimal {
|
||
font-size: 22rpx;
|
||
line-height: 1;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0 !important;
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
.rc-action-minimal { justify-content: space-between; }
|
||
}
|
||
/* ===== 预设 3:card 金色边框 ===== */
|
||
.style-card {
|
||
box-sizing: border-box;
|
||
width: 100%;
|
||
.rc-accent-bar {
|
||
position: absolute;
|
||
left: 0; top: 0; right: 0;
|
||
height: 8rpx;
|
||
}
|
||
.rc-header-card { margin-top: 8rpx; }
|
||
.rc-code-card {
|
||
background: rgba(184,134,11,0.04);
|
||
padding: 28rpx 32rpx;
|
||
gap: 16rpx;
|
||
border-radius: 20rpx;
|
||
.rc-copy-card {
|
||
padding: 14rpx 30rpx;
|
||
font-weight: 700;
|
||
font-size: 24rpx;
|
||
cursor: pointer;
|
||
transition: opacity 0.2s ease, transform 0.15s ease;
|
||
&:active { opacity: 0.9; transform: scale(0.98); }
|
||
}
|
||
}
|
||
.rc-action-card { justify-content: flex-start; }
|
||
}
|
||
|
||
/* ===== 预设 4:bookgold 白底风格 ===== */
|
||
.style-bookgold {
|
||
box-sizing: border-box;
|
||
width: 100%;
|
||
.rc-header-bookgold {
|
||
.rc-title {
|
||
color: #333333;
|
||
}
|
||
}
|
||
.rc-code-bookgold {
|
||
.rc-prefix {
|
||
color: rgba(92,61,30,0.7);
|
||
}
|
||
.rc-copy-bookgold {
|
||
background: #B8A08A;
|
||
color: #fff;
|
||
}
|
||
}
|
||
.rc-action-bookgold {
|
||
justify-content: flex-start;
|
||
.rc-btn-share-bookgold {
|
||
background: #B8A08A;
|
||
color: #fff;
|
||
}
|
||
}
|
||
.rc-link-bookgold {
|
||
.rc-link-text {
|
||
background: rgba(92,61,30,0.06);
|
||
color: #5C3D1E;
|
||
}
|
||
}
|
||
.rc-qr-bookgold {
|
||
.rc-qr-bookgold-img {
|
||
border: 3rpx solid #D4C4B0;
|
||
border-radius: 16rpx;
|
||
}
|
||
.rc-qr-tip-bookgold {
|
||
color: #5C3D1E;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|
||
|