Files
lucky_shop/components-diy/diy-recommend-code.vue

1987 lines
98 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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="$util.objToStyle({ fontSize: px2rpx(value.titleFontSize || 17) + 'px', 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="$util.objToStyle({ width: 'calc(100% - 28rpx)', boxSizing: 'border-box', marginTop: '24rpx', marginLeft: 'auto', marginRight: 'auto', background: '#FFFFFF', borderRadius: px2rpx(value.borderRadius || 8) + 'px', padding: px2rpx(value.padding || 16) + 'px ' + px2rpx(value.padding || 16) + 'px', 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="font-size:30rpx;">🔑</text>
<text :style="$util.objToStyle({ fontSize: px2rpx(value.titleFontSize || 17) + 'px', fontWeight: '800', color: value.titleColor || '#1A1A1A' })">{{ value.title || '我的推荐码' }}</text>
</view>
<!-- 推荐码区域:浅蓝虚线框 + 右侧蓝色方形复制按钮 -->
<view style="margin-top:24rpx;display:flex;align-items:center;gap:16rpx;position:relative;z-index:1;">
<view style="flex:1;display:flex;align-items:center;justify-content:center;height:124rpx;background:#F0F7FF;border:4rpx dashed #3B82F6;border-radius:24rpx;box-sizing:border-box;">
<text :style="$util.objToStyle({ fontSize: px2rpx(Math.max(20, Math.min(40, Number(value.codeFontSize) || 28))) + 'px', fontWeight: '900', color: value.codeColor || '#2563EB', letterSpacing: '2rpx', fontFamily: 'Courier New, monospace' })">{{ code || value.placeholderCode || 'HK-8888' }}</text>
</view>
<view v-if="value.showCopyButton !== false" @tap="copyRecommendCode" style="flex-shrink:0;width:96rpx;height:96rpx;display:flex;align-items:center;justify-content:center;background:linear-gradient(180deg,#3B82F6 0%,#2563EB 100%);border-radius:20rpx;box-shadow:0 4rpx 12rpx rgba(37,99,235,0.3);">
<text style="font-size:44rpx;color:#FFFFFF;">📋</text>
</view>
</view>
<!-- 提示文字 -->
<view style="margin-top:16rpx;display:flex;align-items:flex-start;gap:8rpx;position:relative;z-index:1;">
<text style="font-size:24rpx;color:#F59E0B;flex-shrink:0;">💡</text>
<text style="font-size:24rpx;color:#999999;line-height:1.5;flex:1;">{{ value.tipText || '复制推荐码分享给合作企业,对方填表时填入即可绑定' }}</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;">
<view :style="$util.objToStyle({ width: px2rpx(nc.qrSize || 200) + 'px', height: px2rpx(nc.qrSize || 200) + 'px', background: nc.qrBoxBackground || 'transparent', border: nc.qrBoxBorderWidth && nc.qrBoxBorderColor ? (px2rpx(nc.qrBoxBorderWidth) + 'px solid ' + nc.qrBoxBorderColor) : 'none', borderRadius: px2rpx(nc.qrBoxBorderRadius || 0) + 'px', padding: px2rpx(nc.qrBoxPadding || 0) + 'px', boxSizing: 'border-box' })">
<image :src="qrcodeRenderUrl" mode="aspectFit" style="width:100%;height:100%;display:block;" />
</view>
<text style="font-size:22rpx;color:#999;margin-top:12rpx;">{{ value.posterQrTip || '扫码进入并填写入驻信息' }}</text>
</view>
</view>
<!-- 分享推荐链接按钮:深蓝渐变,左侧图标 + 文字,右侧“复制链接”小按钮(独立于白色卡片外) -->
<button v-if="stylePreset === 'gradient' && value.showShareButton !== false && !isH5Platform" open-type="share" @tap="onShareClick" style="position:relative;left:0;right:0;margin:24rpx auto 0 auto;width:calc(100% - 28rpx);text-align:left;background:transparent;border:0;padding:0;line-height:normal;display:block;box-sizing:border-box;">
<view style="display:flex;align-items:center;justify-content:space-between;width:100%;height:100rpx;background:linear-gradient(90deg,#3B82F6 0%,#1E40AF 100%);border-radius:20rpx;padding:0 24rpx;box-shadow:0 4rpx 16rpx rgba(29,78,216,0.35);box-sizing:border-box;">
<view style="display:flex;align-items:center;gap:12rpx;">
<text style="font-size:36rpx;color:#FFFFFF;">📤</text>
<text style="font-size:28rpx;font-weight:700;color:#FFFFFF;">{{ value.buttonText || '分享推荐链接给企业' }}</text>
</view>
<view @tap.stop="copyLink" style="flex-shrink:0;height:52rpx;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,0.2);border-radius:26rpx;padding:0 20rpx;">
<text style="font-size:24rpx;color:#FFFFFF;">复制链接</text>
</view>
</view>
</button>
<view v-else-if="stylePreset === 'gradient' && value.showShareButton !== false" @tap="onShareClick" style="margin:24rpx auto 0 auto;width:calc(100% - 28rpx);">
<view style="display:flex;align-items:center;justify-content:space-between;width:100%;height:100rpx;background:linear-gradient(90deg,#3B82F6 0%,#1E40AF 100%);border-radius:20rpx;padding:0 24rpx;box-shadow:0 4rpx 16rpx rgba(29,78,216,0.35);box-sizing:border-box;">
<view style="display:flex;align-items:center;gap:12rpx;">
<text style="font-size:36rpx;color:#FFFFFF;">📤</text>
<text style="font-size:28rpx;font-weight:700;color:#FFFFFF;">{{ nc.buttonText || '分享推荐链接给企业' }}</text>
</view>
<view @tap.stop="copyLink" style="flex-shrink:0;height:52rpx;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,0.2);border-radius:26rpx;padding:0 20rpx;">
<text style="font-size:24rpx;color:#FFFFFF;">复制链接</text>
</view>
</view>
</view>
<!-- ============ 预设 2:Minimal 极简风格(全部内联样式) ============ -->
<view v-if="stylePreset === 'minimal'" :style="$util.objToStyle({ width: '100%', boxSizing: 'border-box', marginTop: '24rpx', background: 'linear-gradient(90deg,#FFFFFF 0%,#FFF8F8 30%,#FFF5F5 60%,#FFF0F0 100%)', borderRadius: px2rpx(value.borderRadius || 8) + 'px', padding: px2rpx(value.padding || 16) + 'px ' + px2rpx(value.padding || 16) + 'px', 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="$util.objToStyle({ fontSize: px2rpx(value.titleFontSize || 17) + 'px', 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="$util.objToStyle({ fontSize: px2rpx(Math.max(16, Math.min(48, (Number(value.codeFontSize) || 28) * 0.86))) + 'px', 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="$util.objToStyle(copyBtnStyleObj({width:'fit-content',height:'30px',display:'flex',justifyContent:'center',alignItems:'center',borderRadius:'15px',padding:'0 16px',gap:'4px'}))">
<text style="font-size:22rpx;">📋</text>
<text :style="$util.objToStyle(copyBtnStyleObj({}, true))">{{ 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="position:relative;left:0;right:0;margin:16rpx 0 0 0;width:100%;text-align:left;background:transparent;border:0;padding:0;line-height:normal;display:block;box-sizing:border-box;">
<view style="display:block;width:100%;height:76rpx;line-height:76rpx;background:linear-gradient(90deg,#FF6B6B 0%,#FF4444 100%);border-radius:38rpx;box-shadow:0 4rpx 12rpx rgba(255,68,68,0.3);box-sizing:border-box;text-align:center;">
<text style="font-size:27rpx;font-weight:700;color:#FFFFFF;">📤 {{ value.buttonText || '立即分享' }}</text>
</view>
</button>
<view v-else-if="value.showShareButton !== false" @tap="onShareClick" style="margin-top:16rpx;width:100%;">
<view style="display:block;width:100%;height:76rpx;line-height:76rpx;background:linear-gradient(90deg,#FF6B6B 0%,#FF4444 100%);border-radius:38rpx;box-shadow:0 4rpx 12rpx rgba(255,68,68,0.3);box-sizing:border-box;text-align:center;">
<text style="font-size:27rpx;font-weight:700;color:#FFFFFF;">📤 {{ nc.buttonText || '立即分享' }}</text>
</view>
</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;">
<view :style="$util.objToStyle({ width: px2rpx(nc.qrSize || 200) + 'px', height: px2rpx(nc.qrSize || 200) + 'px', background: nc.qrBoxBackground || 'transparent', border: nc.qrBoxBorderWidth && nc.qrBoxBorderColor ? (px2rpx(nc.qrBoxBorderWidth) + 'px solid ' + nc.qrBoxBorderColor) : 'none', borderRadius: px2rpx(nc.qrBoxBorderRadius || 0) + 'px', padding: px2rpx(nc.qrBoxPadding || 0) + 'px', boxSizing: 'border-box' })">
<image :src="qrcodeRenderUrl" mode="aspectFit" style="width:100%;height:100%;display:block;" />
</view>
<text style="font-size:22rpx;color:#999;margin-top:8rpx;">{{ value.posterQrTip || '扫码进入并填写入驻信息' }}</text>
</view>
</view>
<!-- ============ 预设 3:Card 卡片风格(全部内联样式) ============ -->
<view v-if="stylePreset === 'card'" :style="$util.objToStyle({ width: '100%', boxSizing: 'border-box', marginTop: '24rpx', background: 'linear-gradient(180deg,#FFF9F5 0%,#FFF5F0 100%)', borderRadius: px2rpx(value.borderRadius || 8) + 'px', padding: px2rpx(value.padding || 16) + 'px ' + px2rpx(value.padding || 16) + 'px', 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="$util.objToStyle({ fontSize: px2rpx(value.titleFontSize || 17) + 'px', 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="$util.objToStyle({ fontSize: px2rpx(Math.max(16, Math.min(48, (Number(value.codeFontSize) || 28) * 0.71))) + 'px', 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="$util.objToStyle(copyBtnStyleObj({width:'fit-content',height:'30px',display:'flex',justifyContent:'center',alignItems:'center',borderRadius:'15px',padding:'0 16px',gap:'4px'}))">
<text style="font-size:22rpx;">📋</text>
<text :style="$util.objToStyle(copyBtnStyleObj({}, true))">{{ 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="position:relative;left:0;right:0;margin:16rpx 0 0 0;width:100%;text-align:left;background:transparent;border:0;padding:0;line-height:normal;display:block;box-sizing:border-box;">
<view style="display:block;width:100%;height:76rpx;line-height:76rpx;background:#FFFFFF;border:2rpx solid #FF6B6B;border-radius:38rpx;box-sizing:border-box;text-align:center;">
<text style="font-size:27rpx;font-weight:600;color:#FF6B6B;">📤 {{ value.buttonText || '立即分享' }}</text>
</view>
</button>
<view v-else-if="value.showShareButton !== false" @tap="onShareClick" style="margin-top:16rpx;width:100%;">
<view style="display:block;width:100%;height:76rpx;line-height:76rpx;background:#FFFFFF;border:2rpx solid #FF6B6B;border-radius:38rpx;box-sizing:border-box;text-align:center;">
<text style="font-size:27rpx;font-weight:600;color:#FF6B6B;">📤 {{ nc.buttonText || '立即分享' }}</text>
</view>
</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;">
<view :style="$util.objToStyle({ width: px2rpx(nc.qrSize || 200) + 'px', height: px2rpx(nc.qrSize || 200) + 'px', background: nc.qrBoxBackground || 'transparent', border: nc.qrBoxBorderWidth && nc.qrBoxBorderColor ? (px2rpx(nc.qrBoxBorderWidth) + 'px solid ' + nc.qrBoxBorderColor) : 'none', borderRadius: px2rpx(nc.qrBoxBorderRadius || 0) + 'px', padding: px2rpx(nc.qrBoxPadding || 0) + 'px', boxSizing: 'border-box' })">
<image :src="qrcodeRenderUrl" mode="aspectFit" style="width:100%;height:100%;display:block;" />
</view>
<text style="font-size:22rpx;color:#999;margin-top:8rpx;">{{ value.posterQrTip || '扫码进入并填写入驻信息' }}</text>
</view>
</view>
<!-- ============ 预设 4:Horizontal 横向风格(全部内联样式,匹配参考图) ============ -->
<view v-if="stylePreset === 'horizontal'" :style="$util.objToStyle({ width: '100%', boxSizing: 'border-box', marginTop: '24rpx', background: '#FFFFFF', borderRadius: px2rpx(value.borderRadius || 8) + 'px', padding: px2rpx(value.padding || 16) + 'px ' + px2rpx(value.padding || 16) + 'px', position: 'relative', overflow: 'hidden', boxShadow: '0 4rpx 16rpx rgba(0,0,0,0.06)' })">
<!-- 卡片顶部:主标题 -->
<text :style="$util.objToStyle({ fontSize: px2rpx(value.titleFontSize || 17) + 'px', 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="$util.objToStyle({ fontSize: px2rpx(Math.max(16, Math.min(48, (Number(value.codeFontSize) || 28) * 1.07))) + 'px', 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:center;justify-content:space-between;gap:16rpx;padding:0 8rpx;position:relative;z-index:1;">
<!-- 左下角:复制推荐码按钮(描边圆角胶囊,按内容自适应) -->
<view v-if="value.showCopyButton !== false" @tap="copyRecommendCode" :style="$util.objToStyle(copyBtnStyleObj({height:'32px',display:'inline-flex',justifyContent:'center',alignItems:'center',borderRadius:'16px',padding:'0 12px',gap:'3px'}))">
<text style="font-size:22rpx;">📋</text>
<text :style="$util.objToStyle(copyBtnStyleObj({}, true))">{{ nc.copyButtonText || '复制推荐码' }}</text>
</view>
<!-- 右下角:立即分享链接按钮(黄橙渐变胶囊,按内容自适应,触发微信原生分享)
H5 端用 view 触发,小程序/APP 端保留 button 以触发 open-type=share -->
<button v-if="value.showShareButton !== false && !isH5Platform" open-type="share" @tap="onShareClick" style="position:relative;left:0;right:0;margin:0;padding:0;text-align:center;background:transparent;border:0;line-height:normal;display:inline-block;">
<view style="display:inline-block;height:72rpx;line-height:72rpx;padding:0 32rpx;background:linear-gradient(90deg,#FFC107 0%,#FF8F00 100%);border-radius:36rpx;box-shadow:0 4rpx 12rpx rgba(255,143,0,0.3);box-sizing:border-box;">
<text style="font-size:27rpx;font-weight:700;color:#FFFFFF;">📤 {{ value.buttonText || '立即分享' }}</text>
</view>
</button>
<view v-else-if="value.showShareButton !== false" @tap="onShareClick" style="height:72rpx;display:inline-flex;justify-content:center;align-items:center;background:linear-gradient(90deg,#FFC107 0%,#FF8F00 100%);border-radius:36rpx;box-shadow:0 4rpx 12rpx rgba(255,143,0,0.3);padding:0 32rpx;line-height:1;box-sizing:border-box;">
<text style="font-size:27rpx;font-weight: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;">
<view :style="$util.objToStyle({ width: px2rpx(nc.qrSize || 200) + 'px', height: px2rpx(nc.qrSize || 200) + 'px', background: nc.qrBoxBackground || 'transparent', border: nc.qrBoxBorderWidth && nc.qrBoxBorderColor ? (px2rpx(nc.qrBoxBorderWidth) + 'px solid ' + nc.qrBoxBorderColor) : 'none', borderRadius: px2rpx(nc.qrBoxBorderRadius || 0) + 'px', padding: px2rpx(nc.qrBoxPadding || 0) + 'px', boxSizing: 'border-box' })">
<image :src="qrcodeRenderUrl" mode="aspectFit" style="width:100%;height:100%;display:block;" />
</view>
<text style="font-size:22rpx;color:#999;margin-top:8rpx;">{{ value.posterQrTip || '扫码进入并填写入驻信息' }}</text>
</view>
</view>
<!-- ============ 预设 5(样式5):直接采用用户提供的代码,原样新增 ============ -->
<view v-if="stylePreset === 'style5'" :style="$util.objToStyle({ width: '100%', boxSizing: 'border-box', marginTop: '24rpx', background: '#FFFFFF', borderRadius: px2rpx(value.borderRadius || 8) + 'px', padding: px2rpx(value.padding || 16) + 'px ' + px2rpx(value.padding || 16) + 'px', 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="$util.objToStyle({ fontSize: px2rpx(value.titleFontSize || 17) + 'px', 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="$util.objToStyle({ fontSize: px2rpx(Math.max(16, Math.min(48, (Number(value.codeFontSize) || 28) * 0.93))) + 'px', 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;text-align:center;">
<view @tap="copyRecommendCode" :style="$util.objToStyle(copyBtnStyleObj({display:'inline-flex',alignItems:'center',borderRadius:'15px',padding:'0 16px',height:'30px',gap:'4px'}))">
<text style="font-size:22rpx;">📋</text>
<text :style="$util.objToStyle(copyBtnStyleObj({}, true))">{{ nc.copyButtonText || '复制推荐码' }}</text>
</view>
</view>
</view>
<!-- 立即分享链接按钮:黄橙渐变,胶囊形圆角,紧凑布局
固定走 linear-gradient,不读 diyData.buttonBgColor(避免被后端默认色覆盖)
H5 端用 view 触发(避免 button 默认样式),小程序/APP 端保留 button 以触发 open-type=share -->
<button v-if="stylePreset === 'style5' && value.showShareButton !== false && !isH5Platform" open-type="share" @tap="onShareClick" style="position:relative;left:0;right:0;margin:16rpx 0 0 0;width:100%;text-align:left;background:transparent;border:0;padding:0;line-height:normal;display:block;box-sizing:border-box;">
<view style="display:block;width:100%;height:80rpx;line-height:80rpx;background:linear-gradient(90deg,#FFC107 0%,#FF8F00 100%);border-radius:40rpx;box-shadow:0 4rpx 12rpx rgba(255,143,0,0.3);box-sizing:border-box;text-align:center;">
<text style="font-size:28rpx;font-weight:700;color:#FFFFFF;">📤 {{ value.buttonText || '立即分享' }}</text>
</view>
</button>
<view v-else-if="stylePreset === 'style5' && value.showShareButton !== false" @tap="onShareClick" style="margin-top:16rpx;width:100%;">
<view style="display:block;height:80rpx;line-height:80rpx;background:linear-gradient(90deg,#FFC107 0%,#FF8F00 100%);border-radius:40rpx;box-shadow:0 4rpx 12rpx rgba(255,143,0,0.3);box-sizing:border-box;text-align:center;">
<text style="font-size:28rpx;font-weight:700;color:#FFFFFF;">📤 {{ nc.buttonText || '立即分享' }}</text>
</view>
</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;">
<view :style="$util.objToStyle({ width: px2rpx(nc.qrSize || 200) + 'px', height: px2rpx(nc.qrSize || 200) + 'px', background: nc.qrBoxBackground || 'transparent', border: nc.qrBoxBorderWidth && nc.qrBoxBorderColor ? (px2rpx(nc.qrBoxBorderWidth) + 'px solid ' + nc.qrBoxBorderColor) : 'none', borderRadius: px2rpx(nc.qrBoxBorderRadius || 0) + 'px', padding: px2rpx(nc.qrBoxPadding || 0) + 'px', boxSizing: 'border-box' })">
<image :src="qrcodeRenderUrl" mode="aspectFit" style="width:100%;height:100%;display:block;" />
</view>
<text style="font-size:22rpx;color:#999;margin-top:12rpx;">{{ value.posterQrTip || '扫码进入并填写入驻信息' }}</text>
</view>
</view>
</block>
</view>
<!-- 登录组件占位(ns-login 全局 mixin 已注册) -->
<ns-login ref="nsLoginBox" :popup="true" @getUserInfo="onLoginSuccess"></ns-login>
<!-- ============ 分享海报 canvas(隐藏,仅小程序分享时生成推荐码组件的截图) ============ -->
<canvas v-if="!isH5Platform" canvas-id="sharePosterCanvas" id="sharePosterCanvas"
style="position:fixed;left:-9999px;top:-9999px;width:375px;height:480px;z-index:-1;"></canvas>
</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,
// 二维码容器样式(与后端 diyData / design.html 对齐)
qrSize: 200, // 二维码图片尺寸(前端 rpx)
qrBoxBorderColor: '', // 容器边框色;空=不渲染边框
qrBoxBorderWidth: 0, // 边框宽度(rpx)
qrBoxBorderRadius: 0, // 容器圆角(rpx)
qrBoxBackground: '', // 容器背景色;空=透明
qrBoxPadding: 0, // 容器内边距(rpx)
/* ============== 兼容旧字段(不破坏之前商家已经配置过的卡片) ============== */
// 外间距
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', 'style5'].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 || '邀您入驻低空经济产业平台';
// 小程序环境下,生成「仅推荐码组件」的截图海报作为分享图
// H5 平台不进入 canvas 路径,直接用 diyData.shareImageUrl
if (!this.isH5Platform) {
return new Promise((resolve) => {
// 先生成海报;失败或非小程序时回退到默认 imageUrl
this.generateSharePoster()
.then((posterPath) => {
resolve({
title,
path: this.extractMiniProgramPath(),
imageUrl: posterPath || (v.shareImageUrl || '')
});
})
.catch(() => {
resolve({
title,
path: this.extractMiniProgramPath(),
imageUrl: v.shareImageUrl || ''
});
});
});
}
return {
title,
path: this.extractMiniProgramPath(),
imageUrl: v.shareImageUrl || ''
};
},
/**
* 小程序分享到朋友圈(uniapp 生命周期钩子,必须在 export default 对象上,不能进 methods)
*/
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('');
},
/**
* 复制按钮样式对象(跟随后端 diyData.codeColor)
* 返回对象,用 $util.objToStyle 转字符串,
* 兼容 H5/小程序/APP/鸿蒙
* 注意:使用 px 而非 rpx 单位,避免鸿蒙元服务对动态 style 中 rpx
* 解析异常的问题。1rpx = 0.5px,直接换算成 px 数值。
* @param {Object} preset 预设差异(可选,如 {borderRadius:'16px',height:'32px'})
* @param {Boolean} textOnly 只返回文字样式(用于内嵌 text 节点)
*/
copyBtnStyleObj(preset, textOnly) {
const v = this.nc;
const codeColor = (v && v.codeColor) ? v.codeColor : '#DC143C';
if (textOnly) {
return Object.assign({
fontSize: '12px', // 原 24rpx → 12px
color: codeColor,
fontWeight: '600'
}, preset || {});
}
const base = {
background: '#FFFFFF',
border: `1px solid ${codeColor}`, // 原 2rpx → 1px
boxSizing: 'border-box'
};
return Object.assign(base, preset || {});
},
px2rpx(val) {
// 后端 diyData 数值语义是 px(商家在设计器按 px 输入,后端 wap/design.html
// 也都用 px 输出)。前端之前 *2 转 rpx 是错的,商家设置 8px 实际渲染
// 成 16px 偏大。鸿蒙元服务对动态拼接的 rpx 也有解析问题,所以这里
// 直接返回原值,作为 px 使用。
// 调用方:动态拼接时用 + 'px'(不要用 + 'px')。
return Number(val || 0)
},
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' });
}
// 拿到码后立即预生成分享海报(不等二维码,先占位;二维码拿到后会再次更新)
try { vm._prepareSharePoster(); } catch(e){}
// 拿到码后如果 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);
}
// 拿到二维码后立即预生成「仅推荐码组件」分享海报
// 这样用户点"立即分享"时,storage 里已有海报,页面级 onShareAppMessage
// 同步读取后返回 imageUrl,微信就不会用全屏截图
try { vm._prepareSharePoster(); } catch(e) {}
}
});
} catch (e) {}
},
/**
* 预生成分享海报(异步,完成后写入 _recommendShareData storage)
* 小程序端:onShareAppMessage 读取这个 storage 拿到 imageUrl
* 非小程序端:跳过,onShareClick 不依赖它
*/
_prepareSharePoster() {
const vm = this;
console.log('[推荐码分享] _prepareSharePoster 被调用,isH5Platform=', this.isH5Platform, 'code=', this.code);
// 仅小程序需要这个(其他端 onShareClick 不依赖 storage)
// #ifdef MP-WEIXIN
if (!this.code) {
console.warn('[推荐码分享] 跳过预生成:推荐码为空');
return;
}
// 安全:isH5Platform 为 true 时不进入(H5 下 canvas 不渲染,createCanvasContext 会失败)
if (this.isH5Platform) {
console.log('[推荐码分享] H5 环境跳过海报预生成');
return;
}
try {
console.log('[推荐码分享] 开始调 generateSharePoster...');
this.generateSharePoster().then(function(posterPath) {
console.log('[推荐码分享] generateSharePoster resolved, posterPath=', posterPath);
if (!posterPath) {
console.warn('[推荐码分享] 海报路径为空,跳过');
return;
}
try {
uni.setStorageSync('_recommendSharePoster', posterPath);
const data = {
title: (vm.nc && (vm.nc.shareTitle || vm.nc.title)) || '邀您入驻低空经济产业平台',
path: vm.extractMiniProgramPath(),
imageUrl: posterPath
};
uni.setStorageSync('_recommendShareData', JSON.stringify(data));
console.log('[推荐码分享] 海报预生成完成,imageUrl=', posterPath);
} catch(e) {
console.error('[推荐码分享] 写 storage 失败:', e);
}
}).catch(function(err) {
console.warn('[推荐码分享] 海报预生成失败', err);
});
} catch(e) {
console.error('[推荐码分享] _prepareSharePoster try 块异常:', e);
}
// #endif
},
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());
// 先用上次缓存的海报路径占位(避免用户快速二次分享时空白)
var cachedPoster = '';
try { cachedPoster = uni.getStorageSync('_recommendSharePoster') || ''; } catch(e) {}
try {
var shareData = {
title: v.shareTitle || v.title || '邀您入驻低空经济产业平台',
path: this.extractMiniProgramPath(),
imageUrl: cachedPoster || v.shareImageUrl || ''
};
uni.setStorageSync('_recommendShareData', JSON.stringify(shareData));
} catch(e) {}
// 异步生成「仅推荐码组件」的海报图,生成后更新 storage
// 微信端:onShareAppMessage 触发时 _recommendShareData 中已是新海报
this.generateSharePoster().then(function(posterPath) {
if (!posterPath) return;
try {
// 缓存本次海报,下次分享时直接用
uni.setStorageSync('_recommendSharePoster', posterPath);
var data = {
title: v.shareTitle || v.title || '邀您入驻低空经济产业平台',
path: vm.extractMiniProgramPath(),
imageUrl: posterPath
};
uni.setStorageSync('_recommendShareData', JSON.stringify(data));
console.log('[推荐码分享]海报已生成并写入 storage', posterPath);
} catch(e) {}
}).catch(function(err) {
console.warn('[推荐码分享]海报生成失败,使用 fallback', err);
});
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' });
}
});
}
},
/**
* 生成推荐码组件的分享海报(canvas 绘制 → 临时文件路径)
* 画布内容:白底 + 标题 + 推荐码大字 + 二维码(如有) + 简短提示
* 返回 Promise<tempFilePath>
*/
generateSharePoster() {
const v = this.nc;
const code = this.code || v.placeholderCode || 'ABCD1234';
const title = v.title || '我的推荐码';
const sp = this.stylePreset;
// ★ 所有视觉参数都从 diyData.value 读取,后端可改
const palette = {
bg: v.bgColor || '#FFFFFF',
bgStart: v.bgColorStart, // 渐变起始(可选)
bgEnd: v.bgColorEnd, // 渐变结束(可选)
border: v.borderColor || '#E5E7EB',
borderRadius: Number(v.borderRadius) || 12,
padding: Number(v.padding) || 16,
titleColor: v.titleColor || '#1A1A1A',
titleFontSize: Number(v.titleFontSize) || 18,
codeColor: v.codeColor || '#DC143C',
codeFontSize: Number(v.codeFontSize) || 36,
accent: v.accentColor || '#FFD700',
text: v.textColor || '#303133',
subtitle: '#999999',
codeBlockBg: '#FFF8F0', // 推荐码区背景(米黄)
codeBlockBorder: '#FFB74D'
};
const W = 375;
const H = 480;
console.log('[推荐码分享] generateSharePoster 进入,stylePreset=', sp);
return new Promise((resolve, reject) => {
try {
const ctx = uni.createCanvasContext('sharePosterCanvas', this);
if (!ctx) return reject(new Error('createCanvasContext 失败'));
console.log('[推荐码分享] createCanvasContext 返回');
// ====================== 绘制海报 ======================
// 1. 整张背景(根据后端 bgColorStart/End 渐变,否则用 bgColor 单色)
this._drawPosterBg(ctx, W, H, palette);
// 2. 卡片(根据 stylePreset 选择布局)
this._drawPosterCard(ctx, W, H, palette, sp);
// 3. 标题(读 titleColor/titleFontSize)
ctx.setFontSize(palette.titleFontSize);
ctx.setFillStyle(palette.titleColor);
ctx.setTextAlign('left');
ctx.fillText(title, 24, 44);
// 4. 副标题(根据后端配置,可选)
const showLink = this.isFeatureOn(v.showLink);
const showQR = this.isFeatureOn(v.showQR);
ctx.setFontSize(12);
ctx.setFillStyle(palette.subtitle);
ctx.fillText('分享给好友,邀请入驻获得奖励', 24, 70);
// 4.5 如果后端开启 showLink,绘制分享链接
let urlY = 88;
if (showLink) {
try {
ctx.setFontSize(11);
ctx.setFillStyle(palette.text || '#666');
ctx.setTextAlign('left');
// 缩短显示:最多 30 字符
let linkText = (v.shareLink || this.shareLink || '').toString();
if (linkText.length > 36) linkText = linkText.slice(0, 36) + '…';
ctx.fillText('链接:' + (linkText || '(未生成)'), 24, urlY);
urlY += 16; // 后续内容往下挪
} catch (e) {}
}
// 5. 推荐码区域(读 codeColor/codeFontSize/borderRadius)
// blockY 根据 showLink 动态下移
const blockY = urlY, blockH = 110, blockX = 24, blockW = W - 48;
const codeBlockRadius = Math.min(palette.borderRadius, 16);
// 推荐码区背景
ctx.setFillStyle(palette.codeBlockBg);
this._roundRect(ctx, blockX, blockY, blockW, blockH, codeBlockRadius);
ctx.fill();
// 推荐码区边框(浅橙)
ctx.setStrokeStyle(palette.codeBlockBorder);
ctx.setLineWidth(1);
this._roundRect(ctx, blockX, blockY, blockW, blockH, codeBlockRadius);
ctx.stroke();
// 推荐码大字(居中)
ctx.setFontSize(palette.codeFontSize);
ctx.setFillStyle(palette.codeColor);
ctx.setTextAlign('center');
if (typeof ctx.setFontWeight === 'function') {
ctx.setFontWeight('bold');
} else {
try { ctx.font = 'bold ' + palette.codeFontSize + 'px sans-serif'; } catch(e) {}
}
ctx.fillText(code, W / 2, blockY + blockH / 2 + 12);
// 6. 二维码(如果有)
const qr = this.qrcodeRenderUrl;
const drawQrcodeAndExport = (qrLocalPath) => {
if (qrLocalPath) {
const qrSize = 180;
const qrX = (W - qrSize) / 2;
const qrY = blockY + blockH + 24;
// 白底框
ctx.setFillStyle('#FFFFFF');
this._roundRect(ctx, qrX - 6, qrY - 6, qrSize + 12, qrSize + 12, codeBlockRadius);
ctx.fill();
ctx.drawImage(qrLocalPath, qrX, qrY, qrSize, qrSize);
// 底部提示
ctx.setFontSize(11);
ctx.setFillStyle(palette.subtitle);
ctx.setTextAlign('center');
ctx.fillText(v.posterQrTip || '扫码进入并填写入驻信息', W / 2, qrY + qrSize + 24);
}
console.log('[推荐码分享] 准备 ctx.draw(false, cb)');
ctx.draw(false, () => {
console.log('[推荐码分享] ctx.draw callback 触发,准备 _exportCanvas');
this._exportCanvas(W, H).then(resolve).catch(reject);
});
};
if (qr) {
this._downloadImage(qr).then(drawQrcodeAndExport).catch(() => drawQrcodeAndExport(null));
} else {
drawQrcodeAndExport(null);
}
} catch (e) {
console.error('[推荐码分享] generateSharePoster try 块异常:', e);
reject(e);
}
});
},
/**
* 海报背景:根据 diyData.bgColorStart/End 画渐变,否则用 bgColor 单色
*/
_drawPosterBg(ctx, W, H, palette) {
// 海报"画布外"背景:用 bgColor(浅色),不做整体渐变
try {
ctx.setFillStyle(palette.bg || '#FFFFFF');
ctx.fillRect(0, 0, W, H);
} catch (e) {
ctx.setFillStyle('#FFFFFF');
ctx.fillRect(0, 0, W, H);
}
},
/**
* 卡片绘制:画海报中部的小"卡片"(模仿组件的卡片视图)
* 根据 stylePreset 选不同视觉:
* - gradient: 卡片用 bgColorStart→bgColorEnd 渐变
* - minimal: 卡片用 bgColorStart→bgColorEnd 渐变(粉/米)
* - card: 卡片用 bgColorStart→bgColorEnd 渐变(米橙)
* - horizontal: 卡片用 bgColorStart→bgColorEnd 渐变
* 所有预设的卡片圆角读 borderRadius
*/
_drawPosterCard(ctx, W, H, palette, sp) {
// 不再画独立卡片,推荐码区本身就是"卡片"
// 这里只根据 stylePreset 在卡片右上角加 emoji 装饰
try {
ctx.setFontSize(36);
// 不同预设的装饰色 + emoji
const decos = {
gradient: { color: 'rgba(255,143,0,0.4)', emoji: '🎁' },
minimal: { color: 'rgba(255,107,107,0.35)', emoji: '🎁' },
card: { color: 'rgba(218,165,32,0.4)', emoji: '🎁' },
horizontal: { color: 'rgba(184,160,138,0.4)', emoji: '🎁' }
};
const deco = decos[sp] || decos.gradient;
ctx.setFillStyle(deco.color);
ctx.setTextAlign('right');
// 装饰位置:推荐码区右上角(blockX+W/2 区域,blockY 上方)
ctx.fillText(deco.emoji, W - 40, 78);
} catch (e) {}
},
/**
* canvas 转图片(uni.canvasToTempFilePath Promise 化)
*/
_exportCanvas(W, H) {
return new Promise((resolve, reject) => {
console.log('[推荐码分享] _exportCanvas 开始执行');
uni.canvasToTempFilePath({
canvasId: 'sharePosterCanvas',
width: W,
height: H,
destWidth: W * 2,
destHeight: H * 2,
fileType: 'png',
quality: 1,
success: (r) => {
console.log('[推荐码分享] canvasToTempFilePath 成功:', r.tempFilePath);
resolve(r.tempFilePath);
},
fail: (err) => {
console.error('[推荐码分享] canvasToTempFilePath 失败:', err);
reject(err);
}
}, this);
// 兜底超时:5s 后还没返回就 reject,避免永远卡住
setTimeout(() => {
console.warn('[推荐码分享] canvasToTempFilePath 超时');
reject(new Error('canvasToTempFilePath 超时'));
}, 5000);
});
},
/**
* 圆角矩形路径(canvas API)
*/
_roundRect(ctx, x, y, w, h, r) {
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
},
/**
* 下载图片到临时路径(Promise 化)
* 支持本地路径直接返回
*/
_downloadImage(url) {
return new Promise((resolve, reject) => {
if (!url) return reject(new Error('url 为空'));
if (!/^https?:\/\//i.test(url)) return resolve(url);
uni.downloadFile({
url,
success: (res) => {
if (res.statusCode === 200 && res.tempFilePath) resolve(res.tempFilePath);
else reject(new Error('下载失败:' + res.statusCode));
},
fail: reject
});
});
}
}
};
</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>