Files
lucky_shop/pages_tool/card/leads.vue
2026-10-19 16:44:02 +08:00

419 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="lead-page">
<!-- 平级入口: 与我的人脉页互链(导航合并第一步) -->
<view class="page-switch" @tap="goContacts">
<text class="switch-label">我的人脉</text>
<text class="switch-arrow">›</text>
</view>
<!-- 状态筛选 -->
<scroll-view scroll-x class="tab-bar">
<view class="tab-inner">
<view v-for="tab in tabs" :key="tab.value" class="tab-item"
:class="{ active: status === tab.value }" @tap="switchTab(tab.value)">
{{ tab.label }}
</view>
</view>
</scroll-view>
<!-- 客户列表 -->
<view class="lead-list">
<!-- 点「别人的名片」= 打开对方名片; 拨号/按钮一律 .stop, 避免误触成打电话 -->
<view v-for="(item, index) in list" :key="item.id || index" :data-index="index" class="lead-item" @tap="viewCard">
<view class="lead-top">
<view class="lead-name">{{ item.name || '未留姓名' }}</view>
<view class="lead-status" :class="'st-' + item.status">{{ item.status_text || '新客户' }}</view>
</view>
<!-- 手机号是唯一拨号入口(H5 下调 tel: 浏览器会报 ERR_UNKNOWN_URL_SCHEME, 属正常) -->
<view class="lead-mobile" v-if="item.mobile" @tap.stop="callPhone(item.mobile)">📱 {{ item.mobile }}</view>
<view class="lead-note" v-if="item.note">「{{ item.note }}」</view>
<view class="lead-meta">
<view class="meta-card" :class="{ tappable: !!cardIdOf(item) }">
<text class="meta-card-name">来自名片:{{ item.card_name || '—' }}</text>
<text class="meta-card-arrow" v-if="cardIdOf(item)">查看名片 ›</text>
</view>
<text class="meta-time">{{ item.create_time_txt }}</text>
</view>
<view class="lead-actions">
<view class="action-btn copy" v-if="item.wechat" @tap.stop="copyText(item.wechat)">复制微信</view>
<view class="action-btn primary" :data-index="index" @tap.stop="changeStatus">更新状态</view>
</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="'暂无意向客户,访客在你的名片页留资后会出现在这里'"></ns-empty>
</view>
</view>
</template>
<script>
// 与后端 Lead::STATUSES 一致: 0=新客户 1=已联系 2=已成交 3=无效
const STATUS_MAP = [
{ value: 0, label: '新客户' },
{ value: 1, label: '已联系' },
{ value: 2, label: '已成交' },
{ value: 3, label: '无效' }
];
export default {
data() {
return {
tabs: [{ value: '', label: '全部' }].concat(STATUS_MAP),
status: '',
list: [],
page: 1,
pageSize: 20,
pageCount: 1,
isLoading: false
};
},
onLoad() {
this.refresh();
},
onReady() {
// H5 uni-page-head 挂载早于路由 meta 就绪会回落默认标题 "title", onReady 再兜底一次
uni.setNavigationBarTitle({ title: '名片' });
// #ifdef H5
document.title = '名片';
// #endif
},
onPullDownRefresh() {
this.refresh(() => uni.stopPullDownRefresh());
},
onReachBottom() {
if (this.isLoading) return;
if (this.page >= this.pageCount) return;
this.loadPage(this.page + 1);
},
methods: {
// 顶部平级入口: $util.redirectTo(鸿蒙禁裸 uni.switchTab, 静默失败)
goContacts() {
this.$util.redirectTo('/pages_tool/card/contacts');
},
switchTab(value) {
if (this.status === value) return;
this.status = value;
this.refresh();
},
refresh(done) {
this.page = 1;
this.loadPage(1, done);
},
loadPage(pageNo, done) {
this.isLoading = true;
const data = { page: pageNo, page_size: this.pageSize };
// 空字符串 = 全部, 后端对 '' 不加 status 条件
if (this.status !== '') {
data.status = this.status;
}
this.$cardApi.leadLists({
data: data,
success: res => {
if (res.code == 0) {
const d = res.data || {};
const rows = d.list || [];
this.pageCount = d.page_count || 1;
this.page = pageNo;
this.list = pageNo === 1 ? rows : this.list.concat(rows);
} else {
uni.showToast({ title: res.message || '加载失败', icon: 'none' });
}
this.isLoading = false;
typeof done === 'function' && done();
},
fail: () => {
this.isLoading = false;
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
typeof done === 'function' && done();
}
});
},
/**
* 取事件对应的行数据。小程序端 v-for 内联传对象会丢参, 统一 :data-index + 不传参。
* 兼容: 事件对象(data-index) / 数字索引 / 行对象本身。
*/
resolveItem(payload) {
if (payload == null) return null;
if (typeof payload === 'number') return this.list[payload] || null;
const ds = payload.currentTarget && payload.currentTarget.dataset;
if (ds) {
const i = Number(ds.index);
if (!isNaN(i) && this.list[i]) return this.list[i];
}
return payload.id ? payload : null;
},
changeStatus(payload) {
let item, index;
if (payload && payload.currentTarget) {
index = Number(payload.currentTarget.dataset.index);
item = this.list[index];
} else if (typeof payload === 'number') {
index = payload;
item = this.list[index];
} else {
item = payload;
index = this.list.indexOf(item);
}
if (!item) return;
uni.showActionSheet({
itemList: STATUS_MAP.map(s => s.label),
success: res => {
const target = STATUS_MAP[res.tapIndex];
if (!target || target.value === item.status) return;
this.doSetStatus(item, index, target.value);
}
});
},
doSetStatus(item, index, status) {
this.$cardApi.leadSetStatus({
data: { id: item.id, status: status },
success: res => {
if (res.code == 0) {
// 就地更新, 不整页刷新
this.list[index].status = status;
this.list[index].status_text = STATUS_MAP.find(s => s.value === status).label;
uni.showToast({ title: '已更新', icon: 'success' });
} else {
uni.showToast({ title: res.message || '更新失败', icon: 'none' });
}
},
fail: () => {
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
}
});
},
callPhone(mobile) {
if (!mobile) return;
uni.makePhoneCall({ phoneNumber: mobile });
},
copyText(text) {
uni.setClipboardData({
data: text,
success: () => uni.showToast({ title: '已复制', icon: 'success' })
});
},
// 该线索关联的名片编号(后端 lead/lists 行下发; 兼容常见字段名)
cardIdOf(item) {
if (!item) return '';
return item.card_id || item.from_card_id || item.source_card_id || '';
},
// 点整条客户卡片 → 打开对方名片
// 与交换名片(exchange.vue)同款写法: detail 接收 card_id(卡片编号字符串);
// 本人名下的卡 → 正常详情页; 非本人名下 → detail 兜底跳公开名片页(后端返回的 wap_url)
viewCard(payload) {
const item = this.resolveItem(payload);
const cardId = this.cardIdOf(item);
if (!cardId) {
// 后端 lead/lists 目前只下发 card_name(展示名), 没有名片编号 → 无法跳转。
// 这里把该行真实字段打到控制台, 便于确认后端是否用了别的字段名(如 relation_id)
console.warn('[card/leads] 该行缺少名片编号(card_id/from_card_id/source_card_id), 无法打开对方名片; 可用字段:',
item ? Object.keys(item) : 'event', item);
uni.showToast({ title: '该客户未关联名片,无法查看', icon: 'none' });
return;
}
uni.navigateTo({
url: '/pages_tool/card/detail?card_id=' + encodeURIComponent(cardId)
});
}
}
};
</script>
<style lang="scss" scoped>
.lead-page {
min-height: 100vh;
background-color: #f6f7f9;
padding-bottom: 40rpx;
box-sizing: border-box;
}
.page-switch {
display: flex;
align-items: center;
justify-content: flex-end;
padding: 16rpx 24rpx 0;
.switch-label {
font-size: 26rpx;
color: var(--main-color, #0054a5);
}
.switch-arrow {
font-size: 26rpx;
color: var(--main-color, #0054a5);
margin-left: 4rpx;
}
}
.tab-bar {
background-color: #ffffff;
white-space: nowrap;
.tab-inner {
display: inline-flex;
padding: 0 12rpx;
}
.tab-item {
display: inline-block;
padding: 24rpx 28rpx;
font-size: 28rpx;
color: #666;
position: relative;
&.active {
color: var(--main-color, #0054a5);
font-weight: 600;
&::after {
content: '';
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 10rpx;
width: 40rpx;
height: 6rpx;
border-radius: 3rpx;
background-color: var(--main-color, #0054a5);
}
}
}
}
.lead-list {
padding: 20rpx 24rpx 0;
}
.lead-item {
background-color: #ffffff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 20rpx;
.lead-top {
display: flex;
align-items: center;
.lead-name {
flex: 1;
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.lead-status {
font-size: 22rpx;
padding: 6rpx 18rpx;
border-radius: 22rpx;
&.st-0 {
color: var(--main-color, #0054a5);
background-color: var(--main-color-shallow, #e8f1fb);
}
&.st-1 {
color: #e6a23c;
background-color: #fdf3e6;
}
&.st-2 {
color: #1daa39;
background-color: #e8f8ea;
}
&.st-3 {
color: #999;
background-color: #f2f2f2;
}
}
}
.lead-mobile {
font-size: 28rpx;
color: #333;
margin-top: 14rpx;
}
.lead-note {
font-size: 26rpx;
color: #666;
margin-top: 12rpx;
line-height: 1.5;
}
.lead-meta {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 22rpx;
color: #999;
margin-top: 14rpx;
.meta-card {
display: flex;
align-items: center;
max-width: 64%;
// 有名片编号时才高亮/可点, 否则退化为普通文字
&.tappable .meta-card-name {
color: var(--main-color, #0054a5);
}
.meta-card-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.meta-card-arrow {
flex-shrink: 0;
margin-left: 8rpx;
color: var(--main-color, #0054a5);
}
}
}
.lead-actions {
display: flex;
justify-content: flex-end;
margin-top: 20rpx;
padding-top: 20rpx;
border-top: 1rpx solid #f0f0f0;
.action-btn {
font-size: 24rpx;
border-radius: 30rpx;
padding: 10rpx 30rpx;
margin-left: 20rpx;
&.copy {
color: #666;
border: 1rpx solid #ddd;
}
&.primary {
color: #ffffff;
background-color: var(--main-color, #0054a5);
}
}
}
}
.load-more {
text-align: center;
color: #999;
font-size: 24rpx;
padding: 24rpx 0;
.load-end {
color: #ccc;
}
}
</style>