Files
lucky_shop/pages_tool/card/list.vue

445 lines
11 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 class="card-wallet">
<!-- 搜索区 -->
<view class="search-bar">
<view class="search-input-wrap">
<input class="search-input" confirm-type="search" :value="keyword"
placeholder="搜索姓名 / 手机 / 公司" @confirm="onSearch" @input="onKeywordInput" />
<text v-if="keyword" class="search-clear" @tap="clearKeyword">✕</text>
<text class="iconfont icon-sousuo3" @tap="onSearch"></text>
</view>
<view class="claim-btn create-btn" @tap="goCreate">新建</view>
<view class="claim-btn" @tap="onClaimTap">认领</view>
</view>
<!-- 筛选条: 仅看收藏 -->
<view class="filter-row">
<view class="fav-filter" :class="{ active: onlyFav }" @tap="toggleFavFilter">
<text class="fav-filter-icon">{{ onlyFav ? '★' : '☆' }}</text>
<text>{{ onlyFav ? '仅看收藏' : '全部名片' }}</text>
</view>
</view>
<!-- 列表 -->
<view class="card-list">
<view v-for="(item, index) in list" :key="item.id || index" class="card-item"
@tap="goDetail(item)">
<view class="card-avatar">
<text>{{ avatarText(item.name) }}</text>
</view>
<view class="card-info">
<view class="card-name line1">{{ item.name || '未命名名片' }}</view>
<view class="card-sub line1" v-if="item.job_title || item.company">
{{ [item.job_title, item.company].filter(Boolean).join(' · ') }}
</view>
<view class="card-mobile" v-if="item.mobile">📱 {{ item.mobile }}</view>
</view>
<!-- 收藏星标: 实心=已收藏, 空心=未收藏; 单独点击不进详情 -->
<text class="card-fav" :class="{ 'is-fav': item.is_favorite == 1 }"
@tap.stop="toggleFavorite(item)">{{ item.is_favorite == 1 ? '★' : '☆' }}</text>
<view class="card-arrow">›</view>
</view>
<!-- 加载状态 -->
<view class="load-more" v-if="list.length > 0">
<text v-if="isLoading">加载中…</text>
<text v-else-if="page < pageCount">上拉加载更多</text>
<text v-else class="load-end">— 没有更多了 —</text>
</view>
<!-- 空态 -->
<ns-empty v-if="!isLoading && list.length === 0" :isIndex="false"
:text="emptyText"></ns-empty>
</view>
</view>
</template>
<script>
export default {
data() {
return {
list: [],
page: 1,
pageSize: 20,
pageCount: 1,
count: 0,
keyword: '',
onlyFav: false,
isLoading: false,
emptyText: '暂无名片,可点击右上角「新建」或「认领」添加'
};
},
onLoad() {
this.refresh();
},
onPullDownRefresh() {
this.refresh(() => uni.stopPullDownRefresh());
},
onReachBottom() {
if (this.isLoading) return;
if (this.page >= this.pageCount) return;
this.loadPage(this.page + 1);
},
methods: {
avatarText(name) {
return (name || '?').trim().charAt(0).toUpperCase();
},
onKeywordInput(e) {
this.keyword = e.detail.value;
},
onSearch() {
this.refresh();
},
clearKeyword() {
this.keyword = '';
this.refresh();
},
// 仅看收藏筛选开关(列表筛选, favorite=1; 可与 keyword 叠加)
toggleFavFilter() {
this.onlyFav = !this.onlyFav;
this.refresh();
},
/**
* 切换收藏(双向 toggle): 直接调, 用返回的 is_favorite 回填。
* 乐观更新 + 失败回滚; 仅看收藏模式下取消收藏则从列表移除该行。
*/
toggleFavorite(item) {
if (!item) return;
const cardId = this.cardIdOf(item);
if (!cardId) {
uni.showToast({ title: '该名片缺少编号,无法收藏', icon: 'none' });
return;
}
const prev = item.is_favorite == 1 ? 1 : 0;
// 乐观翻转
this.$set(item, 'is_favorite', prev ? 0 : 1);
this.$cardApi.cardToggleFavorite({
data: { card_id: cardId },
success: res => {
if (res.code == 0) {
const d = res.data || {};
const next = d.is_favorite == 1 ? 1 : 0;
this.$set(item, 'is_favorite', next);
// 仅看收藏模式下取消收藏 → 从列表移除
if (this.onlyFav && next === 0) {
const idx = this.list.indexOf(item);
if (idx >= 0) this.list.splice(idx, 1);
}
} else {
// 「名片不存在」/「非自己名下卡」等拒绝场景: 回滚 + 直接 toast message
this.$set(item, 'is_favorite', prev);
if (res.message) uni.showToast({ title: res.message, icon: 'none' });
}
},
fail: () => {
this.$set(item, 'is_favorite', prev);
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
}
});
},
refresh(done) {
this.page = 1;
this.loadPage(1, done);
},
loadPage(pageNo, done) {
this.isLoading = true;
// 列表筛选: favorite=1 只返回星标卡(后端 2.9.12); 不传/非1 返回全部。
// 与现有 page/keyword 一并放 body(TP6 param() 合并 query+body, 等效 ?favorite=1)。
const reqData = {
page: pageNo,
page_size: this.pageSize,
keyword: this.keyword.trim()
};
if (this.onlyFav) reqData.favorite = 1;
this.$cardApi.cardLists({
data: reqData,
success: res => {
if (res.code == 0) {
const data = res.data || {};
const rows = data.list || [];
this.pageCount = data.page_count || 1;
this.count = data.count || 0;
this.page = pageNo;
this.list = pageNo === 1 ? rows : this.list.concat(rows);
this.emptyText = this.keyword.trim()
? '未找到匹配的名片'
: '暂无名片,可点击右上角「新建」或「认领」添加';
} else {
this.handleApiError(res);
}
this.isLoading = false;
typeof done === 'function' && done();
},
fail: () => {
this.isLoading = false;
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
typeof done === 'function' && done();
}
});
},
// 详情路由只认字符串 card_id(后端文档 1.3.2), 行数据里可能写成 card_id / cardId
cardIdOf(item) {
if (!item) return '';
const raw = item.card_id || item.cardId;
return raw ? String(raw) : '';
},
goDetail(item) {
const cardId = this.cardIdOf(item);
if (!cardId) {
// 之前这里是静默 return: 命中时点名片毫无反应, 用户只会认为"点不进去"
console.warn('[card/list] 该行缺少 card_id, 无法打开详情; 可用字段:',
item ? Object.keys(item) : item);
uni.showToast({ title: '该名片缺少编号,无法打开', icon: 'none' });
return;
}
uni.navigateTo({
url: '/pages_tool/card/detail?card_id=' + encodeURIComponent(cardId),
// navigateTo 默认无界面反馈(页面未注册/栈满都会静默失败), 显式暴露错误
fail: err => {
console.error('[card/list] 打开名片详情失败:', err);
uni.showToast({ title: '打开失败,请稍后重试', icon: 'none' });
}
});
},
goCreate() {
uni.navigateTo({
url: '/pages_tool/card/edit'
});
},
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' });
return;
}
this.doClaim(cardId);
}
});
},
doClaim(cardId) {
uni.showLoading({ title: '认领中…' });
this.$cardApi.cardClaim({
data: { card_id: cardId },
success: res => {
uni.hideLoading();
if (res.code == 0) {
const claimed = res.data && res.data.claimed === false;
uni.showToast({
title: claimed ? '该名片已在您的名片夹中' : '认领成功',
icon: 'success'
});
this.refresh();
} else {
this.handleApiError(res);
}
},
fail: () => {
uni.hideLoading();
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
}
});
},
handleApiError(res) {
const msg = (res && res.message) || '操作失败';
// 未登录/凭证失效时引导登录(与全站约定一致:code < 0)
if (res && res.code < 0) {
uni.showToast({ title: msg, icon: 'none' });
return;
}
uni.showToast({ title: msg, icon: 'none' });
}
}
};
</script>
<style lang="scss" scoped>
.card-wallet {
min-height: 100vh;
background-color: #f6f7f9;
padding: 20rpx 24rpx 40rpx;
box-sizing: border-box;
}
.search-bar {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.search-input-wrap {
flex: 1;
display: flex;
align-items: center;
background-color: #ffffff;
border-radius: 36rpx;
padding: 0 24rpx;
height: 72rpx;
.search-input {
flex: 1;
height: 72rpx;
font-size: 28rpx;
color: #333;
}
.search-clear {
padding: 0 12rpx;
color: #bbb;
font-size: 28rpx;
}
.iconfont {
color: #999;
font-size: 32rpx;
padding-left: 12rpx;
}
}
.claim-btn {
margin-left: 20rpx;
height: 72rpx;
line-height: 72rpx;
padding: 0 30rpx;
background-color: var(--main-color, #0054a5);
color: #ffffff;
font-size: 28rpx;
border-radius: 36rpx;
/* 「新建」次级按钮: 主按钮同构, 淡底弱化一档 */
&.create-btn {
background-color: var(--main-color-shallow, #e8f1fb);
color: var(--main-color, #0054a5);
}
}
}
/* 筛选条: 仅看收藏 */
.filter-row {
display: flex;
margin-bottom: 20rpx;
}
.fav-filter {
display: inline-flex;
align-items: center;
height: 60rpx;
padding: 0 28rpx;
border-radius: 30rpx;
background-color: #ffffff;
color: #666;
font-size: 26rpx;
border: 1rpx solid #e5e5e5;
.fav-filter-icon {
margin-right: 8rpx;
font-size: 30rpx;
color: #ccc;
}
&.active {
background-color: #fff6e0;
border-color: #f5b301;
color: #c8860a;
.fav-filter-icon {
color: #f5b301;
}
}
}
.card-list {
.card-item {
display: flex;
align-items: center;
background-color: #ffffff;
border-radius: 16rpx;
padding: 28rpx 24rpx;
margin-bottom: 20rpx;
&:active {
background-color: #f0f2f5;
}
.card-avatar {
width: 88rpx;
height: 88rpx;
border-radius: 50%;
background-color: var(--main-color-shallow, #e8f1fb);
color: var(--main-color, #0054a5);
font-size: 36rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-right: 24rpx;
}
.card-info {
flex: 1;
min-width: 0;
.card-name {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.card-sub {
font-size: 26rpx;
color: #666;
margin-top: 8rpx;
}
.card-mobile {
font-size: 26rpx;
color: #999;
margin-top: 8rpx;
}
}
.card-arrow {
color: #ccc;
font-size: 40rpx;
margin-left: 16rpx;
}
/* 收藏星标: 空心=未收藏(灰), 实心=已收藏(金) */
.card-fav {
flex-shrink: 0;
font-size: 44rpx;
line-height: 1;
color: #ccc;
margin-left: 16rpx;
padding: 4rpx;
&.is-fav {
color: #f5b301;
}
}
}
}
.load-more {
text-align: center;
color: #999;
font-size: 24rpx;
padding: 24rpx 0;
.load-end {
color: #ccc;
}
}
.line1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>