343 lines
7.6 KiB
Vue
343 lines
7.6 KiB
Vue
<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">
|
||
<view v-for="(item, index) in list" :key="item.id || index" class="lead-item">
|
||
<view class="lead-top" @tap="callPhone(item.mobile)">
|
||
<view class="lead-name">{{ item.name || '未留姓名' }}</view>
|
||
<view class="lead-status" :class="'st-' + item.status">{{ item.status_text || '新客户' }}</view>
|
||
</view>
|
||
<view class="lead-mobile" v-if="item.mobile" @tap="callPhone(item.mobile)">📱 {{ item.mobile }}</view>
|
||
<view class="lead-note" v-if="item.note">「{{ item.note }}」</view>
|
||
<view class="lead-meta">
|
||
<text class="meta-card">来自名片:{{ item.card_name }}</text>
|
||
<text class="meta-time">{{ item.create_time_txt }}</text>
|
||
</view>
|
||
<view class="lead-actions">
|
||
<view class="action-btn copy" v-if="item.wechat" @tap="copyText(item.wechat)">复制微信</view>
|
||
<view class="action-btn primary" @tap="changeStatus(item, index)">更新状态</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();
|
||
},
|
||
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.$api.sendRequest({
|
||
url: '/card/api/lead/lists',
|
||
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();
|
||
}
|
||
});
|
||
},
|
||
changeStatus(item, index) {
|
||
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.$api.sendRequest({
|
||
url: '/card/api/lead/setStatus',
|
||
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' })
|
||
});
|
||
}
|
||
}
|
||
};
|
||
</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;
|
||
font-size: 22rpx;
|
||
color: #999;
|
||
margin-top: 14rpx;
|
||
|
||
.meta-card {
|
||
max-width: 60%;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
|
||
.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>
|