chore:名片样式和后端同步

This commit is contained in:
2026-10-16 16:41:13 +08:00
parent d358bb55ba
commit 5290271017
5 changed files with 863 additions and 599 deletions

View File

@@ -1513,7 +1513,14 @@
"navigationBarTitleText": "我的人脉", "navigationBarTitleText": "我的人脉",
"enablePullDownRefresh": true "enablePullDownRefresh": true
} }
}, },
{
"path": "card/views",
"style": {
"navigationBarTitleText": "谁看了我",
"enablePullDownRefresh": true
}
},
{ {
"path": "card/contact-edit", "path": "card/contact-edit",
"style": { "style": {

View File

@@ -35,11 +35,11 @@
<text>编辑资料</text> <text>编辑资料</text>
</view> </view>
<view class="ca-divider"></view> <view class="ca-divider"></view>
<view class="ca-item" @tap="onStyleTap"> <view class="ca-item" @tap="onStyleTap" v-if="designEnabled">
<text class="ca-icon iconfont icon-erweima"></text> <text class="ca-icon iconfont icon-erweima"></text>
<text>名片样式</text> <text>名片样式</text>
</view> </view>
<view class="ca-divider"></view> <view class="ca-divider" v-if="designEnabled"></view>
<view class="ca-item" @tap="goList"> <view class="ca-item" @tap="goList">
<text class="ca-icon iconfont icon-shuaxin"></text> <text class="ca-icon iconfont icon-shuaxin"></text>
<text>切换名片</text> <text>切换名片</text>
@@ -213,7 +213,9 @@ export default {
qrUrl: '', qrUrl: '',
// 交换名片: 待处理请求(真实, exchange/lists, 只含 status=0) // 交换名片: 待处理请求(真实, exchange/lists, 只含 status=0)
exchangeList: [], exchangeList: [],
exchangeCount: 0 exchangeCount: 0,
// 商家是否开启「员工端样式自选」(GET design/presets 的 enabled); 默认 true(失败开放)
designEnabled: true
}; };
}, },
computed: { computed: {
@@ -345,12 +347,14 @@ export default {
this.cardId = decodeURIComponent(options.card_id || ''); this.cardId = decodeURIComponent(options.card_id || '');
this.loadDetail(); this.loadDetail();
this.loadExchange(); this.loadExchange();
this.loadDesignEnabled();
}, },
onShow() { onShow() {
// 首次进入由 onLoad 负责加载; 从编辑页返回时(onShow 再次触发)刷新 // 首次进入由 onLoad 负责加载; 从编辑页返回时(onShow 再次触发)刷新
if (this._loadedOnce) { if (this._loadedOnce) {
this.loadDetail(); this.loadDetail();
this.loadExchange(); this.loadExchange();
this.loadDesignEnabled();
} }
this._loadedOnce = true; this._loadedOnce = true;
}, },
@@ -558,6 +562,16 @@ export default {
}); });
// #endif // #endif
}, },
// 查询商家是否开启「员工端样式自选」(控制名片样式入口显隐)
loadDesignEnabled() {
this.$cardApi.cardDesignPresets({
success: res => {
const d = res.data || {};
if (res.code == 0) this.designEnabled = d.enabled !== false;
},
fail: () => {} // 失败开放(默认 true, 入口保留)
});
},
// 「名片样式」: 进入样式选择页(名片模板 / 分享封面) // 「名片样式」: 进入样式选择页(名片模板 / 分享封面)
onStyleTap() { onStyleTap() {
const url = this.cardId const url = this.cardId

View File

@@ -10,6 +10,7 @@
</view> </view>
<view class="claim-btn create-btn" @tap="goCreate">新建</view> <view class="claim-btn create-btn" @tap="goCreate">新建</view>
<view class="claim-btn" @tap="onClaimTap">认领</view> <view class="claim-btn" @tap="onClaimTap">认领</view>
<view class="claim-btn" @tap="goViews">访客</view>
</view> </view>
<!-- 筛选条: 仅看收藏 --> <!-- 筛选条: 仅看收藏 -->
@@ -20,6 +21,13 @@
</view> </view>
</view> </view>
<!-- 筛选条: 跟进(全部 / 待跟进 / 已完成, follow_up 传 due/done) -->
<view class="filter-row">
<view class="fav-filter" :class="{ active: followTab === 'all' }" @tap="switchFollow('all')">全部</view>
<view class="fav-filter" :class="{ active: followTab === 'due' }" @tap="switchFollow('due')">待跟进</view>
<view class="fav-filter" :class="{ active: followTab === 'done' }" @tap="switchFollow('done')">已完成</view>
</view>
<!-- 列表 --> <!-- 列表 -->
<view class="card-list"> <view class="card-list">
<view v-for="(item, index) in list" :key="item.id || index" class="card-item" <view v-for="(item, index) in list" :key="item.id || index" class="card-item"
@@ -33,10 +41,18 @@
{{ [item.job_title, item.company].filter(Boolean).join(' · ') }} {{ [item.job_title, item.company].filter(Boolean).join(' · ') }}
</view> </view>
<view class="card-mobile" v-if="item.mobile">📱 {{ item.mobile }}</view> <view class="card-mobile" v-if="item.mobile">📱 {{ item.mobile }}</view>
<view class="card-follow" v-if="item.follow_up_done == 1">
<text class="card-follow-done">✓ 已完成</text>
</view>
<view class="card-follow" v-else-if="followTab === 'due'">
<text class="card-follow-due">跟进中</text>
</view>
</view> </view>
<!-- 收藏星标: 实心=已收藏, 空心=未收藏; 单独点击不进详情 --> <!-- 收藏星标: 实心=已收藏, 空心=未收藏; 单独点击不进详情 -->
<text class="card-fav" :class="{ 'is-fav': item.is_favorite == 1 }" <text class="card-fav" :class="{ 'is-fav': item.is_favorite == 1 }"
@tap.stop="toggleFavorite(item)">{{ item.is_favorite == 1 ? '★' : '☆' }}</text> @tap.stop="toggleFavorite(item)">{{ item.is_favorite == 1 ? '★' : '☆' }}</text>
<view v-if="followTab === 'due' && item.follow_up_done != 1" class="card-done-btn"
@tap.stop="markDone(item)">完成</view>
<view class="card-arrow">›</view> <view class="card-arrow">›</view>
</view> </view>
@@ -47,9 +63,34 @@
<text v-else class="load-end">— 没有更多了 —</text> <text v-else class="load-end">— 没有更多了 —</text>
</view> </view>
<!-- 空态 --> <!-- 空态: 建卡申请 / 申请状态(2.11.15) -->
<ns-empty v-if="!isLoading && list.length === 0" :isIndex="false" <view v-if="!isLoading && list.length === 0" class="card-empty">
:text="emptyText"></ns-empty> <block v-if="apply && apply.status === 0">
<view class="empty-icon">⏳</view>
<view class="empty-title">申请审核中</view>
<view class="empty-sub">您的建卡申请正在等待商家审批</view>
</block>
<block v-else-if="apply && apply.status === 1">
<view class="empty-icon">✅</view>
<view class="empty-title">已通过</view>
<view class="empty-sub">下拉刷新查看我的名片</view>
</block>
<block v-else>
<view class="empty-icon">📇</view>
<view class="empty-title" v-if="apply && apply.status === 2">已拒绝:{{ apply.note || '商家未填写理由' }}</view>
<view class="empty-title" v-else>还没有名片</view>
<view class="empty-sub">提交信息,申请商家为您建卡</view>
<view class="apply-form">
<input class="apply-input" :value="applyName" @input="e => applyName = e.detail.value"
placeholder="您的姓名" />
<input class="apply-input" :value="applyMobile" @input="e => applyMobile = e.detail.value"
placeholder="手机号" type="number" />
<view class="apply-submit" :class="{ disabled: applying }" @tap="submitApply">
{{ applying ? '提交中…' : '提交申请' }}
</view>
</view>
</block>
</view>
</view> </view>
<!-- 认领弹层: 支持领取码/名片编号 + 扫码带参预填(与 card_claim_api 文档同步) --> <!-- 认领弹层: 支持领取码/名片编号 + 扫码带参预填(与 card_claim_api 文档同步) -->
@@ -96,9 +137,16 @@ export default {
showClaim: false, showClaim: false,
claimInput: '', claimInput: '',
claimMode: 'code', // code=领取码(主通道), id=名片编号(旧入口兼容) claimMode: 'code', // code=领取码(主通道), id=名片编号(旧入口兼容)
pendingClaimCode: '' // 扫码带参预填的码 pendingClaimCode: '', // 扫码带参预填的码
}; // 跟进筛选(2.11.3): all / due(待跟进) / done(已完成)
}, followTab: 'all',
// 建卡申请状态(2.11.15)
apply: null, // { status, status_text, note, has_card }
applyName: '',
applyMobile: '',
applying: false
};
},
onLoad(options) { onLoad(options) {
// 扫码带参预填(文档 §5): 二维码/链接带 claim_code 或 card_id 直接进入认领 // 扫码带参预填(文档 §5): 二维码/链接带 claim_code 或 card_id 直接进入认领
if (options && (options.claim_code || options.card_id)) { if (options && (options.claim_code || options.card_id)) {
@@ -135,6 +183,68 @@ export default {
this.onlyFav = !this.onlyFav; this.onlyFav = !this.onlyFav;
this.refresh(); this.refresh();
}, },
// 跟进筛选切换(全部/待跟进/已完成, 2.11.3)
switchFollow(tab) {
this.followTab = tab;
this.refresh();
},
// 标记跟进完成(2.11.3): followDone, 成功后该卡从待跟进消失
markDone(item) {
const cardId = this.cardIdOf(item);
if (!cardId) return;
uni.showLoading({ title: '标记中…' });
this.$cardApi.cardFollowDone({
data: { card_id: cardId },
success: res => {
uni.hideLoading();
if (res.code == 0) {
uni.showToast({ title: '已标记完成', icon: 'success' });
this.refresh();
} else {
this.handleApiError(res);
}
},
fail: () => {
uni.hideLoading();
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
}
});
},
// 空态: 查询我的建卡申请状态(2.11.15)
loadApplyStatus() {
this.$cardApi.applyStatus({
success: res => {
if (res.code == 0) this.apply = res.data || null;
},
fail: () => {}
});
},
// 提交建卡申请(2.11.15)
submitApply() {
const name = (this.applyName || '').trim();
const mobile = (this.applyMobile || '').trim();
if (!name) { uni.showToast({ title: '请输入姓名', icon: 'none' }); return; }
if (!mobile) { uni.showToast({ title: '请输入手机号', icon: 'none' }); return; }
this.applying = true;
this.$cardApi.applySubmit({
data: { name: name, mobile: mobile },
success: res => {
this.applying = false;
if (res.code == 0) {
uni.showToast({ title: '已提交,等待审核', icon: 'none' });
this.apply = { status: 0, status_text: '待审核' };
this.applyName = '';
this.applyMobile = '';
} else {
this.handleApiError(res);
}
},
fail: () => {
this.applying = false;
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
}
});
},
/** /**
* 切换收藏(双向 toggle): 直接调, 用返回的 is_favorite 回填。 * 切换收藏(双向 toggle): 直接调, 用返回的 is_favorite 回填。
* 乐观更新 + 失败回滚; 仅看收藏模式下取消收藏则从列表移除该行。 * 乐观更新 + 失败回滚; 仅看收藏模式下取消收藏则从列表移除该行。
@@ -187,6 +297,8 @@ export default {
keyword: this.keyword.trim() keyword: this.keyword.trim()
}; };
if (this.onlyFav) reqData.favorite = 1; if (this.onlyFav) reqData.favorite = 1;
if (this.followTab === 'due') reqData.follow_up = 'due';
else if (this.followTab === 'done') reqData.follow_up = 'done';
this.$cardApi.cardLists({ this.$cardApi.cardLists({
data: reqData, data: reqData,
success: res => { success: res => {
@@ -197,6 +309,8 @@ export default {
this.count = data.count || 0; this.count = data.count || 0;
this.page = pageNo; this.page = pageNo;
this.list = pageNo === 1 ? rows : this.list.concat(rows); this.list = pageNo === 1 ? rows : this.list.concat(rows);
// 空态(首页无卡): 拉取建卡申请状态, 驱动空态申请 UI(2.11.15)
if (pageNo === 1 && rows.length === 0) this.loadApplyStatus();
this.emptyText = this.keyword.trim() this.emptyText = this.keyword.trim()
? '未找到匹配的名片' ? '未找到匹配的名片'
: '暂无名片,可点击右上角「新建」或「认领」添加'; : '暂无名片,可点击右上角「新建」或「认领」添加';
@@ -242,6 +356,12 @@ export default {
url: '/pages_tool/card/edit' url: '/pages_tool/card/edit'
}); });
}, },
// 访客轨迹(2.11.4): 进入「谁看了我」总入口(不带 card_id)
goViews() {
uni.navigateTo({
url: '/pages_tool/card/views'
});
},
// 认领: 打开弹层(支持领取码/名片编号 + 扫码带参预填, 与 card_claim_api 文档同步) // 认领: 打开弹层(支持领取码/名片编号 + 扫码带参预填, 与 card_claim_api 文档同步)
onClaimTap() { onClaimTap() {
this.claimInput = this.pendingClaimCode || ''; this.claimInput = this.pendingClaimCode || '';
@@ -521,6 +641,85 @@ export default {
white-space: nowrap; white-space: nowrap;
} }
/* 列表行: 跟进徽标 + 完成按钮 */
.card-follow {
margin-top: 8rpx;
.card-follow-done {
font-size: 22rpx;
color: #1aad19;
}
.card-follow-due {
font-size: 22rpx;
color: #f5a623;
}
}
.card-done-btn {
flex-shrink: 0;
margin-left: 16rpx;
padding: 8rpx 20rpx;
background-color: var(--main-color, #0054a5);
color: #fff;
font-size: 24rpx;
border-radius: 24rpx;
}
/* 空态: 建卡申请 */
.card-empty {
text-align: center;
padding: 120rpx 48rpx;
.empty-icon {
font-size: 96rpx;
}
.empty-title {
margin-top: 24rpx;
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.empty-sub {
margin-top: 12rpx;
font-size: 26rpx;
color: #999;
}
.apply-form {
margin-top: 48rpx;
.apply-input {
height: 84rpx;
background-color: #fff;
border-radius: 14rpx;
padding: 0 24rpx;
font-size: 28rpx;
color: #333;
margin-bottom: 24rpx;
text-align: left;
}
.apply-submit {
height: 88rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #2aa5ff 0%, #0d7bf0 100%);
color: #fff;
font-size: 30rpx;
font-weight: 600;
&.disabled {
opacity: 0.6;
}
}
}
}
/* 认领弹层(与 card_claim_api 文档同步) */ /* 认领弹层(与 card_claim_api 文档同步) */
.claim-mask { .claim-mask {
position: fixed; position: fixed;

File diff suppressed because it is too large Load Diff

View File

@@ -46,6 +46,8 @@ const DOC_URLS = [
'/card/api/card/recognize', '/card/api/card/recognize',
'/card/api/card/retry', '/card/api/card/retry',
'/card/api/card/recognizeBatch', '/card/api/card/recognizeBatch',
'/card/api/card/followDone', // 跟进完成态 2.11.3
'/card/api/card/views', // 访客轨迹 2.11.4
// 人脉库 /card/api/contact/* // 人脉库 /card/api/contact/*
'/card/api/contact/lists', '/card/api/contact/lists',
'/card/api/contact/add', '/card/api/contact/add',
@@ -59,7 +61,11 @@ const DOC_URLS = [
// 交换名片 /card/api/exchange/* // 交换名片 /card/api/exchange/*
'/card/api/exchange/lists', '/card/api/exchange/lists',
'/card/api/exchange/agree', '/card/api/exchange/agree',
'/card/api/exchange/ignore' '/card/api/exchange/ignore',
'/card/api/apply/submit', // 员工建卡申请 2.11.15
'/card/api/apply/status', // 员工建卡申请 2.11.15
'/card/api/design/presets', // 名片预设列表(同源后台 CardTemplatePresets, 后端 v2 无 /card/ 段)
'/card/api/design/save' // 应用预设/保存本卡样式
]; ];
// helper 方法名 ↔ URL 常量名(收口层必须两端齐全) // helper 方法名 ↔ URL 常量名(收口层必须两端齐全)
@@ -82,11 +88,17 @@ const HELPER_URL_PAIRS = [
['leadSetStatus', 'LEAD_SET_STATUS'], ['leadSetStatus', 'LEAD_SET_STATUS'],
['exchangeLists', 'EXCHANGE_LISTS'], ['exchangeLists', 'EXCHANGE_LISTS'],
['exchangeAgree', 'EXCHANGE_AGREE'], ['exchangeAgree', 'EXCHANGE_AGREE'],
['exchangeIgnore', 'EXCHANGE_IGNORE'] ['exchangeIgnore', 'EXCHANGE_IGNORE'],
['cardFollowDone', 'CARD_FOLLOW_DONE'],
['cardViews', 'CARD_VIEWS'],
['applySubmit', 'APPLY_SUBMIT'],
['applyStatus', 'APPLY_STATUS'],
['cardDesignPresets', 'CARD_DESIGN_PRESETS'],
['cardDesignSave', 'CARD_DESIGN_SAVE']
]; ];
// 文档 3.3/4.2/4.4/3.7: 嵌套对象/数组入参必须走 JSON body(sendJson) // 文档 3.3/4.2/4.4/3.7: 嵌套对象/数组入参必须走 JSON body(sendJson)
const JSON_BODY_HELPERS = ['cardUpdate', 'cardCreate', 'contactAdd', 'contactUpdate', 'cardRecognizeBatch', 'exchangeAgree', 'exchangeIgnore']; const JSON_BODY_HELPERS = ['cardUpdate', 'cardCreate', 'contactAdd', 'contactUpdate', 'cardRecognizeBatch', 'exchangeAgree', 'exchangeIgnore', 'cardDesignSave'];
// ============================================================= // =============================================================
// 1. 端点与后端文档逐一比对 // 1. 端点与后端文档逐一比对