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

245 lines
5.9 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="views-page">
<!-- 汇总卡(2.11.4): 今日/累计 的 PV 与 UV -->
<view class="summary">
<view class="sum-card">
<view class="sum-num">{{ summary.today_pv || 0 }}</view>
<view class="sum-label">今日浏览 PV</view>
</view>
<view class="sum-card">
<view class="sum-num">{{ summary.today_uv || 0 }}</view>
<view class="sum-label">今日访客 UV</view>
</view>
<view class="sum-card">
<view class="sum-num">{{ summary.total_pv || 0 }}</view>
<view class="sum-label">累计浏览 PV</view>
</view>
<view class="sum-card">
<view class="sum-num">{{ summary.total_uv || 0 }}</view>
<view class="sum-label">累计访客 UV</view>
</view>
</view>
<!-- 列表: time / name / channel / device / ip -->
<view class="view-list">
<view v-for="(item, idx) in list" :key="item.id || idx" class="view-item">
<view class="view-avatar">{{ avatarText(item) }}</view>
<view class="view-info">
<view class="view-name">{{ item.viewer_name || item.name || '匿名访客' }}</view>
<view class="view-meta">
<text class="view-channel">{{ item.channel || '未知渠道' }}</text>
<text class="view-dot">·</text>
<text class="view-device">{{ item.device || '' }}</text>
<text class="view-dot" v-if="item.ip">·</text>
<text class="view-ip">{{ item.ip || '' }}</text>
</view>
</view>
<view class="view-time">{{ item.view_time || item.time || '' }}</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>
<view v-if="!isLoading && list.length === 0" class="views-empty">暂无访客记录</view>
</view>
</view>
</template>
<script>
/**
* 访客轨迹 / 谁看了我(2.11.4,建议新页面)
* 入口: 我的名片「访客」总入口(不带 card_id,查名下全部名片)
* 或某张名片「浏览」入口(带 card_id,查该卡访客)
* 接口: POST /card/api/card/views { card_id?, page?, page_size?(<=50, 默认20) }
* 响应: { list, total, page, page_size, summary: { today_pv, today_uv, total_pv, total_uv } }
*/
export default {
data() {
return {
cardId: '', // 带 card_id=查该卡访客; 空=名下全部
list: [],
page: 1,
pageSize: 20,
pageCount: 1,
isLoading: false,
summary: {}
};
},
onLoad(options) {
this.cardId = (options && options.card_id) ? String(options.card_id) : '';
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: {
avatarText(item) {
const name = item.viewer_name || item.name || '?';
return name.trim().charAt(0).toUpperCase();
},
refresh(done) {
this.page = 1;
this.loadPage(1, done);
},
loadPage(pageNo, done) {
this.isLoading = true;
const data = { page: pageNo, page_size: this.pageSize };
if (this.cardId) data.card_id = this.cardId;
this.$cardApi.cardViews({
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.summary = d.summary || {};
this.list = pageNo === 1 ? rows : this.list.concat(rows);
} else {
this.handleApiError(res);
}
this.isLoading = false;
typeof done === 'function' && done();
},
fail: () => {
this.isLoading = false;
uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
typeof done === 'function' && done();
}
});
},
handleApiError(res) {
const msg = (res && res.message) || '加载失败';
uni.showToast({ title: msg, icon: 'none' });
}
}
};
</script>
<style lang="scss" scoped>
.views-page {
min-height: 100vh;
background-color: #f6f7f9;
padding: 20rpx 24rpx 40rpx;
box-sizing: border-box;
}
.summary {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
.sum-card {
flex: 1;
background-color: #fff;
border-radius: 16rpx;
padding: 24rpx 0;
text-align: center;
margin: 0 8rpx;
&:first-child { margin-left: 0; }
&:last-child { margin-right: 0; }
.sum-num {
font-size: 40rpx;
font-weight: 700;
color: var(--main-color, #0054a5);
line-height: 1.2;
}
.sum-label {
margin-top: 8rpx;
font-size: 22rpx;
color: #999;
}
}
}
.view-list {
.view-item {
display: flex;
align-items: center;
background-color: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 16rpx;
.view-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background-color: var(--main-color-shallow, #e8f1fb);
color: var(--main-color, #0054a5);
font-size: 32rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-right: 20rpx;
}
.view-info {
flex: 1;
min-width: 0;
.view-name {
font-size: 30rpx;
font-weight: 600;
color: #333;
}
.view-meta {
margin-top: 8rpx;
font-size: 24rpx;
color: #999;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
.view-dot {
margin: 0 10rpx;
}
}
}
.view-time {
flex-shrink: 0;
margin-left: 16rpx;
font-size: 24rpx;
color: #bbb;
}
}
}
.load-more {
text-align: center;
color: #999;
font-size: 24rpx;
padding: 24rpx 0;
.load-end { color: #ccc; }
}
.views-empty {
text-align: center;
color: #999;
font-size: 28rpx;
padding: 120rpx 0;
}
</style>