Files
frontend-app/pages/other_package/invite_friends/invite_friends.vue
T
2026-09-09 11:46:36 +08:00

633 lines
15 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="invite_friends_warp">
<!-- 1. 顶部导航 (固定) -->
<Header leftTitle="邀请好友" />
<!-- 2. 头部 Banner 区域 (固定) -->
<view class="banner_header">
<view class="banner_left">
<view class="title_row">
<text class="title_black">旧朋带新伴</text>
</view>
<view class="title_row">
<text class="title_black">邀约</text>
<text class="title_purple">享福利</text>
</view>
<view class="sub_title">
<text>累计邀请达标,可解锁更多券礼</text>
</view>
</view>
<view class="banner_right">
<up-image src="https://static.tbmall.xin/static/mine/invite_friends_img.png" width="130" height="115"
bgColor="#f1f6ff00"></up-image>
</view>
</view>
<!-- 3. 卡片容器 (填充中间区域,内部固定标题) -->
<view class="card_container">
<view class="card_title">我的邀请</view>
<!-- 无数据状态 (图一) -->
<view class="empty_box" v-if="!loading && list.length === 0">
<up-image src="https://static.tbmall.xin/static/mine/invite_friends_empty.png" width="120" height="120"
bgColor="#f1f6ff00"></up-image>
<text class="empty_text">暂无邀请好友</text>
</view>
<!-- 有数据状态 (图二) -->
<view class="table_box" v-else-if="list.length > 0">
<!-- 固定表头 (用户ID | 手机号 | 注册时间) -->
<view class="table_header">
<view class="th th_border">用户ID</view>
<view class="th th_border">手机号</view>
<view class="th">注册时间</view>
</view>
<!-- 仅在此 scroll-view 区域内(表格数据行)可滚动 -->
<scroll-view scroll-y class="table_body_scroll" style="height: 100%;" @scrolltolower="onReachBottom"
refresher-enabled :refresher-triggered="refreshing" @refresherrefresh="onRefresh">
<view class="table_body">
<view class="table_row" v-for="(item, index) in list" :key="index">
<view class="td td_border">{{ item.userId || '-' }}</view>
<view class="td td_border">{{ item.mobile || '-' }}</view>
<view class="td">{{ item.registTime || '-' }}</view>
</view>
<!-- 加载完成提示 -->
<view class="load_more_tip" v-if="finished && list.length > 0">
<text class="no_more">已加载全部</text>
</view>
</view>
</scroll-view>
</view>
<!-- 初次加载动画 -->
<view class="loading_box" v-if="loading && list.length === 0">
<up-loading-icon show color="#7934F6"></up-loading-icon>
</view>
</view>
<!-- 4. 底部固定分享按钮 -->
<view class="bottom_action">
<button class="share_btn" @click="openSharePopup">
分享好友
</button>
</view>
<!-- 分享弹窗组件 -->
<view v-if="shareOpen">
<SharePopup ref="share" :show="shareShow" @close="onClose" @wxShare="onWxShare" @share="onShare"
:id="posterId" text="我的推广海报" :tu="inviteImage || shareImg"></SharePopup>
</view>
<up-toast ref="uToastRef"></up-toast>
</view>
</template>
<script>
import Header from "@/components/header.vue";
import { getSharePoster, generateSharePoster } from "@/api/common.js";
import { getDirectReferrals } from "@/api/auth.js";
import SharePopup from "@/pages/other_package/components/share-popup.vue";
export default {
components: {
Header,
SharePopup
},
data() {
return {
inviteImage: null,
imageLoading: false,
shareImg: '',
shareShow: false,
shareOpen: true,
posterId: '',
// 邀请列表相关数据
list: [],
page: 1,
pageSize: 10,
totalPage: 1,
loading: false,
refreshing: false,
finished: false
};
},
mounted() {
this.getSharePoster();
this.getReferralList(true);
},
onShareAppMessage() {
return {
title: "邀请您体验信任桥TB商城",
path: "/pages/home/home",
imageUrl: this.inviteImage || this.shareImg,
};
},
onShareTimeline() {
return {
title: "邀请您体验信任桥TB商城",
query: "",
imageUrl: this.inviteImage || this.shareImg,
};
},
methods: {
// 获取邀请好友列表
async getReferralList(isRefresh = false) {
if (this.loading) return;
if (isRefresh) {
this.page = 1;
this.finished = false;
}
this.loading = true;
try {
const params = {
page: this.page,
pageSize: this.pageSize
};
const resp = await getDirectReferrals(params);
if (resp && (resp.bizcode === 100 || resp.bizcode === 0)) {
const data = resp.data || {};
const newEntities = data.entitys || data.entities || [];
if (isRefresh) {
this.list = newEntities;
} else {
this.list = [...this.list, ...newEntities];
}
this.totalPage = data.totalPage || 1;
if (this.page >= this.totalPage || newEntities.length < this.pageSize) {
this.finished = true;
}
}
} catch (error) {
console.error("获取邀请列表失败:", error);
} finally {
this.loading = false;
this.refreshing = false;
}
},
// 下拉刷新
async onRefresh() {
this.refreshing = true;
await this.getReferralList(true);
},
// 触底加载更多
onReachBottom() {
if (!this.finished && !this.loading) {
this.page++;
this.getReferralList(false);
}
},
// 打开分享弹窗 / 微信小程序下直接触发朋友圈/分享
openSharePopup() {
if (!this.inviteImage && !this.shareImg) {
this.$refs.uToastRef.show({
type: 'warning',
message: "分享图正在加载,请稍后重试",
});
return;
}
// #ifdef MP-WEIXIN
this.onWxShare(1);
return;
// #endif
this.shareShow = true;
},
// 关闭分享弹窗
onClose() {
this.shareShow = false;
},
// 微信小程序环境分享
onWxShare(index) {
console.log('小程序分享', index);
if (index === 0) {
this.onClose();
} else if (index === 1) {
// #ifdef MP-WEIXIN
if (typeof wx !== 'undefined' && wx.showShareImageMenu && this.shareImg) {
wx.showShareImageMenu({
path: this.shareImg,
fail: () => {
uni.showShareMenu({
withShareTicket: true,
menus: ["shareTimeline"]
});
}
});
} else {
uni.showShareMenu({
withShareTicket: true,
menus: ["shareTimeline"]
});
}
// #endif
this.onClose();
}
},
// App 及 H5 环境分享
onShare(index) {
console.log('App/H5分享', index);
if (index === 0) {
// 微信好友
// #ifdef APP-PLUS
uni.share({
provider: 'weixin',
scene: 'WXSceneSession',
type: 2,
imageUrl: this.shareImg || this.inviteImage,
success: () => {
this.$refs.uToastRef.show({ type: 'success', message: '分享成功' });
},
fail: (err) => {
console.error('App分享好友失败:', err);
this.savePosterToAlbum();
}
});
// #endif
// #ifdef H5
uni.previewImage({ urls: [this.shareImg || this.inviteImage] });
this.$refs.uToastRef.show({ type: 'info', message: '请长按图片发送给好友' });
// #endif
this.onClose();
} else if (index === 1) {
// 微信朋友圈
// #ifdef APP-PLUS
uni.share({
provider: 'weixin',
scene: 'WXSceneTimeline',
type: 2,
imageUrl: this.shareImg || this.inviteImage,
success: () => {
this.$refs.uToastRef.show({ type: 'success', message: '分享成功' });
},
fail: (err) => {
console.error('App分享朋友圈失败:', err);
this.savePosterToAlbum();
}
});
// #endif
// #ifdef H5
uni.previewImage({ urls: [this.shareImg || this.inviteImage] });
this.$refs.uToastRef.show({ type: 'info', message: '请长按图片分享到朋友圈' });
// #endif
this.onClose();
} else if (index === 2) {
// 复制链接
uni.setClipboardData({
data: 'https://m.tbmall.xin/#/pages/home/home',
success: () => {
this.$refs.uToastRef.show({ type: 'success', message: '链接已复制到剪贴板' });
}
});
this.onClose();
} else if (index === 3) {
// 保存图片
this.savePosterToAlbum();
this.onClose();
} else if (index === 4) {
// QQ
this.$refs.uToastRef.show({ type: 'warning', message: '暂未开放' });
this.onClose();
}
},
// 保存海报至相册
savePosterToAlbum() {
const imgPath = this.shareImg || this.inviteImage;
if (!imgPath) {
this.$refs.uToastRef.show({ type: 'warning', message: '海报图片生成中,请稍后重试' });
return;
}
// #ifdef APP-PLUS || MP-WEIXIN
uni.saveImageToPhotosAlbum({
filePath: imgPath,
success: () => {
this.$refs.uToastRef.show({ type: 'success', message: '海报已保存至手机相册' });
},
fail: (err) => {
console.error('保存至相册失败:', err);
if (err && err.errMsg && err.errMsg.includes('auth')) {
uni.showModal({
title: '提示',
content: '需要保存图片到相册权限,请在设置中开启相册权限',
success: (res) => {
if (res.confirm) uni.openSetting();
}
});
} else {
uni.previewImage({ urls: [imgPath] });
}
}
});
// #endif
// #ifdef H5
uni.previewImage({ urls: [imgPath] });
this.$refs.uToastRef.show({ type: 'info', message: '请长按图片保存到手机相册' });
// #endif
},
// 将base64转换为本地临时文件
base64ToTempFile(base64Data) {
return new Promise((resolve, reject) => {
if (!base64Data) {
return reject(new Error('Base64数据为空'));
}
// #ifdef APP-PLUS
try {
const bitmap = new plus.nativeObj.Bitmap('poster_' + Date.now());
bitmap.loadBase64Data(
base64Data,
() => {
const tempPath = `_doc/share_${Date.now()}.png`;
bitmap.save(
tempPath,
{ overwrite: true, format: 'png', quality: 100 },
(e) => {
bitmap.clear();
resolve(e.target);
},
(err) => {
bitmap.clear();
reject(err);
}
);
},
(err) => {
bitmap.clear();
reject(err);
}
);
} catch (err) {
reject(err);
}
// #endif
// #ifdef MP-WEIXIN
try {
const base64 = base64Data.replace(/^data:image\/\w+;base64,/, '');
const arrayBuffer = uni.base64ToArrayBuffer(base64);
const fs = typeof wx !== 'undefined' && wx.getFileSystemManager ? wx.getFileSystemManager() : uni.getFileSystemManager();
const tempFilePath = `${wx.env.USER_DATA_PATH}/share_${Date.now()}.png`;
fs.writeFileSync(tempFilePath, arrayBuffer, 'binary');
resolve(tempFilePath);
} catch (err) {
reject(err);
}
// #endif
// #ifdef H5
resolve(base64Data);
// #endif
});
},
// 查询分享模版
async getSharePoster() {
this.imageLoading = true;
try {
const resp = await getSharePoster();
if (resp && resp.bizcode === 100) {
const data = resp.data[0];
if (data) this.posterId = data.id || '';
const generateResp = await generateSharePoster({ id: data.id });
if (generateResp && generateResp.bizcode === 100) {
this.inviteImage = generateResp.data;
this.shareImg = await this.base64ToTempFile(generateResp.data);
console.log('图片转换成功:', this.shareImg);
}
}
} catch (error) {
console.error("获取分享图失败:", error);
} finally {
this.imageLoading = false;
}
}
}
};
</script>
<style lang="scss" scoped>
.invite_friends_warp {
height: 100vh;
display: flex;
flex-direction: column;
background: linear-gradient(180deg, #F3ECFF 0%, #F6F3FE 300rpx, #F5F2FC 600rpx, #F5F2FC 100%);
overflow: hidden;
box-sizing: border-box;
}
/* 头部 Banner 区域 (固定不滑动) */
.banner_header {
flex-shrink: 0;
display: flex;
align-items: flex-start;
justify-content: space-between;
padding: 20rpx 20rpx 10rpx 40rpx;
position: relative;
.banner_left {
flex: 1;
padding-top: 16rpx;
.title_row {
display: flex;
align-items: flex-start;
font-size: 40rpx;
font-weight: 800;
line-height: 1.25;
color: #1D1D1F;
.title_black {
color: #1D1D1F;
}
.title_purple {
color: #8C48FF;
margin-left: 6rpx;
}
}
.sub_title {
font-size: 22rpx;
color: #949497;
margin-top: 16rpx;
letter-spacing: 0.5rpx;
}
}
.banner_right {
width: 260rpx;
height: 230rpx;
display: flex;
justify-content: flex-end;
align-items: flex-start;
}
}
/* 白框卡片:填充中间区域 */
.card_container {
flex: 1;
height: 0;
min-height: 0;
display: flex;
flex-direction: column;
background: #FFFFFF;
border-radius: 24rpx;
margin: 0 24rpx 20rpx;
padding: 32rpx 24rpx;
box-shadow: 0 4rpx 20rpx rgba(121, 52, 246, 0.03);
box-sizing: border-box;
overflow: hidden;
.card_title {
flex-shrink: 0;
font-size: 32rpx;
font-weight: bold;
color: #1D1D1F;
margin-bottom: 28rpx;
}
}
/* 无数据空状态 */
.empty_box {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.empty_text {
font-size: 26rpx;
color: #8A8A8E;
margin-top: 24rpx;
}
}
/* 初次加载动画居中 */
.loading_box {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}
/* 有数据表格状态 */
.table_box {
flex: 1;
height: 0;
min-height: 0;
display: flex;
flex-direction: column;
border: 1rpx solid #F0EBF8;
border-radius: 16rpx;
overflow: hidden;
/* 固定表头 */
.table_header {
flex-shrink: 0;
display: flex;
background-color: #FAF7FF;
border-bottom: 1rpx solid #F0EBF8;
.th {
flex: 1;
text-align: center;
padding: 22rpx 8rpx;
font-size: 26rpx;
font-weight: 500;
color: #666666;
&.th_border {
border-right: 1rpx solid #F0EBF8;
}
}
}
/* 仅此处表格列表可垂直滑动,支持 App / 微信小程序 / H5 三端兼容 */
.table_body_scroll {
flex: 1;
height: 0;
min-height: 0;
box-sizing: border-box;
/* #ifdef H5 || APP-PLUS */
::v-deep uni-scroll-view,
::v-deep .uni-scroll-view {
height: 100% !important;
}
/* #endif */
.table_body {
.table_row {
display: flex;
border-bottom: 1rpx solid #F0EBF8;
&:last-child {
border-bottom: none;
}
.td {
flex: 1;
text-align: center;
padding: 24rpx 8rpx;
font-size: 26rpx;
color: #333333;
display: flex;
align-items: center;
justify-content: center;
word-break: break-all;
&.td_border {
border-right: 1rpx solid #F0EBF8;
}
}
}
}
}
}
.load_more_tip {
text-align: center;
padding: 24rpx 0;
font-size: 22rpx;
color: #B0B0B0;
}
/* 底部固定分享按钮 */
.bottom_action {
flex-shrink: 0;
background: #FFFFFF;
padding: 20rpx 32rpx calc(24rpx + env(safe-area-inset-bottom));
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.04);
box-sizing: border-box;
.share_btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background: linear-gradient(135deg, #9553FF 0%, #7934F6 100%);
border-radius: 44rpx;
color: #FFFFFF;
font-weight: bold;
font-size: 32rpx;
border: none;
outline: none;
text-align: center;
&:active {
opacity: 0.9;
}
}
}
</style>