Files
lucky_shop/components-diy/diy-recommend-code.vue
ZF sun 12e4493a17 style(diy-recommend-code): minimal 预设同步采用 100% 宽 + 居中结构
minimal 预设的"立即分享"按钮之前是 inline-block + padding 自适应宽度,
与其他预设不一致。改为和 gradient 预设一致的"双层结构":

- button path (小程序/APP):
  外层 button: position:relative; left:0; right:0;
  margin:16rpx 0 0 0; width:100%; display:block; box-sizing:border-box;
  内层 view: display:block; width:100%; height:76rpx;
  background:linear-gradient(90deg,#FF6B6B 0%,#FF4444 100%);
  border-radius:38rpx; box-shadow; text-align:center;

- view path (H5):
  外层 view: margin-top:16rpx; width:100%;
  内层 view: display:block; width:100%; height:76rpx;
  同上渐变样式

效果:
- 按钮 100% 卡片宽,与 gradient 一致
- 小程序 button 默认偏移问题(position:relative + left:0 + right:0)同步修复
- 商家在 4 个预设下看到一致的全宽分享按钮布局
2026-08-08 10:29:31 +08:00

1569 lines
74 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="{ 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;text-align:center;">
<view @tap="copyRecommendCode" style="display:inline-flex;align-items:center;background:#FFFFFF;border:2rpx solid #DC143C;border-radius:30rpx;padding:0 32rpx;height:60rpx;gap:8rpx;">
<text style="font-size:22rpx;">📋</text>
<text style="font-size:24rpx;color:#DC143C;font-weight: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="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="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="{ width: px2rpx(nc.qrSize || 200) + 'rpx', height: px2rpx(nc.qrSize || 200) + 'rpx', background: nc.qrBoxBackground || 'transparent', border: nc.qrBoxBorderWidth && nc.qrBoxBorderColor ? (px2rpx(nc.qrBoxBorderWidth) + 'rpx solid ' + nc.qrBoxBorderColor) : 'none', borderRadius: px2rpx(nc.qrBoxBorderRadius || 0) + 'rpx', padding: px2rpx(nc.qrBoxPadding || 0) + 'rpx', 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>
<!-- ============ 预设 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="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="{ width: px2rpx(nc.qrSize || 200) + 'rpx', height: px2rpx(nc.qrSize || 200) + 'rpx', background: nc.qrBoxBackground || 'transparent', border: nc.qrBoxBorderWidth && nc.qrBoxBorderColor ? (px2rpx(nc.qrBoxBorderWidth) + 'rpx solid ' + nc.qrBoxBorderColor) : 'none', borderRadius: px2rpx(nc.qrBoxBorderRadius || 0) + 'rpx', padding: px2rpx(nc.qrBoxPadding || 0) + 'rpx', 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="{ 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="margin-top:16rpx;text-align:center;background:transparent;border:none;padding:0;line-height:normal;">
<view style="display:inline-block;height:76rpx;line-height:76rpx;padding:0 40rpx;background:#FFFFFF;border:2rpx solid #FF6B6B;border-radius:38rpx;box-sizing:border-box;">
<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;text-align:center;">
<view style="display:inline-block;height:76rpx;line-height:76rpx;padding:0 40rpx;background:#FFFFFF;border:2rpx solid #FF6B6B;border-radius:38rpx;box-sizing:border-box;">
<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="{ width: px2rpx(nc.qrSize || 200) + 'rpx', height: px2rpx(nc.qrSize || 200) + 'rpx', background: nc.qrBoxBackground || 'transparent', border: nc.qrBoxBorderWidth && nc.qrBoxBorderColor ? (px2rpx(nc.qrBoxBorderWidth) + 'rpx solid ' + nc.qrBoxBorderColor) : 'none', borderRadius: px2rpx(nc.qrBoxBorderRadius || 0) + 'rpx', padding: px2rpx(nc.qrBoxPadding || 0) + 'rpx', 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="{ 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="text-align:center;background:transparent;border:none;padding:0;line-height:normal;display:inline-block;">
<view style="display:inline-block;height:72rpx;line-height:72rpx;padding:0 40rpx;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: '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;">
<view :style="{ width: px2rpx(nc.qrSize || 200) + 'rpx', height: px2rpx(nc.qrSize || 200) + 'rpx', background: nc.qrBoxBackground || 'transparent', border: nc.qrBoxBorderWidth && nc.qrBoxBorderColor ? (px2rpx(nc.qrBoxBorderWidth) + 'rpx solid ' + nc.qrBoxBorderColor) : 'none', borderRadius: px2rpx(nc.qrBoxBorderRadius || 0) + 'rpx', padding: px2rpx(nc.qrBoxPadding || 0) + 'rpx', 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>
</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,
// 二维码容器样式(与后端 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'].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>