360 lines
10 KiB
Vue
360 lines
10 KiB
Vue
<template>
|
||
<view class="feature-page">
|
||
<view class="page-header" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||
<view class="nav-bar">
|
||
<view class="nav-left" @click="goBack">
|
||
<text class="back-icon">←</text>
|
||
</view>
|
||
<text class="nav-title">成交归因分析</text>
|
||
</view>
|
||
</view>
|
||
|
||
<scroll-view class="content-scroll" scroll-y="true" :style="{ top: contentTop, bottom: contentBottom }">
|
||
<view class="stats-card">
|
||
<view class="stat-item">
|
||
<text class="stat-value">{{ conversionRate }}%</text>
|
||
<text class="stat-label">总体转化率</text>
|
||
</view>
|
||
<view class="stat-item">
|
||
<text class="stat-value">{{ totalDeals }}</text>
|
||
<text class="stat-label">成交总数</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="chart-section">
|
||
<text class="section-title">成交因素分析图</text>
|
||
<view class="chart-placeholder">
|
||
<text class="placeholder-text">📊 图表区域</text>
|
||
<text class="placeholder-desc">此处将显示成交归因分析图表</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="data-list">
|
||
<text class="section-title">关键成交因素</text>
|
||
<view class="list-item" v-for="(item, index) in dataList" :key="index">
|
||
<view class="item-left">
|
||
<text class="item-date">{{ item.factor }}</text>
|
||
<text class="item-desc">{{ item.description }}</text>
|
||
</view>
|
||
<view class="item-right">
|
||
<text class="item-percentage">{{ item.percentage }}%</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
// 获取系统信息的辅助函数
|
||
function getSystemInfo() {
|
||
// #ifdef MP-WEIXIN
|
||
// 微信小程序:优先使用新的 API
|
||
if (typeof wx !== 'undefined' && wx.getWindowInfo) {
|
||
try {
|
||
const windowInfo = wx.getWindowInfo();
|
||
return {
|
||
statusBarHeight: windowInfo.statusBarHeight || 20,
|
||
windowWidth: windowInfo.windowWidth,
|
||
windowHeight: windowInfo.windowHeight
|
||
};
|
||
} catch (e) {
|
||
console.warn('[DealAttribution] Failed to use wx.getWindowInfo, fallback to getSystemInfoSync:', e);
|
||
}
|
||
}
|
||
// #endif
|
||
|
||
// 回退到旧的 API 或使用 uni-app 的 API
|
||
try {
|
||
const systemInfo = uni.getSystemInfoSync();
|
||
return {
|
||
statusBarHeight: systemInfo.statusBarHeight || 20,
|
||
windowWidth: systemInfo.windowWidth,
|
||
windowHeight: systemInfo.windowHeight
|
||
};
|
||
} catch (e) {
|
||
console.error('[DealAttribution] Failed to get system info:', e);
|
||
return {
|
||
statusBarHeight: 20,
|
||
windowWidth: 375,
|
||
windowHeight: 667
|
||
};
|
||
}
|
||
}
|
||
|
||
export default {
|
||
data() {
|
||
// 动态计算导航栏高度
|
||
const systemInfo = getSystemInfo();
|
||
const statusBarHeight = systemInfo.statusBarHeight;
|
||
const navbarHeight = 44; // navbar高度44px
|
||
// 转换为rpx:1px = 2rpx(在375px设计稿下)
|
||
const statusBarHeightRpx = statusBarHeight * 2;
|
||
const navbarHeightRpx = navbarHeight * 2;
|
||
const totalNavbarHeight = statusBarHeightRpx + navbarHeightRpx;
|
||
|
||
return {
|
||
statusBarHeight: statusBarHeight, // 状态栏高度(px)
|
||
contentTop: totalNavbarHeight + 'rpx', // 内容区域距离顶部的距离
|
||
contentBottom: '0rpx', // 内容区域距离底部的距离(默认值,会在 onReady 中更新)
|
||
conversionRate: '15.8',
|
||
totalDeals: '201',
|
||
dataList: [
|
||
{ factor: '产品优势', description: '突出产品核心优势', percentage: '35.2' },
|
||
{ factor: '价格优惠', description: '提供合适的价格优惠', percentage: '28.4' },
|
||
{ factor: '服务质量', description: '优质的售后服务', percentage: '18.7' },
|
||
{ factor: '客户关系', description: '良好的客户关系维护', percentage: '12.3' },
|
||
{ factor: '竞争优势', description: '相对于竞品的优势', percentage: '5.4' }
|
||
]
|
||
}
|
||
},
|
||
onReady() {
|
||
// 页面渲染完成后,查询实际导航栏高度并更新位置
|
||
this.$nextTick(() => {
|
||
// 查询导航栏实际高度
|
||
const query = uni.createSelectorQuery().in(this);
|
||
query.select('.nav-bar').boundingClientRect((data) => {
|
||
if (data && data.height) {
|
||
// 使用实际高度
|
||
const actualHeight = data.height; // 实际导航栏高度(px)
|
||
// 转换为rpx:1px = 2rpx(在375px设计稿下)
|
||
const actualHeightRpx = actualHeight * 2;
|
||
const statusBarHeightRpx = this.statusBarHeight * 2;
|
||
const totalHeight = statusBarHeightRpx + actualHeightRpx;
|
||
// 稍微减小一点,确保紧贴
|
||
const adjustedHeight = Math.max(totalHeight - 4, 0);
|
||
this.$set(this, 'contentTop', adjustedHeight + 'rpx');
|
||
console.log('[DealAttribution] 使用实际导航栏高度:', actualHeight, 'px =', actualHeightRpx, 'rpx, 总高度:', totalHeight, 'rpx, 调整后:', adjustedHeight, 'rpx');
|
||
} else {
|
||
// 如果查询失败,使用默认计算
|
||
const systemInfo = getSystemInfo();
|
||
const statusBarHeight = systemInfo.statusBarHeight;
|
||
const navbarHeight = 44;
|
||
const statusBarHeightRpx = statusBarHeight * 2;
|
||
const navbarHeightRpx = navbarHeight * 2;
|
||
const totalNavbarHeight = statusBarHeightRpx + navbarHeightRpx;
|
||
const adjustedHeight = Math.max(totalNavbarHeight - 4, 0);
|
||
this.$set(this, 'contentTop', adjustedHeight + 'rpx');
|
||
}
|
||
}).exec();
|
||
|
||
// 查询底部 tabbar 实际高度(如果有)
|
||
const queryTabbar = uni.createSelectorQuery().in(this);
|
||
queryTabbar.select('.tabbar').boundingClientRect((tabbarData) => {
|
||
if (tabbarData && tabbarData.height) {
|
||
// 使用实际高度
|
||
const tabbarHeight = tabbarData.height; // tabbar 实际高度(px)
|
||
const tabbarHeightRpx = tabbarHeight * 2;
|
||
// 稍微减小一点,确保紧贴
|
||
const adjustedBottom = Math.max(tabbarHeightRpx - 4, 0);
|
||
this.$set(this, 'contentBottom', adjustedBottom + 'rpx');
|
||
console.log('[DealAttribution] 使用实际tabbar高度:', tabbarHeight, 'px =', tabbarHeightRpx, 'rpx, 调整后:', adjustedBottom, 'rpx');
|
||
} else {
|
||
// 如果查询失败,使用默认值(无tabbar)
|
||
this.$set(this, 'contentBottom', '0rpx');
|
||
}
|
||
}).exec();
|
||
|
||
// 延迟再次更新,确保DOM已渲染
|
||
setTimeout(() => {
|
||
// 再次查询并更新导航栏
|
||
const query2 = uni.createSelectorQuery().in(this);
|
||
query2.select('.nav-bar').boundingClientRect((data) => {
|
||
if (data && data.height) {
|
||
const actualHeight = data.height;
|
||
const actualHeightRpx = actualHeight * 2;
|
||
const statusBarHeightRpx = this.statusBarHeight * 2;
|
||
const totalHeight = statusBarHeightRpx + actualHeightRpx;
|
||
const adjustedHeight = Math.max(totalHeight - 4, 0);
|
||
this.$set(this, 'contentTop', adjustedHeight + 'rpx');
|
||
console.log('[DealAttribution] 延迟更新导航栏高度:', actualHeight, 'px =', actualHeightRpx, 'rpx, 总高度:', totalHeight, 'rpx, 调整后:', adjustedHeight, 'rpx');
|
||
}
|
||
}).exec();
|
||
|
||
// 再次查询 tabbar 高度
|
||
const queryTabbar2 = uni.createSelectorQuery().in(this);
|
||
queryTabbar2.select('.tabbar').boundingClientRect((tabbarData) => {
|
||
if (tabbarData && tabbarData.height) {
|
||
const tabbarHeight = tabbarData.height;
|
||
const tabbarHeightRpx = tabbarHeight * 2;
|
||
const adjustedBottom = Math.max(tabbarHeightRpx - 4, 0);
|
||
this.$set(this, 'contentBottom', adjustedBottom + 'rpx');
|
||
console.log('[DealAttribution] 延迟更新tabbar高度:', tabbarHeight, 'px =', tabbarHeightRpx, 'rpx, 调整后:', adjustedBottom, 'rpx');
|
||
}
|
||
}).exec();
|
||
}, 200);
|
||
});
|
||
},
|
||
methods: {
|
||
goBack() {
|
||
uni.navigateBack()
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.feature-page {
|
||
min-height: 100vh;
|
||
background: #f8f9fa;
|
||
}
|
||
|
||
.page-header {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
z-index: 999;
|
||
background: #ffffff;
|
||
border-bottom: 1rpx solid #e9ecef;
|
||
}
|
||
|
||
.nav-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
height: 88rpx;
|
||
padding: 0 30rpx;
|
||
}
|
||
|
||
.nav-left {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.back-icon {
|
||
font-size: 36rpx;
|
||
color: #333333;
|
||
}
|
||
|
||
.nav-title {
|
||
flex: 1;
|
||
text-align: center;
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
}
|
||
|
||
.content-scroll {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
top: 0; /* 先从顶部开始,通过内联样式动态设置 */
|
||
bottom: 0; /* 先从底部开始,通过内联样式动态设置 */
|
||
/* top和bottom值通过内联样式动态设置,覆盖上面的默认值 */
|
||
padding: 0 !important;
|
||
margin: 0 !important;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.stats-card {
|
||
display: flex;
|
||
margin: 20rpx 30rpx;
|
||
background: #ffffff;
|
||
border-radius: 16rpx;
|
||
padding: 40rpx 30rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.stat-item {
|
||
flex: 1;
|
||
text-align: center;
|
||
}
|
||
|
||
.stat-item:not(:last-child) {
|
||
border-right: 1rpx solid #e9ecef;
|
||
}
|
||
|
||
.stat-value {
|
||
display: block;
|
||
font-size: 48rpx;
|
||
font-weight: bold;
|
||
color: #007aff;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
|
||
.stat-label {
|
||
display: block;
|
||
font-size: 24rpx;
|
||
color: #666666;
|
||
}
|
||
|
||
.chart-section, .data-list {
|
||
margin: 20rpx 30rpx;
|
||
background: #ffffff;
|
||
border-radius: 16rpx;
|
||
overflow: hidden;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.section-title {
|
||
display: block;
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
padding: 30rpx;
|
||
border-bottom: 1rpx solid #e9ecef;
|
||
}
|
||
|
||
.chart-placeholder {
|
||
padding: 80rpx 30rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.placeholder-text {
|
||
display: block;
|
||
font-size: 48rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.placeholder-desc {
|
||
display: block;
|
||
font-size: 28rpx;
|
||
color: #666666;
|
||
}
|
||
|
||
.list-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 30rpx;
|
||
border-bottom: 1rpx solid #f0f0f0;
|
||
}
|
||
|
||
.list-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.item-left {
|
||
flex: 1;
|
||
}
|
||
|
||
.item-date {
|
||
display: block;
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.item-desc {
|
||
display: block;
|
||
font-size: 24rpx;
|
||
color: #666666;
|
||
}
|
||
|
||
.item-right {
|
||
text-align: right;
|
||
}
|
||
|
||
.item-percentage {
|
||
display: block;
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: #007aff;
|
||
}
|
||
</style>
|