1295 lines
51 KiB
Vue
1295 lines
51 KiB
Vue
<template>
|
||
<view data-component-name="diy-recommend-code">
|
||
<!-- ============ 未登录:占位引导卡片(保持原有风格与逻辑一致) ============ -->
|
||
<view class="invite-login-tip" v-if="!storeToken" @tap="goLogin"
|
||
:style="loginWrapStyle">
|
||
<text class="tip-icon">{{ value.loginIcon || '🔑' }}</text>
|
||
<text class="tip-text" :style="{ color: value.loginTipColor || '#666666' }">
|
||
{{ value.loginTipText || '登录后查看我的推荐码' }}
|
||
</text>
|
||
<text class="tip-btn" :style="loginBtnStyle">
|
||
{{ value.loginBtnText || '去登录' }}
|
||
</text>
|
||
</view>
|
||
<!-- ============ 已登录:推荐码卡片(后端驱动,3 套预设 + consent 遮罩) ============ -->
|
||
<view v-else>
|
||
<!-- ============ Consent 同意遮罩(requireConsent 开启 + 后端返回 consent_required=true 才显示) ============ -->
|
||
<view v-if="showConsent" class="consent-overlay" :style="cardBaseStyle">
|
||
<view class="consent-header">
|
||
<text class="consent-title" :style="{ color: value.titleColor || value.textColor || '#333333' }">
|
||
{{ value.title || '我的推荐码' }}
|
||
</text>
|
||
</view>
|
||
<view class="consent-content">
|
||
<text class="consent-text" :style="{ color: value.textColor || '#666666' }">
|
||
{{ value.consentText || '您的推荐码将用于企业入驻来源追踪,我们会记录每一次通过您分享链接完成的入驻申请,用于您的推广收益结算。开通后可随时在页面停止使用。' }}
|
||
</text>
|
||
</view>
|
||
<view class="consent-action">
|
||
<view class="consent-btn" :style="consentBtnStyle" @tap="confirmConsent" :class="{ 'btn-loading': loadingCode }">
|
||
<text>{{ 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>
|
||
|
||
<!-- ============ 预设 1:Gradient 渐变风格(默认) ============ -->
|
||
<view v-if="stylePreset === 'gradient'" class="style-gradient" :style="gradientStyle">
|
||
<view class="rc-header">
|
||
<text class="rc-title" :style="{ color: value.titleColor || '#5C3D1E' }">{{ value.title || '我的推荐码' }}</text>
|
||
</view>
|
||
<view class="rc-code-box rc-code-gradient" @tap="copyRecommendCode" style="display:block;text-align:center;background:transparent;padding:36rpx 30rpx;">
|
||
<text class="rc-code rc-code-gradient-text" :style="codeTextStyle">{{ code || '暂无' }}</text>
|
||
</view>
|
||
<!-- 复制链接按钮:在推荐码下方居中,白色背景 -->
|
||
<view class="rc-copy-link-row rc-copy-link-gradient" style="margin-top:24rpx;display:block;text-align:center;width:100%;">
|
||
<view class="rc-btn rc-btn-copy rc-btn-copy-gradient" :style="gradientCopyLinkStyle" @tap="copyLink" :disabled="loadingCode">
|
||
<text>{{ value.placeholderCopyText || '立即分享链接' }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="rc-action-bar">
|
||
<view v-if="isFeatureOn(value.showButton)" class="rc-btn rc-btn-share"
|
||
:style="mainBtnBgStyle()" @tap="onShareClick" open-type="share">
|
||
<text>{{ value.buttonText || '立即分享' }}</text>
|
||
</view>
|
||
<view v-if="isFeatureOn(value.allowRegenerate)" class="rc-btn rc-btn-regenerate"
|
||
:style="secondaryBtnStyle" @tap="regenerate">
|
||
<text>{{ value.placeholderRegenerateText || '换一个新码' }}</text>
|
||
</view>
|
||
</view>
|
||
<!-- showLink:是否显示分享链接文字 -->
|
||
<view v-if="isFeatureOn(value.showLink)" class="rc-link-row">
|
||
<text class="rc-link-text" @tap="copyLink">{{ formatShareLink}}</text>
|
||
</view>
|
||
<!-- showQR:是否显示二维码 -->
|
||
<view v-if="isFeatureOn(value.showQR)" class="rc-qr-row">
|
||
<image :src="qrcodeRenderUrl" mode="aspectFit" class="rc-qr-img" />
|
||
<text class="rc-qr-tip" :style="{ color: 'rgba(92,61,30,0.8)' }">
|
||
{{ value.posterQrTip || '扫码进入并填写入驻信息' }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- ============ 预设 2:Minimal 极简风格(纯色 + 边框) ============ -->
|
||
<view v-if="stylePreset === 'minimal'" class="style-minimal" :style="minimalStyle">
|
||
<view class="rc-header">
|
||
<text class="rc-title" :style="{ color: value.titleColor || '#5C3D1E' }">{{ value.title || '我的推荐码' }}</text>
|
||
</view>
|
||
<view class="rc-code-box rc-code-minimal" @tap="copyRecommendCode" :style="minimalCodeBoxStyle">
|
||
<text class="rc-code rc-code-minimal-text" :style="codeTextStyle">{{ code || '暂无' }}</text>
|
||
<text class="rc-copy rc-copy-minimal" :style="minimalCopyBtnStyle" @tap.stop="copyLink">{{ value.placeholderCopyText || '立即分享链接' }}</text>
|
||
</view>
|
||
<view class="rc-action-bar rc-action-minimal">
|
||
<button v-if="isFeatureOn(value.showButton)" class="rc-btn rc-btn-share rc-btn-share-minimal"
|
||
:style="mainBtnBgStyle()" @tap="onShareClick" open-type="share">
|
||
<text>{{ value.buttonText || '立即分享' }}</text>
|
||
</button>
|
||
<view v-if="isFeatureOn(value.allowRegenerate)" class="rc-btn rc-btn-regenerate"
|
||
:style="secondaryBtnStyle" @tap="regenerate">
|
||
<text>{{ value.placeholderRegenerateText || '换一个新码' }}</text>
|
||
</view>
|
||
</view>
|
||
<view v-if="isFeatureOn(value.showLink)" class="rc-link-row">
|
||
<text class="rc-link-text rc-link-minimal" @tap="copyLink">{{ formatShareLink }}</text>
|
||
</view>
|
||
<view v-if="isFeatureOn(value.showQR)" class="rc-qr-row rc-qr-minimal">
|
||
<image :src="qrcodeRenderUrl" mode="aspectFit" class="rc-qr-img rc-qr-minimal-img" />
|
||
<text class="rc-qr-tip rc-qr-tip-minimal" :style="{ color: '#8B7355' }">
|
||
{{ value.posterQrTip || '扫码进入并填写入驻信息' }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- ============ 预设 3:Card 金色边框卡片风格 ============ -->
|
||
<view v-if="stylePreset === 'card'" class="style-card" :style="cardStyleBox">
|
||
<view class="rc-accent-bar" :style="{ background: value.accentColor || '#F0C040' }"></view>
|
||
<view class="rc-header rc-header-card">
|
||
<text class="rc-title" :style="{ color: value.titleColor || '#B8860B' }">{{ value.title || '我的推荐码' }}</text>
|
||
</view>
|
||
<view class="rc-code-box rc-code-card" @tap="copyRecommendCode" style="display:block;text-align:center;background:rgba(184,134,11,0.04);padding:36rpx 30rpx;border-radius:20rpx;">
|
||
<text class="rc-code" :style="codeTextStyle">{{ code || '暂无' }}</text>
|
||
</view>
|
||
<view style="margin-top:24rpx;display:block;text-align:center;width:100%;">
|
||
<view class="rc-btn rc-btn-copy rc-copy-card rc-copy-link-card" :style="{ background: value.accentColor || '#DAA520', color: '#FFFFFF' }" @tap.stop="copyLink">
|
||
<text>{{ value.placeholderCopyText || '立即分享链接' }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="rc-action-bar rc-action-card">
|
||
<button v-if="isFeatureOn(value.showButton)" class="rc-btn rc-btn-share"
|
||
:style="mainBtnBgStyle()" @tap="onShareClick" open-type="share">
|
||
<text>{{ value.buttonText || '立即分享' }}</text>
|
||
</button>
|
||
<view v-if="isFeatureOn(value.allowRegenerate)" class="rc-btn rc-btn-regenerate"
|
||
:style="secondaryBtnStyle" @tap="regenerate">
|
||
<text>{{ value.placeholderRegenerateText || '换一个新码' }}</text>
|
||
</view>
|
||
</view>
|
||
<view v-if="isFeatureOn(value.showLink)" class="rc-link-row rc-link-card">
|
||
<text class="rc-link-text" @tap="copyLink">{{ formatShareLink }}</text>
|
||
</view>
|
||
<view v-if="isFeatureOn(value.showQR)" class="rc-qr-row rc-qr-card">
|
||
<view class="rc-qr-wrap-card" :style="{ borderColor: value.accentColor || '#F0C040' }">
|
||
<image :src="qrcodeRenderUrl" mode="aspectFit" class="rc-qr-img rc-qr-card-img" />
|
||
</view>
|
||
<text class="rc-qr-tip rc-qr-tip-card" :style="{ color: '#B8860B' }">
|
||
{{ value.posterQrTip || '扫码进入并填写入驻信息' }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- ============ 预设 4:Bookgold 金棕风格 ============ -->
|
||
<view v-if="stylePreset === 'horizontal'" class="style-bookgold" :style="bookGoldStyle">
|
||
<view class="rc-header rc-header-bookgold">
|
||
<text class="rc-title" :style="{ color: value.titleColor || '#FFFFFF' }">{{ value.title || '我的推荐码' }}</text>
|
||
</view>
|
||
<view class="rc-code-box rc-code-bookgold" @tap="copyRecommendCode" :style="bookGoldCodeBoxStyle + 'display:block;text-align:center;'">
|
||
<text class="rc-code" :style="codeTextStyle">{{ code || '暂无' }}</text>
|
||
</view>
|
||
<view style="margin-top:24rpx;display:block;text-align:center;width:100%;">
|
||
<view class="rc-btn rc-btn-copy rc-copy-bookgold" :style="bookGoldCopyBtnStyle" @tap.stop="copyLink">
|
||
<text>{{ value.placeholderCopyText || '立即分享链接' }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="rc-action-bar rc-action-bookgold">
|
||
<view v-if="isFeatureOn(value.allowRegenerate)" class="rc-btn rc-btn-regenerate"
|
||
:style="secondaryBtnStyle" @tap="regenerate">
|
||
<text>{{ value.placeholderRegenerateText || '换一个新码' }}</text>
|
||
</view>
|
||
</view>
|
||
<!-- showLink:是否显示分享链接文字 -->
|
||
<view v-if="isFeatureOn(value.showLink)" class="rc-link-row rc-link-bookgold">
|
||
<text class="rc-link-text" @tap="copyLink">{{ formatShareLink}}</text>
|
||
</view>
|
||
<!-- showQR:是否显示二维码 -->
|
||
<view v-if="isFeatureOn(value.showQR)" class="rc-qr-row rc-qr-bookgold">
|
||
<image :src="qrcodeRenderUrl" mode="aspectFit" class="rc-qr-img rc-qr-bookgold-img" />
|
||
<text class="rc-qr-tip rc-qr-tip-bookgold" :style="{ color: '#E8DDD0' }">
|
||
{{ value.posterQrTip || '扫码进入并填写入驻信息' }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</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: '立即分享',
|
||
showButton: true,
|
||
allowRegenerate: true,
|
||
placeholderRegenerateText: '换一个新码',
|
||
placeholderCopyText: '复制链接',
|
||
placeholderShareText: '分享',
|
||
requireConsent: true,
|
||
consentText: '您的推荐码将用于企业入驻来源追踪,我们会记录每一次通过您分享链接完成的入驻申请,用于您的推广收益结算。',
|
||
consentButtonText: '同意并获取推荐码',
|
||
|
||
/* ============== 风格与样式 ============== */
|
||
stylePreset: 'gradient', // gradient / minimal / card
|
||
bgColorStart: '#3B82F6',
|
||
bgColorEnd: '#1E40AF',
|
||
bgColor: '#FFFFFF',
|
||
borderColor: '#E5E7EB',
|
||
accentColor: '#FFD700',
|
||
textColor: '#FFFFFF',
|
||
titleColor: '#333333',
|
||
codeColor: '#E4393C',
|
||
buttonBgColor: '#E4393C',
|
||
buttonTextColor: '#FFFFFF',
|
||
shareButtonBgColor: '#67C23A',
|
||
titleFontSize: 14, // px,前端转 rpx(×2)
|
||
codeFontSize: 28, // px,前端转 rpx(×2)
|
||
borderRadius: 8, // px → rpx(×2)
|
||
padding: 16, // px → rpx(×2)
|
||
showQR: false,
|
||
showLink: false,
|
||
|
||
/* ============== 兼容旧字段(不破坏之前商家已经配置过的卡片) ============== */
|
||
// 外间距
|
||
marginTop: 20, marginBottom: 20,
|
||
// 未登录引导
|
||
loginIcon: '🔑', loginTipText: '登录后查看我的推荐码', loginTipColor: '#666666',
|
||
loginBtnText: '去登录', loginBtnBg: '#E4393C', loginBtnTextColor: '#FFFFFF',
|
||
loginBg: '#FFF5F5', loginRadius: 20, loginPaddingY: 40, loginPaddingX: 30,
|
||
posterQrTip: '扫码进入并填写入驻信息'
|
||
})
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
/* 接口返回的核心数据(完全后端驱动,不自己生成) */
|
||
code: '',
|
||
shareLink: '',
|
||
qrcodeUrl: '',
|
||
/* 状态机 */
|
||
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 [
|
||
`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;'
|
||
].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:#B8A08A;`,
|
||
`color:#ffffff;`,
|
||
`border-radius:40rpx;`,
|
||
`font-size:24rpx;padding:10rpx 24rpx;`
|
||
].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, ${v.bgColorStart || '#FFD699'} 0%, ${v.bgColorEnd || '#FFB84D'} 100%);`,
|
||
`color:${v.buttonTextColor || '#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;'
|
||
].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 {
|
||
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;
|
||
return {
|
||
title: v.shareTitle || v.title || '邀您入驻低空经济产业平台',
|
||
query: this.code ? ('recommend_code=' + encodeURIComponent(this.code)) : '',
|
||
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() {
|
||
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
|
||
this.copyLink();
|
||
// #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() {
|
||
// 直接生成落地页路径,不需要解析后端废弃shareLink
|
||
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() {
|
||
const isPage = getCurrentPages().length > 0;
|
||
if (this.$refs && this.$refs.nsLoginBox) {
|
||
try {
|
||
if (typeof this.$refs.nsLoginBox.showLoginBox === 'function') {
|
||
this.$refs.nsLoginBox.showLoginBox();
|
||
return;
|
||
}
|
||
} catch (e) {}
|
||
}
|
||
uni.navigateTo && uni.navigateTo({
|
||
url: '/pages/login/login',
|
||
fail: () => { uni.redirectTo && uni.redirectTo({ url: '/pages/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; }
|
||
}
|
||
|
||
/* ===== 通用:同意遮罩 / loading ===== */
|
||
.consent-overlay {
|
||
width: 100%;
|
||
background: #FFFFFF;
|
||
box-shadow: 0 10rpx 30rpx rgba(0,0,0,0.08);
|
||
.consent-header {
|
||
.consent-title { font-size: 34rpx; font-weight: 700; display: block; margin-bottom: 8rpx; }
|
||
}
|
||
.consent-content {
|
||
margin-top: 28rpx;
|
||
background: rgba(0,0,0,0.04);
|
||
border-radius: 20rpx;
|
||
padding: 28rpx 28rpx;
|
||
.consent-text { font-size: 26rpx; line-height: 1.75; }
|
||
}
|
||
.consent-action {
|
||
margin-top: 40rpx;
|
||
display: flex;
|
||
justify-content: center;
|
||
.consent-btn {
|
||
display: inline-flex; justify-content: center; align-items: center;
|
||
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>
|
||
|