chore:认领码正常

This commit is contained in:
2026-10-16 10:52:27 +08:00
parent 2f750dbb99
commit d358bb55ba
2 changed files with 378 additions and 46 deletions

View File

@@ -51,6 +51,31 @@
<ns-empty v-if="!isLoading && list.length === 0" :isIndex="false"
:text="emptyText"></ns-empty>
</view>
<!-- 认领弹层: 支持领取码/名片编号 + 扫码带参预填(与 card_claim_api 文档同步) -->
<view v-if="showClaim" class="claim-mask" @tap="closeClaim">
<view class="claim-pop" @tap.stop>
<view class="claim-pop-title">认领名片</view>
<view class="claim-pop-tabs">
<view class="claim-tab" :class="{ active: claimMode === 'code' }" @tap="claimMode = 'code'">领取码</view>
<view class="claim-tab" :class="{ active: claimMode === 'id' }" @tap="claimMode = 'id'">名片编号</view>
</view>
<view class="claim-input-wrap">
<input class="claim-input" :value="claimInput"
:placeholder="claimMode === 'code' ? '输入 8 位领取码(大写字母数字)' : '输入名片编号'"
@input="onClaimInput" confirm-type="done" @confirm="submitClaim" />
<text v-if="claimInput" class="claim-clear" @tap="claimInput = ''">✕</text>
</view>
<view class="claim-scan" @tap="scanClaim">
<text class="iconfont icon-saomafu"></text>
<text>扫码认领(自动识别领取码 / 编号)</text>
</view>
<view class="claim-pop-actions">
<view class="claim-btn-cancel" @tap="closeClaim">取消</view>
<view class="claim-btn-confirm" @tap="submitClaim">确认认领</view>
</view>
</view>
</view>
</view>
</template>
@@ -66,10 +91,21 @@ export default {
keyword: '',
onlyFav: false,
isLoading: false,
emptyText: '暂无名片,可点击右上角「新建」或「认领」添加'
emptyText: '暂无名片,可点击右上角「新建」或「认领」添加',
// 认领弹层状态(与 card_claim_api 文档同步)
showClaim: false,
claimInput: '',
claimMode: 'code', // code=领取码(主通道), id=名片编号(旧入口兼容)
pendingClaimCode: '' // 扫码带参预填的码
};
},
onLoad() {
onLoad(options) {
// 扫码带参预填(文档 §5): 二维码/链接带 claim_code 或 card_id 直接进入认领
if (options && (options.claim_code || options.card_id)) {
this.pendingClaimCode = (options.claim_code || options.card_id || '')
.toUpperCase().replace(/\s+/g, '');
this.claimMode = options.claim_code ? 'code' : 'id';
}
this.refresh();
},
onPullDownRefresh() {
@@ -206,35 +242,78 @@ export default {
url: '/pages_tool/card/edit'
});
},
// 认领: 打开弹层(支持领取码/名片编号 + 扫码带参预填, 与 card_claim_api 文档同步)
onClaimTap() {
uni.showModal({
title: '认领名片',
editable: true,
placeholderText: '粘贴商家提供的名片编号(card_id)',
success: res => {
if (!res.confirm) return;
const cardId = (res.content || '').trim();
if (!cardId) {
uni.showToast({ title: '请输入名片编号', icon: 'none' });
this.claimInput = this.pendingClaimCode || '';
this.showClaim = true;
},
onClaimInput(e) {
// 输入框自动转大写并过滤空格(文档 §5 checklist)
this.claimInput = (e.detail.value || '').toUpperCase().replace(/\s+/g, '');
},
closeClaim() {
this.showClaim = false;
},
submitClaim() {
const code = (this.claimInput || '').trim().toUpperCase().replace(/\s+/g, '');
this.claimInput = code;
if (!code) {
uni.showToast({
title: this.claimMode === 'code' ? '请输入领取码' : '请输入名片编号',
icon: 'none'
});
return;
}
this.doClaim(code);
this.closeClaim();
},
// 扫码认领: 解析二维码(URL 参数或整串), 自动识别领取码/名片编号
scanClaim() {
// #ifndef H5
uni.scanCode({
success: r => {
const code = this.parseClaimParam(r.result);
if (!code) {
uni.showToast({ title: '二维码未包含领取码或名片编号', icon: 'none' });
return;
}
this.doClaim(cardId);
}
this.claimInput = code;
this.claimMode = /^\d+$/.test(code) ? 'id' : 'code';
this.doClaim(code);
this.closeClaim();
},
fail: () => {}
});
// #endif
// #ifdef H5
uni.showToast({ title: '请在微信/小程序内扫码', icon: 'none' });
// #endif
},
doClaim(cardId) {
parseClaimParam(val) {
const s = (val || '').trim();
if (!s) return '';
const m = s.match(/[?&](claim_code|card_id)=([^&]+)/i);
if (m) return decodeURIComponent(m[2]).toUpperCase().replace(/\s+/g, '');
return s.toUpperCase().replace(/\s+/g, '');
},
doClaim(code) {
uni.showLoading({ title: '认领中…' });
// 文档 §2: claim_code 主通道, card_id 旧入口兼容; 二者二选一
const payload = this.claimMode === 'code'
? { claim_code: code }
: { card_id: code };
this.$cardApi.cardClaim({
data: { card_id: cardId },
data: payload,
success: res => {
uni.hideLoading();
if (res.code == 0) {
const claimed = res.data && res.data.claimed === false;
// claimed=true=新认领; false=早已是自己的(幂等)
const isNew = res.data && res.data.claimed === true;
uni.showToast({
title: claimed ? '该名片已在您的名片夹中' : '认领成功',
title: isNew ? '认领成功' : '该名片已在您名下',
icon: 'success'
});
this.refresh();
this.refresh(); // 认领后与员工自建/扫描卡同池展示
} else {
this.handleApiError(res);
}
@@ -441,4 +520,122 @@ export default {
text-overflow: ellipsis;
white-space: nowrap;
}
/* 认领弹层(与 card_claim_api 文档同步) */
.claim-mask {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.45);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.claim-pop {
width: 600rpx;
background-color: #ffffff;
border-radius: 24rpx;
padding: 40rpx 36rpx;
box-sizing: border-box;
.claim-pop-title {
font-size: 34rpx;
font-weight: 600;
color: #1f2329;
text-align: center;
}
.claim-pop-tabs {
display: flex;
margin: 32rpx 0 24rpx;
background-color: #f3f4f6;
border-radius: 14rpx;
padding: 6rpx;
.claim-tab {
flex: 1;
text-align: center;
font-size: 28rpx;
color: #6b7280;
padding: 16rpx 0;
border-radius: 10rpx;
&.active {
background-color: #ffffff;
color: var(--main-color, #0054a5);
font-weight: 600;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
}
}
}
.claim-input-wrap {
display: flex;
align-items: center;
background-color: #f7f8fa;
border: 2rpx solid #e5e7eb;
border-radius: 14rpx;
padding: 0 20rpx;
height: 84rpx;
.claim-input {
flex: 1;
height: 84rpx;
font-size: 30rpx;
color: #1f2329;
letter-spacing: 2rpx;
}
.claim-clear {
color: #bbb;
font-size: 30rpx;
padding: 0 8rpx;
}
}
.claim-scan {
display: flex;
align-items: center;
justify-content: center;
margin-top: 24rpx;
color: var(--main-color, #0054a5);
font-size: 26rpx;
.iconfont {
margin-right: 10rpx;
font-size: 32rpx;
}
}
.claim-pop-actions {
display: flex;
margin-top: 36rpx;
.claim-btn-cancel,
.claim-btn-confirm {
flex: 1;
height: 84rpx;
line-height: 84rpx;
text-align: center;
border-radius: 14rpx;
font-size: 30rpx;
}
.claim-btn-cancel {
background-color: #f3f4f6;
color: #6b7280;
margin-right: 20rpx;
}
.claim-btn-confirm {
background-color: var(--main-color, #0054a5);
color: #ffffff;
font-weight: 600;
}
}
}
</style>