mi-assessment/uniapp/pages/index/index.vue
2026-04-09 14:38:35 +08:00

568 lines
13 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="home-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content" :style="{ height: navbarHeight + 'px' }">
<text class="navbar-title">首页</text>
</view>
</view>
<!-- 导航栏占位 -->
<view class="navbar-placeholder" :style="{ height: totalNavbarHeight + 'px' }"></view>
<!-- 页面内容 -->
<scroll-view
class="page-content"
scroll-y
refresher-enabled
:refresher-triggered="isRefreshing"
:style="{ height: 'calc(100vh - ' + totalNavbarHeight + 'px)' }"
@refresherrefresh="onRefresh"
>
<!-- Banner 轮播图 -->
<view class="banner-section" v-if="bannerList.length > 0">
<swiper
class="banner-swiper"
:indicator-dots="bannerList.length > 1"
indicator-color="rgba(255,255,255,0.5)"
indicator-active-color="#FFFFFF"
:autoplay="true"
:interval="4000"
:circular="true"
>
<swiper-item
v-for="(item, index) in bannerList"
:key="index"
@click="handleBannerClick(item)"
>
<image
:src="item.imageUrl"
mode="aspectFill"
class="banner-image"
/>
</swiper-item>
</swiper>
</view>
<!-- 专业测评入口 -->
<view class="section-card" v-if="assessmentList.length > 0">
<view class="section-header">
<view class="section-indicator"></view>
<text class="section-title">专业测评</text>
</view>
<scroll-view class="assessment-scroll" scroll-x enhanced :show-scrollbar="false">
<view class="assessment-grid">
<view
class="assessment-card"
v-for="(item, index) in assessmentList"
:key="index"
@click="handleCardClick(item)"
>
<image
:src="item.imageUrl"
mode="aspectFit"
class="assessment-image"
/>
</view>
</view>
</scroll-view>
</view>
<!-- 学业规划 -->
<view class="section-card">
<view class="section-header">
<view class="section-indicator"></view>
<text class="section-title">学业规划</text>
</view>
<view class="planner-btn" @click="goToPlanner">
<text class="planner-btn__text">预约入口</text>
</view>
</view>
<!-- 更多 -->
<view class="section-card" v-if="moreList.length > 0">
<view class="section-header">
<view class="section-indicator"></view>
<text class="section-title">更多</text>
</view>
<view class="more-grid">
<view
class="more-card"
v-for="(item, index) in moreList"
:key="index"
:class="{ 'more-card--full': moreList.length % 2 === 1 && index === moreList.length - 1 }"
@click="handleCardClick(item)"
>
<image
:src="item.imageUrl"
mode="aspectFit"
class="more-card__image"
/>
</view>
</view>
</view>
<!-- 加载状态 -->
<view class="loading-section" v-if="pageLoading">
<Loading type="inline" :loading="true" />
</view>
<!-- 底部安全间距 -->
<view class="safe-bottom"></view>
</scroll-view>
<!-- 客服二维码弹窗 -->
<view v-if="showQrPopup" class="popup-mask" @click="showQrPopup = false">
<view class="contact-popup" @click.stop>
<view class="contact-popup-header">
<text class="contact-popup-title">联系我们</text>
<text class="contact-popup-close" @click="showQrPopup = false">✕</text>
</view>
<view class="contact-popup-body">
<image
v-if="qrcodeUrl"
class="contact-qrcode"
:src="qrcodeUrl"
mode="widthFix"
@click="handlePreviewQrcode"
/>
<text v-if="!qrcodeUrl" class="contact-tip">暂未配置客服二维码</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { onLoad, onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
import { useUserStore } from '@/store/user.js'
import { useNavbar } from '@/composables/useNavbar.js'
import { getBannerList, getNavigationList } from '@/api/home.js'
import { getContactInfo } from '@/api/system.js'
import Loading from '@/components/Loading/index.vue'
const userStore = useUserStore()
const { statusBarHeight, navbarHeight, totalNavbarHeight } = useNavbar()
// 页面数据
const pageLoading = ref(true)
const isRefreshing = ref(false)
const bannerList = ref([])
const assessmentList = ref([])
const moreList = ref([])
const qrcodeUrl = ref('')
const showQrPopup = ref(false)
/**
* 加载Banner数据
*/
async function loadBannerList() {
try {
const res = await getBannerList()
if (res && res.code === 0 && res.data) {
bannerList.value = Array.isArray(res.data) ? res.data : (res.data.list || [])
}
} catch (error) {
console.error('加载Banner失败:', error)
}
}
/**
* 加载专业测评区域数据position=1
*/
async function loadAssessmentList() {
try {
const res = await getNavigationList({ position: 1 })
if (res && res.code === 0 && res.data) {
assessmentList.value = Array.isArray(res.data) ? res.data : (res.data.list || [])
}
} catch (error) {
console.error('加载专业测评数据失败:', error)
}
}
/**
* 加载更多区域数据position=2
*/
async function loadMoreList() {
try {
const res = await getNavigationList({ position: 2 })
if (res && res.code === 0 && res.data) {
moreList.value = Array.isArray(res.data) ? res.data : (res.data.list || [])
}
} catch (error) {
console.error('加载更多区域数据失败:', error)
}
}
/**
* 预览二维码图片
*/
function handlePreviewQrcode() {
if (!qrcodeUrl.value) return
uni.previewImage({
urls: [qrcodeUrl.value],
current: qrcodeUrl.value
})
}
/**
* 加载客服二维码URL
*/
async function loadContactInfo() {
try {
const res = await getContactInfo()
if (res && res.code === 0 && res.data) {
qrcodeUrl.value = res.data.qrcodeUrl || ''
}
} catch (error) {
console.error('加载客服二维码失败:', error)
}
}
/**
* 初始化页面数据
*/
async function initPageData() {
pageLoading.value = true
try {
userStore.restoreFromStorage()
await Promise.all([
loadBannerList(),
loadAssessmentList(),
loadMoreList(),
loadContactInfo()
])
} finally {
pageLoading.value = false
}
}
/**
* 内部页面跳转(兼容 TabBar 页面带参数)
* switchTab 不支持 query 参数,通过 globalData 传递
*/
function navigateToInternal(url) {
if (!url) return
// 解析路径和参数
const [path, queryStr] = url.split('?')
const query = {}
if (queryStr) {
queryStr.split('&').forEach(pair => {
const [key, val] = pair.split('=')
if (key) query[key] = decodeURIComponent(val || '')
})
}
uni.navigateTo({
url,
fail: () => {
// TabBar 页面:通过 globalData 传递参数
if (Object.keys(query).length > 0) {
getApp().globalData = getApp().globalData || {}
getApp().globalData.tabPageParams = query
}
uni.switchTab({ url: path })
}
})
}
/**
* 处理Banner点击
*/
function handleBannerClick(item) {
if (!item.linkUrl) return
if (item.linkType === 1) {
navigateToInternal(item.linkUrl)
} else if (item.linkType === 2) {
uni.navigateTo({
url: `/pages/webview/index?url=${encodeURIComponent(item.linkUrl)}`
})
}
}
/**
* 处理卡片点击(根据 ActionType 分发)
*/
function handleCardClick(item) {
if (item.actionType === 3) {
uni.showToast({ title: '即将上线', icon: 'none', duration: 2000 })
return
}
if (item.actionType === 2) {
showQrPopup.value = true
return
}
// actionType === 1: 跳转页面
if (!item.linkUrl) return
navigateToInternal(item.linkUrl)
}
/**
* 跳转学业规划预约
*/
function goToPlanner() {
uni.navigateTo({ url: '/pages/planner/list/index' })
}
/**
* 下拉刷新
*/
async function onRefresh() {
isRefreshing.value = true
await initPageData()
isRefreshing.value = false
}
onLoad((options) => {
// 解析分享链接中的邀请人参数
if (options && options.inviterId) {
console.log('[Index] 从页面参数获取inviterId:', options.inviterId)
uni.setStorageSync('inviterId', options.inviterId)
}
})
onMounted(() => {
initPageData()
})
// 微信分享
onShareAppMessage(() => ({
title: '学业邑规划 - 智能学业规划测评',
path: '/pages/index/index'
}))
onShareTimeline(() => ({
title: '学业邑规划 - 智能学业规划测评'
}))
</script>
<style lang="scss" scoped>
@import '@/styles/variables.scss';
.home-page {
min-height: 100vh;
background-color: $bg-color;
}
// 自定义导航栏
.custom-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
background-color: $bg-white;
z-index: 999;
.navbar-content {
display: flex;
align-items: center;
justify-content: center;
.navbar-title {
font-size: 34rpx;
font-weight: $font-weight-medium;
color: $text-color;
}
}
}
.navbar-placeholder {
width: 100%;
}
// 页面内容
.page-content {
// 高度通过内联 style 动态计算,不在此处设置
}
// Banner轮播
.banner-section {
width: 100%;
padding: 0 $spacing-lg;
box-sizing: border-box;
margin-top: $spacing-sm;
.banner-swiper {
width: 100%;
height: 330rpx;
border-radius: $border-radius-lg;
overflow: hidden;
.banner-image {
width: 100%;
height: 100%;
border-radius: $border-radius-lg;
}
}
}
// 通用区块卡片
.section-card {
margin: $spacing-lg $spacing-lg 0;
padding: $spacing-lg;
background-color: $bg-white;
border-radius: $border-radius-xl;
}
// 区块标题
.section-header {
display: flex;
align-items: center;
margin-bottom: $spacing-lg;
.section-indicator {
width: 8rpx;
height: 36rpx;
background-color: $warning-color;
border-radius: $border-radius-xs;
margin-right: $spacing-sm;
}
.section-title {
font-size: $font-size-xl;
font-weight: $font-weight-bold;
color: $text-color;
}
}
// 专业测评 - 横向滚动一屏最多显示2个
.assessment-scroll {
width: 100%;
white-space: nowrap;
}
.assessment-grid {
display: inline-flex;
gap: $spacing-md;
}
.assessment-card {
position: relative;
// 每个卡片宽度 = (卡片区域总宽 - 间距) / 2
// 卡片区域总宽 = 750rpx - 左右 section-card margin 32rpx*2 - 左右 section-card padding 32rpx*2 = 622rpx
width: 299rpx;
height: 240rpx;
overflow: hidden;
flex-shrink: 0;
.assessment-image {
width: 100%;
height: 100%;
}
}
// 学业规划 - 预约按钮
.planner-btn {
display: flex;
align-items: center;
justify-content: center;
height: 96rpx;
border-radius: $border-radius-round;
background: linear-gradient(90deg, #FFB347, #FF8C42);
&__text {
font-size: $font-size-lg;
font-weight: $font-weight-bold;
color: $text-white;
letter-spacing: 4rpx;
}
}
// 更多 - 网格布局每行2个
.more-grid {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.more-card {
position: relative;
width: 48.5%;
height: 160rpx;
overflow: hidden;
box-sizing: border-box;
margin-bottom: $spacing-md;
// 奇数个时最后一个占满整行
&--full {
width: 100%;
}
&__image {
width: 100%;
height: 100%;
}
}
// 加载状态
.loading-section {
display: flex;
justify-content: center;
padding: 100rpx 0;
}
// 底部安全区域
.safe-bottom {
height: 40rpx;
padding-bottom: env(safe-area-inset-bottom);
}
// 联系我们弹窗
.popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.contact-popup {
width: 90%;
background-color: $bg-white;
border-radius: $border-radius-xl;
overflow: hidden;
.contact-popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: $spacing-xl $spacing-xl 0;
.contact-popup-title {
font-size: $font-size-lg;
font-weight: $font-weight-bold;
color: $text-color;
}
.contact-popup-close {
font-size: $font-size-xl;
color: $text-placeholder;
}
}
.contact-popup-body {
display: flex;
flex-direction: column;
align-items: center;
padding: $spacing-xl;
.contact-qrcode {
width: 100%;
}
.contact-tip {
margin-top: $spacing-lg;
font-size: $font-size-sm;
color: $text-secondary;
}
}
}
</style>