chore:待跟进和已完成页面正常

This commit is contained in:
2026-10-17 14:51:29 +08:00
parent d4457e58d4
commit 33cd21cf78

View File

@@ -63,9 +63,20 @@
<text v-else class="load-end">— 没有更多了 —</text>
</view>
<!-- 空态: 建卡申请 / 申请状态(2.11.15) -->
<view v-if="!isLoading && list.length === 0" class="card-empty">
<block v-if="apply && apply.status === 0">
<!-- 首次加载占位: 首屏请求结束前用它顶住, 避免先闪空态再跳成列表 -->
<view class="first-loading" v-if="!booted">
<text>加载中…</text>
</view>
<!-- 空态: 建卡申请 / 申请状态(2.11.15); booted 未置位说明还没拿到数据, 不展示空态 -->
<view v-if="booted && !isLoading && list.length === 0" class="card-empty">
<!-- 分组/搜索/收藏筛选为空: 只是当下没有符合条件的名片, 不能当成"还没建卡"去引导申请 -->
<block v-if="emptyScene">
<view class="empty-icon">{{ emptyScene.icon }}</view>
<view class="empty-title">{{ emptyScene.title }}</view>
<view class="empty-sub">{{ emptyScene.sub }}</view>
</block>
<block v-else-if="apply && apply.status === 0">
<view class="empty-icon">⏳</view>
<view class="empty-title">申请审核中</view>
<view class="empty-sub">您的建卡申请正在等待商家审批</view>
@@ -144,9 +155,34 @@ export default {
apply: null, // { status, status_text, note, has_card }
applyName: '',
applyMobile: '',
applying: false
applying: false,
// 首次数据请求是否已结束: 未结束前不渲染空态,
// 否则重新编译/刷新时会先闪一下「还没有名片 + 申请表单」(列表其实有数据)
booted: false
};
},
computed: {
/**
* 筛选态下的空态文案。
* 重要: 只有「全部 + 无搜索 + 无收藏筛选」才代表"用户还没有名片"(这时才引导建卡申请);
* 待跟进/已完成/搜索/收藏 只是当下没有符合条件的卡, 不能弹申请表单。
*/
emptyScene() {
if (this.followTab === 'due') {
return { icon: '📇', title: '暂无待跟进名片', sub: '有跟进中的名片会出现在这里' };
}
if (this.followTab === 'done') {
return { icon: '📇', title: '暂无已完成名片', sub: '标记完成的名片会出现在这里' };
}
if (this.onlyFav) {
return { icon: '☆', title: '暂无收藏的名片', sub: '点名片右侧的星标即可收藏' };
}
if (this.keyword.trim()) {
return { icon: '🔍', title: '未找到匹配的名片', sub: '换个关键词试试' };
}
return null;
}
},
onLoad(options) {
// 扫码带参预填(文档 §5): 二维码/链接带 claim_code 或 card_id 直接进入认领
if (options && (options.claim_code || options.card_id)) {
@@ -154,7 +190,12 @@ export default {
.toUpperCase().replace(/\s+/g, '');
this.claimMode = options.claim_code ? 'code' : 'id';
}
this.refresh();
this.bootstrap();
},
onShow() {
// 首次 onShow 紧跟 onLoad(由 bootstrap 负责首屏加载), 这里只处理"返回本页"的刷新
if (this._entered) this.refresh();
this._entered = true;
},
onPullDownRefresh() {
this.refresh(() => uni.stopPullDownRefresh());
@@ -165,6 +206,45 @@ export default {
this.loadPage(this.page + 1);
},
methods: {
/**
* 首屏加载: 等 member token 就绪后再请求。
*
* 背景(为什么需要): App.vue 的 onLaunch 是 async, token 在多个 await
* (i18n / 主题 / 远端配置 / 隐私弹窗) 之后才从 storage 恢复到 store。
* 页面 onLoad 在 onLaunch 第一次 await 让出后就执行, 此时 store.state.token
* 仍是 null → card/lists 以未登录身份请求, 后端返回空列表;
* 本页原本又没有 onShow 重拉, 于是表现为「刷新/重新编译后没有名片,
* 必须退出页面重新进入才看得到」。
*/
bootstrap() {
// 等 token 期间也处于"加载中", 否则首屏会先渲染空态
this.isLoading = true;
this.waitTokenReady().then(() => this.refresh());
},
/**
* 等 token 就绪:
* - store 已有 token → 直接放行;
* - storage 有 token 但 store 还没有 → onLaunch 还在跑, 轮询等待(最多 timeout);
* - storage 也没有 → 未登录/游客, 不等, 用空 token 请求(由后端决定返回)。
*/
waitTokenReady(timeout = 3000) {
if (this.$store.state.token) return Promise.resolve(true);
let stored = '';
try { stored = uni.getStorageSync('token') || ''; } catch (e) { stored = ''; }
if (!stored) return Promise.resolve(false);
return new Promise(resolve => {
const start = Date.now();
const timer = setInterval(() => {
if (this.$store.state.token) {
clearInterval(timer);
resolve(true);
} else if (Date.now() - start >= timeout) {
clearInterval(timer);
resolve(false);
}
}, 50);
});
},
avatarText(name) {
return (name || '?').trim().charAt(0).toUpperCase();
},
@@ -310,7 +390,8 @@ export default {
this.page = pageNo;
this.list = pageNo === 1 ? rows : this.list.concat(rows);
// 空态(首页无卡): 拉取建卡申请状态, 驱动空态申请 UI(2.11.15)
if (pageNo === 1 && rows.length === 0) this.loadApplyStatus();
// 仅「全部 + 无搜索 + 无收藏筛选」时才需要(其余筛选为空不代表用户没卡)
if (pageNo === 1 && rows.length === 0 && !this.emptyScene) this.loadApplyStatus();
this.emptyText = this.keyword.trim()
? '未找到匹配的名片'
: '暂无名片,可点击右上角「新建」或「认领」添加';
@@ -318,10 +399,13 @@ export default {
this.handleApiError(res);
}
this.isLoading = false;
// 首次请求结束(无论成功/失败)才允许渲染空态
this.booted = true;
typeof done === 'function' && done();
},
fail: () => {
this.isLoading = false;
this.booted = true;
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
typeof done === 'function' && done();
}
@@ -666,6 +750,14 @@ export default {
border-radius: 24rpx;
}
/* 首屏加载占位(替代"先闪空态") */
.first-loading {
text-align: center;
color: #999;
font-size: 26rpx;
padding: 160rpx 0;
}
/* 空态: 建卡申请 */
.card-empty {
text-align: center;