会员权益

This commit is contained in:
2026-04-29 15:33:58 +08:00
commit 54965243da
2787 changed files with 242809 additions and 0 deletions

View File

@@ -0,0 +1,678 @@
<template>
<view>
<!-- 自定义透明标题栏 -->
<view class="custom-title-bar" :style="{paddingTop: statusBarHeight + 'px'}">
<view class="custom-title-content">
<view class="custom-back-btn" @click="goBack">
<image class="custom-back-icon" src="/static/back.png" mode="aspectFit"></image>
</view>
<view class="custom-placeholder"></view>
</view>
</view>
<view class="page">
<!-- 顶部Tabs -->
<view style="display: flex;justify-content: center;padding-top: 150rpx;">
<view class="top-tabs">
<view v-for="(item,index) in tabs" :key="index" class="tab-item"
:class="{ active: activeTab == item.id }" @click="switchTab(item.id)">
{{item.name}}
</view>
</view>
</view>
<!-- ====================== 个人会员 ====================== -->
<view v-if="activeTab == 1">
<view style="padding-left: 30rpx;">
<!-- 卡片轮播 -->
<swiper class="card-swiper" :current="personCurrent" @change="onPersonChange" next-margin="26rpx">
<swiper-item v-for="(item, idx) in personList" :key="idx">
<view class="card-wrapper" @click="handleCardClick(item, idx)">
<image :src="item.src" :class="{ cardActive: personCurrent === idx }"
class="card-img" lazy-load/>
<view class="price-tag" v-if="item.fees"
:style="{ color: item.feess >= 100000 ? '#FFE4A4' : '#1d1d1d' }" >
<text class="price-number" style="font-weight: bold;">{{ item.fees }}</text>
<text style="font-size: 24rpx; margin-left: 4rpx;">
<text></text>
<text style="margin:0 10rpx">/</text>
<text>1</text>
<text style="margin-left: 3rpx;"></text>
</text>
</view>
</view>
</swiper-item>
</swiper>
</view>
<!-- 底部导航 -->
<scroll-view scroll-x class="nav-scroll" :show-scrollbar="false" scroll-with-animation
:scroll-left="personScrollLeft" @touchstart="onPersonTouchStart" @touchend="onPersonTouchEnd">
<view class="nav-wrapper">
<view class="nav-list" :style="personNavStyleStr">
<view class="nav-item" :class="{ active: personCurrent === idx }"
v-for="(item, idx) in personList" :key="idx" :id="'person-nav-' + idx">
{{ item.name }}
</view>
</view>
</view>
</scroll-view>
<!-- 滑动条 -->
<view style="display: flex; align-items: center;justify-content: center;">
<image src="/static/member/hdt.png" style="width: 630rpx;height: 4rpx;padding-right: 30rpx;">
</image>
</view>
<!-- 会员权益 -->
<view class="grid-content">
<view class="grid-container">
<view class="grid-title">会员权益</view>
<view class="grid-page">
<view class="grid-item" v-for="(right, rIdx) in personCurrentRights[0]" :key="rIdx">
<!-- <view>{{JSON.stringify(personCurrentRights)}}----------</view>
<view>{{JSON.stringify(right)}}</view> -->
<image :src="right.image" class="grid-item-img" lazy-load></image>
<view class="grid-item-text">{{ right.title }}</view>
</view>
</view>
</view>
</view>
<!-- 权益对照表 -->
<view class="table-section">
<view class="table-title">个人会员权益等级对照表</view>
<scroll-view scroll-x class="table-scroll" :show-scrollbar="false">
<view class="rights-table">
<view class="table-row thead">
<view class="cell first-col">权益项目</view>
<view class="cell maxw" :class="{
bg2: idx === 0,
bg4: idx === 2,
bg6: idx === 4,
bg8: idx === 6,
'last-col': idx === personList.length - 1
}" v-for="(item, idx) in personList" :key="idx">
{{ item.name }}
</view>
</view>
<view class="table-row" v-for="(item, idx) in personTableData" :key="idx">
<view class="cell first-col text-left">{{ item.name }}</view>
<view class="cell cF5 maxw" style="display: flex;justify-content: center;" :class="{
bg2: colIdx === 0,
bg4: colIdx === 2,
bg6: colIdx === 4,
bg8: colIdx === 6,
'last-col': colIdx === personList.length - 1
}" v-for="(level, colIdx) in personList" :key="colIdx">
<!-- {{ item['v' + (colIdx + 1)] }} -->
<image v-if="item['v' + (colIdx + 1)] === '√'" src="/static/member/gg.png"
class="table-icon" mode="aspectFit">
</image>
<text v-else>{{ item['v' + (colIdx + 1)] }}</text>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
<!-- ====================== 单位会员 ====================== -->
<view v-if="activeTab == 2">
<view style="padding-left: 30rpx;">
<!-- 卡片轮播 -->
<swiper class="card-swiper" :current="companyCurrent" @change="onCompanyChange" next-margin="26rpx">
<swiper-item v-for="(item, idx) in companyList" :key="idx">
<view class="card-wrapper" @click="handleCardClick(item, idx)">
<image :src="item.src" :class="{ cardActive: companyCurrent === idx }" class="card-img" lazy-load/>
<view class="price-tag" v-if="item.fees"
:style="{ color: item.feess >= 100000 ? '#FFE4A4' : '#1d1d1d' }">
<text class="price-number" style="font-weight: bold;">{{ item.fees }}</text>
<text style="font-size: 24rpx; margin-left: 4rpx;">
<text></text>
<text style="margin:0 10rpx">/</text>
<text>1</text>
<text style="margin-left: 3rpx;"></text>
</text>
</view>
</view>
</swiper-item>
</swiper>
</view>
<!-- 底部导航 -->
<scroll-view scroll-x class="nav-scroll" :show-scrollbar="false" scroll-with-animation
:scroll-left="companyScrollLeft" @touchstart="onCompanyTouchStart" @touchend="onCompanyTouchEnd">
<view class="nav-wrapper">
<view class="nav-list" :style="companyNavStyleStr">
<view class="nav-item" :class="{ active: companyCurrent === idx }"
v-for="(item, idx) in companyList" :key="idx" :id="'company-nav-' + idx">
{{ item.name }}
</view>
</view>
</view>
</scroll-view>
<!-- 滑动条 -->
<view style="display: flex; align-items: center;justify-content: center;">
<image src="/static/member/hdt.png" style="width: 630rpx;height: 4rpx;padding-right: 30rpx;">
</image>
</view>
<!-- 会员权益 -->
<view class="grid-content">
<view class="grid-container">
<view class="grid-title">会员权益</view>
<view class="grid-page">
<view class="grid-item" v-for="(right, rIdx) in companyCurrentRights[0]" :key="rIdx">
<image :src="right.image" class="grid-item-img" lazy-load></image>
<view class="grid-item-text">{{ right.title }}</view>
</view>
</view>
</view>
</view>
<!-- 权益对照表 -->
<view class="table-section">
<view class="table-title">单位会员权益等级对照表</view>
<scroll-view scroll-x class="table-scroll" :show-scrollbar="false">
<view class="rights-table">
<view class="table-row thead">
<view class="cell first-col">权益项目</view>
<view class="cell maxw" :class="{
bg2: idx === 0,
bg4: idx === 2,
bg6: idx === 4,
bg8: idx === 6,
'last-col': idx === companyList.length - 1
}" v-for="(item, idx) in companyList" :key="idx">
{{ item.name }}
</view>
</view>
<view class="table-row" v-for="(item, idx) in companyTableData" :key="idx">
<view class="cell first-col text-left">{{ item.name }}</view>
<view class="cell cF5 maxw" style="display: flex;justify-content: center;" :class="{
bg2: colIdx === 0,
bg4: colIdx === 2,
bg6: colIdx === 4,
bg8: colIdx === 6,
'last-col': colIdx === companyList.length - 1
}" v-for="(level, colIdx) in companyList" :key="colIdx">
<image v-if="item['v' + (colIdx + 1)] === '√'" src="/static/member/gg.png"
class="table-icon" mode="aspectFit">
</image>
<text v-else>{{ item['v' + (colIdx + 1)] }}</text>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
<!-- 会员动态 -->
<view class="dt" @click="handleToHy" v-if="dtTitle">
<view class="dt-title">会员动态</view>
<view class="dt-main">
<view class="dt-main-img">
<image :src="dtImg" mode="" style="width: 220rpx;height: 160rpx;display: block;" ></image>
</view>
<view class="dt-main-text">
<view class="dt-main-text-t">{{dtTitle}}</view>
<view class="dt-main-text-s">时间:{{dtTime}}</view>
</view>
</view>
</view>
<!-- 入会标准 -->
<view class="bz">
<view class="bz-title">会员标准</view>
<view class="bz-main">
<image :src="src1" mode="widthFix" style="width: 690rpx;"></image>
</view>
</view>
<!-- 入会流程 -->
<view class="lc">
<view class="lc-title">会员标准</view>
<view class="lc-main">
<image :src="src2" mode="widthFix" style="width: 690rpx;"></image>
</view>
</view>
<!-- 申请入会 -->
<view class="rh" @click="handleCardClick">
<view class="rh-btn">申请入会</view>
</view>
<view style="width: 100%;height: 86rpx;"></view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
tabs: [],
activeTab: 1,
screenWidth: 375,
switchTimer: null,
// 个人会员
personCurrent: 0,
personScrollLeft: 0,
personStartX: 0,
personItemWidths: [],
personNavStyleStr: "padding-left:0px;padding-right:0px",
personInited: false,
personList: [],
personTableData: [],
dtTitle: '',
dtImg: '',
dtTime: '',
articleId: '',
// 单位会员
companyCurrent: 0,
companyScrollLeft: 0,
companyStartX: 0,
companyItemWidths: [],
companyNavStyleStr: "padding-left:0px;padding-right:0px",
companyInited: false,
companyList: [],
companyTableData: [],
src1: '',
src2: '',
// 标题栏
statusBarHeight: 0,
};
},
onLoad() {
// 获取状态栏高度
const systemInfo = uni.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight;
this.screenWidth = systemInfo.screenWidth;
this.getMemberType()
this.getMemberBenefitsList(1)
this.getMemberBenefitsList(2)
const adminPath = getApp().globalData.adminPath;
this.src1 = adminPath + '/assets/addons/wdsxh/img/member_benefits_apply_1.png'
this.src2 = adminPath + '/assets/addons/wdsxh/img/member_benefits_apply_2.png'
console.log(this.src1,'=this.src1=')
console.log(this.src2,'=this.src2=')
},
onReady() {
// 如果数据已经加载完成,直接初始化
if (this.personList && this.personList.length > 0) {
this.initPersonNav();
}
if (this.companyList && this.companyList.length > 0) {
this.initCompanyNav();
}
},
computed: {
// 个人:只显示 has=true 的权益
personCurrentRights() {
const item = this.personList[this.personCurrent];
if (!item || !item.rights) return [];
return item.rights
},
// 单位:只显示 has=true 的权益
companyCurrentRights() {
const item = this.companyList[this.companyCurrent];
if (!item || !item.rights) return [];
return item.rights
}
},
methods: {
// 返回上一页
goBack() {
if (getCurrentPages().length == 1) {
uni.switchTab({
url: "/pages/index/index"
})
} else {
uni.navigateBack()
}
},
// 格式化金额
formatNumber(num) {
if (!num) return '0';
let n = Number(num)
if (n >= 10000) {
let result = n / 10000;
return (Number.isInteger(result) ? result : result.toFixed(1).replace(/\.0$/, '')) + '万';
} else {
let result = n / 1000;
let formatted = Number.isInteger(result) ? result : result.toFixed(1).replace(/\.0$/, '');
return formatted + 'k';
}
},
// 会员类型(单位,个人)
getMemberType() {
this.$util.request("member.typeList").then(res => {
if (res.code == 1) {
this.tabs = res.data
} else {
uni.showToast({
title: res.msg,
icon: 'none'
})
}
}).catch(error => {
console.error('获取入会类型 ', error)
})
},
getMemberBenefitsList(id) {
this.$util.request("member.benefitsList", {
join_config_id: id
}).then(res => {
if (res.code == 1) {
//文章
if (res.data && res.data.articles) {
const art = res.data.articles
this.dtTitle = art.title || ''
this.dtImg = art.image || ''
this.dtTime = art.createtime || ''
this.articleId = art.id
}
let list = res.data.levels_benefits || []
let result = list.map(item => ({
name: item.name,
src: item.image,
feess: item.fees ? Number(item.fees) : '',
fees: this.formatNumber(item.fees),
rights:item.benefits.map(item=>{
return item
})
}))
let tableData = []
if (list.length > 0 && list[0].all_benefits_projects) {
let allBenefits = list[0].all_benefits_projects || []
tableData = allBenefits.map((ben, idx) => {
let row = {
name: ben.name
}
list.forEach((item, i) => {
let has = item.all_benefits_projects[idx]?.has
row[`v${i+1}`] = has ? "√" : "-"
})
return row
})
}
if (id === 1) {
this.personList = result
this.personCurrent = 0
this.personTableData = tableData
this.$nextTick(() => {
this.initPersonNav();
})
} else {
this.companyList = result
this.companyCurrent = 0
this.companyTableData = tableData
this.$nextTick(() => {
this.initCompanyNav();
})
}
} else {
uni.showToast({
title: res.msg,
icon: 'none'
})
}
}).catch(error => {
console.error('获取入会类型 ', error)
})
},
handleToHy() {
this.$util.toPage({
mode: 1,
path: `/pages/article/details?title=${'会员动态'}&id=${this.articleId}`
})
},
handleCardClick() {
this.$util.toPage({
mode: 1,
path: `/pages/member/apply/form?activeTab=${this.activeTab}`
})
},
switchTab(tab) {
this.activeTab = tab;
if (this.switchTimer) clearTimeout(this.switchTimer);
if (tab === 1) {
if (!this.personInited) {
this.switchTimer = setTimeout(() => {
this.initPersonNav();
}, 100);
}
}
if (tab === 2) {
if (!this.companyInited) {
this.switchTimer = setTimeout(() => {
this.initCompanyNav();
}, 100);
}
}
},
// ====================== 个人会员导航相关 ======================
initPersonNav() {
if (this.personInited) return;
this.$nextTick(() => {
const query = uni.createSelectorQuery().in(this);
this.personList.forEach((_, idx) => {
query.select(`#person-nav-${idx}`).boundingClientRect();
});
query.exec(res => {
if (!res) return;
this.personItemWidths = res.map(rect => rect?.width || 70);
const firstW = this.personItemWidths[0] || 70;
const pl = (this.screenWidth - firstW) / 2;
const pr = (this.screenWidth - firstW) / 2;
this.personNavStyleStr = "padding-left:" + pl + "px;padding-right:" + pr + "px";
this.personInited = true;
// 初始化完成后滚动到第一个
setTimeout(() => {
this.scrollToPersonActive(0);
}, 50);
});
});
},
scrollToPersonActive(index) {
this.$nextTick(() => {
const query = uni.createSelectorQuery().in(this);
query.select(`#person-nav-${index}`).boundingClientRect();
query.select('.nav-scroll').boundingClientRect();
query.select('.nav-list').boundingClientRect();
query.exec(res => {
if (!res || res.length < 3) return;
const navItemRect = res[0];
const scrollRect = res[1];
const listRect = res[2];
if (!navItemRect || !scrollRect || !listRect) return;
// 计算当前导航项相对于滚动容器左边缘的距离
const itemRelativeLeft = navItemRect.left - scrollRect.left;
// 计算让导航项居中时,它应该相对于滚动容器左边缘的位置
const targetRelativeLeft = (scrollRect.width - navItemRect.width) / 2;
// 计算需要滚动的距离
let targetScrollLeft = this.personScrollLeft + (itemRelativeLeft - targetRelativeLeft);
// 限制滚动范围
targetScrollLeft = Math.max(0, targetScrollLeft);
const maxScrollLeft = listRect.width - scrollRect.width;
targetScrollLeft = Math.min(targetScrollLeft, maxScrollLeft);
// 更新滚动位置
this.personScrollLeft = targetScrollLeft;
});
});
},
onPersonTouchStart(e) {
this.personStartX = e.touches[0].clientX;
},
onPersonTouchEnd(e) {
const diff = e.changedTouches[0].clientX - this.personStartX;
if (Math.abs(diff) < 30) return;
let next = diff < 0 ? Math.min(this.personCurrent + 1, this.personList.length - 1) : Math.max(this.personCurrent - 1, 0);
this.personCurrent = next;
},
onPersonChange(e) {
this.personCurrent = e.detail.current;
setTimeout(() => {
this.scrollToPersonActive(this.personCurrent);
}, 50);
},
// ====================== 单位会员导航相关 ======================
initCompanyNav() {
if (this.companyInited) return;
this.$nextTick(() => {
const query = uni.createSelectorQuery().in(this);
this.companyList.forEach((_, idx) => {
query.select(`#company-nav-${idx}`).boundingClientRect();
});
query.exec(res => {
if (!res) return;
this.companyItemWidths = res.map(rect => rect?.width || 70);
const firstW = this.companyItemWidths[0] || 70;
const pl = (this.screenWidth - firstW) / 2;
const pr = (this.screenWidth - firstW) / 2;
this.companyNavStyleStr = "padding-left:" + pl + "px;padding-right:" + pr + "px";
this.companyInited = true;
// 初始化完成后滚动到第一个
setTimeout(() => {
this.scrollToCompanyActive(0);
}, 50);
});
});
},
scrollToCompanyActive(index) {
this.$nextTick(() => {
const query = uni.createSelectorQuery().in(this);
query.select(`#company-nav-${index}`).boundingClientRect();
query.select('.nav-scroll').boundingClientRect();
query.select('.nav-list').boundingClientRect();
query.exec(res => {
if (!res || res.length < 3) return;
const navItemRect = res[0];
const scrollRect = res[1];
const listRect = res[2];
if (!navItemRect || !scrollRect || !listRect) return;
// 计算当前导航项相对于滚动容器左边缘的距离
const itemRelativeLeft = navItemRect.left - scrollRect.left;
// 计算让导航项居中时,它应该相对于滚动容器左边缘的位置
const targetRelativeLeft = (scrollRect.width - navItemRect.width) / 2;
// 计算需要滚动的距离
let targetScrollLeft = this.companyScrollLeft + (itemRelativeLeft - targetRelativeLeft);
// 限制滚动范围
targetScrollLeft = Math.max(0, targetScrollLeft);
const maxScrollLeft = listRect.width - scrollRect.width;
targetScrollLeft = Math.min(targetScrollLeft, maxScrollLeft);
// 更新滚动位置
this.companyScrollLeft = targetScrollLeft;
console.log('单位滚动调试:', {
当前索引: index,
当前滚动位置: this.companyScrollLeft,
项相对位置: itemRelativeLeft,
目标相对位置: targetRelativeLeft,
新滚动位置: targetScrollLeft,
最大滚动距离: maxScrollLeft
});
});
});
},
onCompanyTouchStart(e) {
this.companyStartX = e.touches[0].clientX;
},
onCompanyTouchEnd(e) {
const diff = e.changedTouches[0].clientX - this.companyStartX;
if (Math.abs(diff) < 30) return;
let next = diff < 0 ? Math.min(this.companyCurrent + 1, this.companyList.length - 1) : Math.max(this.companyCurrent - 1, 0);
this.companyCurrent = next;
},
onCompanyChange(e) {
this.companyCurrent = e.detail.current;
setTimeout(() => {
this.scrollToCompanyActive(this.companyCurrent);
}, 50);
},
}
};
</script>
<style scoped>
@import 'index.css';
/* 自定义透明标题栏样式 */
.custom-title-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
background: transparent;
}
.custom-title-content {
display: flex;
align-items: center;
justify-content: space-between;
height: 88rpx;
padding: 0 32rpx;
}
.custom-back-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.custom-back-icon {
width: 40rpx;
height: 40rpx;
}
.custom-title {
flex: 1;
text-align: center;
font-size: 36rpx;
font-weight: 500;
color: #FFFFFF;
letter-spacing: 2rpx;
}
.custom-placeholder {
width: 60rpx;
height: 60rpx;
}
.table-icon {
width: 18rpx;
height: 13rpx;
}
</style>