Files
uni-fans-score/pages/index/index.vue
T
2026-02-26 09:16:35 +08:00

2167 lines
50 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="container fullscreen">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content" :style="{ height: navBarHeight + 'px' }">
<text class="navbar-title">{{ $t('home.title') }}</text>
</view>
</view>
<!-- 顶部信息区域通知招商等 -->
<view class="top-info-section" :style="{ top: (statusBarHeight + navBarHeight) + 'px' }">
<!-- 通知栏 -->
<view class="notice-wrapper" v-if="noticeText" @click="openNoticePopup">
<uv-notice-bar :text="noticeText" :speed="50" :show-icon="true" color="#07c160" bg-color="#E8F8EF"
icon="volume"></uv-notice-bar>
</view>
</view>
<!-- 内容区域 -->
<view class="main-content" :style="{ paddingTop: (statusBarHeight + navBarHeight + noticeHeight) + 'px' }">
<!-- 全屏地图组件 -->
<MapComponent v-if="!isLoading && userLocation" ref="mapRef" :userLocation="userLocation"
:positionList="positionList" :filteredPositions="filteredPositions" :searchKeyword="searchKeyword"
:enableMarkers="true" :bannerImages="bannerImages"
:hideMapOverlays="showGuidePopup || showNoticePopup || showActivityPopup" @relocate="handleRelocate"
@scan="handleScan" @showList="showLocationList" @markerTap="selectPosition"
@mapCenterChange="onMapCenterChange" @bannerClick="handleBannerClick" @guide="openGuidePopup" />
<!-- 地图加载状态 -->
<view v-if="isLoading || !userLocation" class="map-loading-placeholder">
<view class="loading-content">
<view class="loading-spinner"></view>
<text>{{ $t('common.loadingLocation') }}</text>
</view>
</view>
</view>
<!-- 底部操作栏附近设备 / 扫码使用 / 我的 -->
<view class="bottom-actions">
<!-- <view class="action-btn secondary small btn-nearby" @click="showLocationList">
<view class="icon-wrap">
<image class="action-icon" src="/static/map.png" mode="aspectFit" />
</view>
<text class="action-label">{{ $t('home.nearbyDevices') }}</text>
</view> -->
<view class="action-btn secondary small btn-nearby" @click="goToBuy">
<view class="icon-wrap">
<image src="/static/shop_icon.png" class="action-icon" mode="scaleToFill" lazy-load="true"></image>
</view>
<text class="action-label">{{ $t('home.buyDevice') }}</text>
</view>
<view class="action-btn primary btn-scan" @click="handleScan">
<view class="icon-wrap">
<image class="action-icon" src="/static/scan-icon.png" mode="aspectFill" lazy-load="true" />
</view>
<text class="primary-label">{{ $t('home.scanToUse') }}</text>
</view>
<view class="action-btn secondary small btn-my" @click="goMy">
<view class="icon-wrap">
<image class="action-icon" src="/static/user.png" mode="aspectFit" lazy-load="true" />
</view>
<text class="action-label">{{ $t('home.personalCenter') }}</text>
</view>
</view>
<!-- 场地列表弹窗组件 -->
<LocationListSheet :show="showLocationPopup" :expanded="isExpanded" :positions="filteredPositions"
:isLoading="isLoading" :title="$t('home.nearbyDeviceLocation')" @close="hideLocationList"
@select="selectPositionFromPopup" @navigate="navigateToPosition" />
<!-- 加载状态 -->
<!-- <view class="loading-overlay" v-if="isLoading">
<view class="loading-content">
<view class="loading-spinner"></view>
<text>{{ $t('common.loadingPosition') }}</text>
</view>
</view> -->
<!-- 手机号授权弹窗 -->
<view class="phone-auth-popup" v-if="showPhoneAuthPopup">
<view class="popup-mask" @click.stop="showPhoneAuthPopup = false"></view>
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">{{ $t('auth.authTitle') }}</text>
</view>
<view class="popup-body">
<view class="auth-desc">
<text>{{ $t('auth.authDesc') }}</text>
</view>
<button class="auth-btn" open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber">
<text>{{ $t('auth.getPhoneNumber') }}</text>
</button>
<view class="auth-cancel" @click="showPhoneAuthPopup = false">
<text>{{ $t('auth.notNow') }}</text>
</view>
</view>
</view>
</view>
<!-- 使用指南居中弹出ref 控制 open/close -->
<uv-popup ref="guidePopup" mode="center" :overlay="true" :closeOnClickOverlay="false"
:safeAreaInsetBottom="false">
<view class="guide-popup">
<view class="guide-header">
<text class="guide-title">{{ $t('guide.title') }}</text>
<!-- <view class="guide-close" @click="closeGuidePopup">
<uv-icon name="close" size="20"></uv-icon>
</view> -->
</view>
<view class="guide-content">
<view class="guide-step" v-for="(step, idx) in guideSteps" :key="idx">
<view class="step-index">{{ idx + 1 }}</view>
<view class="step-info">
<view class="step-title">{{ $t('guide.step' + (idx + 1) + 'Title') }}</view>
<view class="step-desc">{{ $t('guide.step' + (idx + 1) + 'Desc') }}</view>
</view>
</view>
</view>
</view>
<view class="guide-actions">
<!-- <view class="primary-btn" @click="closeGuidePopup"></view> -->
<view class="primary-btn" @click="closeGuidePopup">
<uv-icon name="close" size="20"></uv-icon>
</view>
</view>
</uv-popup>
<!-- 通知详情弹窗居中弹出 -->
<uv-popup ref="noticePopup" mode="center" :overlay="true" :closeOnClickOverlay="true"
:safeAreaInsetBottom="false">
<view class="notice-popup">
<view class="notice-header">
<text class="notice-title">{{ $t('home.noticeTitle') }}</text>
</view>
<view class="notice-content">
<text class="notice-text">{{ noticeText }}</text>
</view>
</view>
<view class="notice-actions">
<view class="primary-btn" @click="closeNoticePopup">
<uv-icon name="close" size="20"></uv-icon>
</view>
</view>
</uv-popup>
<!-- 活动弹窗居中弹出支持多个活动轮播 -->
<uv-popup ref="activityPopup" mode="center" round="16" :overlay="true" :closeOnClickOverlay="false"
:safeAreaInsetBottom="false" :customStyle="{ background: 'transparent' }">
<view class="activity-popup" v-if="activityList && activityList.length > 0">
<!-- 轮播图 -->
<swiper class="activity-swiper" :indicator-dots="activityList.length > 1" :autoplay="false"
:circular="false" @change="onActivitySwiperChange" :current="currentActivityIndex">
<swiper-item v-for="(activity, index) in activityList" :key="index">
<view class="activity-poster-wrapper">
<image :src="activity.coverImageUrl || activity.imageUrl || activity.posterUrl"
mode="aspectFit" class="activity-poster"></image>
</view>
</swiper-item>
</swiper>
<!-- 自定义指示器 -->
<view class="activity-indicators" v-if="activityList.length > 1">
<view v-for="(item, index) in activityList" :key="index" class="indicator-dot"
:class="{ active: index === currentActivityIndex }"></view>
</view>
<!-- 关闭按钮 -->
<view class="activity-close-btn" @click="closeActivityPopup">
<text class="close-text">{{ $t('common.close') }}</text>
</view>
</view>
</uv-popup>
</view>
</template>
<script setup>
import {
ref,
computed,
onMounted,
onUnmounted
} from 'vue'
import {
getQueryString,
wxLogin
} from '../../util/index.js'
import {
URL
} from "../../config/url.js"
import {
getDeviceInfo,
getNearbyDevices,
transformDeviceData
} from '../../config/api/device.js'
import {
getInUseOrder,
getUnpaidOrder
} from '../../config/api/order.js'
import {
getActiveActivity,
getCurrentAnnouncement,
getCurrentAdvertisement
} from '../../config/api/system.js'
import {
getProductList
} from '../../config/api/product.js'
// 导入地图工具函数
import {
getUserLocation,
getRegeo,
calculateDistanceSync,
testDistanceCalculation
} from '../../utils/mapUtils.js'
// 同样需要使用相对路径引入组件
// 注意:从 pages/index/ 目录访问 components/ 需要使用 ../../components/ 路径
import MapComponent from '../../components/MapComponent.vue'
import LocationListSheet from '../../components/LocationListSheet.vue'
import {
useI18n
} from '../../utils/i18n.js'
// 开启右上角分享菜单(仅 mp-weixin 有效)
// #ifdef MP-WEIXIN
wx.showShareMenu({
withShareTicket: true,
menus: ['shareAppMessage', 'shareTimeline']
})
// #endif
const {
t
} = useI18n()
// 响应式数据
const searchKeyword = ref('')
const userLocation = ref(null)
const positionList = ref([])
const filteredPositions = ref([])
const isExpanded = ref(false)
const isLoading = ref(false)
const showPhoneAuthPopup = ref(false)
const isLocationInitialized = ref(false)
const showLocationPopup = ref(false)
const isRelocating = ref(false) // 防抖标志:是否正在重新定位
const showGuidePopup = ref(false) // 使用指南弹窗显示状态
const showNoticePopup = ref(false) // 通知详情弹窗显示状态
const showActivityPopup = ref(false) // 活动弹窗显示状态
const activityList = ref([]) // 活动列表
const currentActivityIndex = ref(0) // 当前活动索引
const hasShownActivityThisSession = ref(false) // 本次会话是否已显示过活动(内存变量)
// 导航栏高度相关
const statusBarHeight = ref(0)
const navBarHeight = ref(44) // 默认导航栏内容高度
const noticeHeight = ref(0) // 通知栏高度
// 使用指南步骤
const guideSteps = ref([{
title: '扫码使用',
desc: '找到附近设备,扫描设备上的二维码'
},
{
title: '免押金支付',
desc: '无需支付押金,使用支付分免押即可完成租借'
},
{
title: '开始使用',
desc: '设备自动解锁,风扇弹出后取出即可开始使用'
},
{
title: '归还设备',
desc: '使用完毕后,按照设备规格要求将风扇还入即可结束订单'
}
])
const redirectToLogin = () => {
try {
const pages = getCurrentPages()
const current = pages && pages.length ? pages[pages.length - 1] : null
const route = current && current.route ? ('/' + current.route) : '/pages/index/index'
const query = current && current.options ? Object.keys(current.options).map(k =>
`${k}=${encodeURIComponent(current.options[k])}`).join('&') : ''
const redirect = encodeURIComponent(query ? `${route}?${query}` : route)
uni.reLaunch({
url: `/subPackages/user/login/index?redirect=${redirect}`
})
} catch (e) {
uni.reLaunch({
url: '/subPackages/user/login/index'
})
}
}
const noticeText = ref('')
const bannerImages = ref([]) // 首页广告图片列表
const bannerImageList = ref([]) // 完整的广告配置列表(包含链接信息)
// 获取公告内容(支持多语言)
const getNoticeText = async () => {
try {
console.log('加载公告')
// 调用接口获取公告内容
const res = await getCurrentAnnouncement({
type: 'wx_user_type' // 微信小程序用户端
})
console.log('公告响应:', res)
if (res && res.code === 200 && res.data) {
// 使用后端自动解析的 announcement 字段
const announcement = res.data.announcement || ''
noticeText.value = announcement
// 设置通知栏高度
if (announcement) {
noticeHeight.value = 50 // 通知栏高度约50px
}
// 将通知内容存储到本地缓存
try {
uni.setStorageSync('noticeContent', announcement)
console.log('通知内容已缓存:', announcement)
} catch (e) {
console.warn('缓存通知内容失败:', e)
}
} else {
console.warn('获取公告失败:', res?.msg || '未知错误')
}
} catch (error) {
console.error('获取公告失败:', error)
}
}
// 获取首页广告图片(支持多语言)
const getBannerImages = async () => {
try {
console.log('加载首页广告')
// 调用接口获取广告内容
const res = await getCurrentAdvertisement({
appPlatform: 'wechat', // 微信平台
appType: 'user' ,// 用户端
pictureLocation:'home_banner'
})
console.log('首页广告响应:', res)
if (res && res.code === 200 && res.data) {
// 使用 imageList 字段(包含图片和链接信息)
const imageList = res.data.imageList || []
if (imageList.length > 0) {
bannerImageList.value = imageList
// 提取图片URL用于展示
bannerImages.value = imageList.map(item => item.imageUrl)
} else {
console.warn('未获取到广告图片')
}
} else {
console.warn('获取首页广告失败:', res?.msg || '未知错误')
}
} catch (error) {
console.error('获取首页广告失败:', error)
}
}
// 处理首页广告点击
const handleBannerClick = (index) => {
if (!bannerImageList.value || !bannerImageList.value[index]) {
console.warn('未找到对应的广告配置')
return
}
const config = bannerImageList.value[index]
console.log('点击首页广告:', index, config)
// 根据链接类型进行跳转
if (config.linkType === 'miniapp' && config.appId) {
// 跳转到外部小程序
// #ifdef MP-WEIXIN
uni.navigateToMiniProgram({
appId: config.appId,
path: config.linkUrl || '',
success: () => {
console.log('跳转小程序成功')
},
fail: (err) => {
console.error('跳转小程序失败:', err)
uni.showToast({
title: t('common.loadFailed'),
icon: 'none'
})
}
})
// #endif
// #ifndef MP-WEIXIN
uni.showToast({
title: t('auth.pleaseUseInWechat'),
icon: 'none'
})
// #endif
} else if (config.linkType === 'external' && config.linkUrl) {
// 跳转到外部链接(H5页面)
uni.navigateTo({
url: `/subPackages/other/webview/index?url=${encodeURIComponent(config.linkUrl)}`
})
} else if (config.linkType === 'internal' && config.linkUrl) {
// 跳转到内部页面
uni.navigateTo({
url: config.linkUrl
})
} else {
console.log('无有效的跳转配置')
}
}
// 查询最近的活动
const checkActiveActivity = async () => {
try {
// 检查本次会话是否已经显示过活动弹窗(使用内存变量)
if (hasShownActivityThisSession.value) {
console.log('本次会话已显示过活动弹窗,跳过');
return;
}
const res = await getActiveActivity();
console.log('活动接口返回:', res);
if (res.code === 200) {
let activities = [];
// 处理活动数据:兼容多种返回格式
if (res.rows && Array.isArray(res.rows)) {
// 格式1: { code: 200, rows: [...] }
activities = res.rows;
} else if (res.data && res.data.rows && Array.isArray(res.data.rows)) {
// 格式2: { code: 200, data: { rows: [...] } }
activities = res.data.rows;
} else if (Array.isArray(res.data)) {
// 格式3: { code: 200, data: [...] }
activities = res.data;
} else if (res.data && typeof res.data === 'object') {
// 格式4: { code: 200, data: {...} } 单个对象
activities = [res.data];
}
// 过滤有效的活动(必须有封面图)
activityList.value = activities.filter(item => {
return item && (item.coverImageUrl || item.imageUrl || item.posterUrl);
});
console.log('过滤后的活动列表:', activityList.value);
// 如果有活动数据,则显示弹窗
if (activityList.value.length > 0) {
currentActivityIndex.value = 0; // 重置索引
// 延迟一下显示,避免与其他弹窗冲突
setTimeout(() => {
openActivityPopup();
// 标记本次会话已显示过活动弹窗(内存变量,小程序重启后自动重置)
hasShownActivityThisSession.value = true;
}, 500);
}
}
} catch (e) {
console.warn('查询活动失败:', e);
}
}
// 距离格式化函数
const formatDistance = (distanceInMeters) => {
if (distanceInMeters < 1000) {
return `${Math.round(distanceInMeters)}m`
} else {
return `${(distanceInMeters / 1000).toFixed(1)}km`
}
}
// 组件引用
const mapRef = ref(null)
const guidePopup = ref(null)
const noticePopup = ref(null)
const activityPopup = ref(null)
// 计算属性
const searchPlaceholder = computed(() => {
if (userLocation.value && userLocation.value.address) {
return `${userLocation.value.district || '当前位置'} - 搜索附近场地`
}
return '搜索附近场地'
})
// 计算导航栏高度
const initNavBarHeight = () => {
try {
const systemInfo = uni.getSystemInfoSync()
statusBarHeight.value = systemInfo.statusBarHeight || 0
// #ifdef MP-WEIXIN
// 获取胶囊按钮位置信息
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
// 计算导航栏内容高度:(胶囊底部坐标 - 状态栏高度) 确保与胶囊对齐
navBarHeight.value = (menuButtonInfo.top - systemInfo.statusBarHeight) * 2 + menuButtonInfo.height
// #endif
// #ifndef MP-WEIXIN
// 非微信小程序使用默认高度
navBarHeight.value = 44
// #endif
console.log('状态栏高度:', statusBarHeight.value)
console.log('导航栏内容高度:', navBarHeight.value)
} catch (error) {
console.error('获取导航栏高度失败:', error)
statusBarHeight.value = 20
navBarHeight.value = 44
}
}
// 生命周期
onMounted(() => {
initNavBarHeight()
init()
// #ifdef H5
// 监听 H5 扫码结果
uni.$on('h5ScanSuccess', (data) => {
console.log('接收到 H5 扫码结果:', data);
processScanResult(data);
});
// #endif
})
onUnmounted(() => {
// #ifdef H5
uni.$off('h5ScanSuccess');
// #endif
})
// 方法
const init = async () => {
isLoading.value = true
try {
// 清理旧版本的缓存键(兼容性处理)
try {
uni.removeStorageSync('hasShownActivityInSession');
uni.removeStorageSync('activityLastShownDate');
} catch (e) {
console.warn('清理旧缓存失败:', e);
}
// // 开发环境测试距离计算
// if (process.env.NODE_ENV === 'development') {
// testDistanceCalculation()
// }
// 并行加载公告和广告(不依赖定位)
await Promise.all([
getNoticeText(),
getBannerImages()
])
// 1. 先获取用户位置
await getUserLocationAndAddress()
// 2. 加载场地列表(依赖定位)
await loadPositions()
// 3. 查询活动并显示弹窗
// await checkActiveActivity()
} catch (error) {
console.error('初始化失败:', error)
// uni.showToast({
// title: t('home.getLocationFailed'),
// icon: 'none'
// })
} finally {
isLoading.value = false
}
}
const getUserLocationAndAddress = async () => {
// 使用腾讯地图SDK获取位置(若失败抛出,由调用方统一处理)
const location = await getUserLocation()
if (!location?.longitude || !location?.latitude) {
throw new Error('invalid location result')
}
// 保存用户位置
userLocation.value = {
longitude: location.longitude,
latitude: location.latitude
}
console.log(userLocation.value);
// 将经纬度写入本地缓存(基础信息)
try {
uni.setStorageSync('userLocation', {
longitude: location.longitude,
latitude: location.latitude
})
} catch (e) {
console.warn('缓存基础定位信息失败:', e)
}
// 只在首次初始化时设置标记
if (!isLocationInitialized.value) {
isLocationInitialized.value = true
}
// 获取详细地址信息
try {
const addressResult = await getRegeo(location.longitude, location.latitude)
if (addressResult.success) {
const addressInfo = addressResult.data
userLocation.value.address = addressInfo.formatted_address
userLocation.value.city = addressInfo.addressComponent.city
userLocation.value.district = addressInfo.addressComponent.district
// 更新本地缓存,包含地址信息
try {
uni.setStorageSync('userLocation', {
longitude: userLocation.value.longitude,
latitude: userLocation.value.latitude,
address: userLocation.value.address,
city: userLocation.value.city,
district: userLocation.value.district
})
} catch (e) {
console.warn('缓存带地址的定位信息失败:', e)
}
}
} catch (error) {
// 忽略地址信息错误,使用基础定位信息
}
return userLocation.value
}
const loadPositions = async () => {
try {
if (!userLocation.value || !userLocation.value.latitude || !userLocation.value.longitude) {
console.warn('用户位置信息不完整,无法查询附近设备')
return
}
const res = await getNearbyDevices({
userLatitude: userLocation.value.latitude,
userLongitude: userLocation.value.longitude,
queryType: 'rent', // 默认查询可租借设备
radiusKm: 5,
pageNum: 1,
pageSize: 100
})
console.log('查询附近设备结果:', res)
if (res.code === 200) {
// 新接口返回的是 data.records,需要适配字段名
const devices = res.data?.records || []
// 将设备数据转换为统一格式
positionList.value = devices.map(transformDeviceData)
calculateDistances()
filteredPositions.value = [...positionList.value]
} else {
console.error('获取设备列表失败:', res.msg)
}
} catch (error) {
console.error('获取设备列表异常:', error)
}
}
const calculateDistances = async (centerPoint = null) => {
// 如果没有指定中心点,优先使用用户位置,其次使用地图中心
const center = centerPoint || userLocation.value || (mapRef.value?.mapCenter)
// 严格检查center对象的有效性
if (!center || typeof center.longitude === 'undefined' || typeof center.latitude === 'undefined') {
return
}
positionList.value.forEach(item => {
if (item.longitude && item.latitude) {
try {
// 使用同步方法计算距离,避免await调用需要改动大量代码
const distanceInMeters = calculateDistanceSync(
center.latitude,
center.longitude,
parseFloat(item.latitude),
parseFloat(item.longitude)
)
// 使用智能距离格式化
item.distance = formatDistance(distanceInMeters)
// 同时保存原始米数用于排序和过滤
item.distanceInMeters = distanceInMeters
} catch (error) {
console.error('计算距离异常:', error, item)
item.distance = '999.0km' // 设置一个默认距离
item.distanceInMeters = 999000
}
}
})
// 按距离排序(使用米数进行排序)
positionList.value.sort((a, b) => {
return (a.distanceInMeters || 999000) - (b.distanceInMeters || 999000)
})
}
const loadPositionsByCenter = async (center) => {
if (!center || !center.longitude || !center.latitude) {
console.warn('loadPositionsByCenter: 无效的中心点', center)
return
}
console.log('根据地图中心加载设备:', center)
try {
// 使用新的附近设备查询接口
const res = await getNearbyDevices({
userLatitude: center.latitude,
userLongitude: center.longitude,
queryType: 'rent', // 默认查询可租借设备
radiusKm: 5,
pageNum: 1,
pageSize: 100
})
if (res.code === 200) {
const devices = res.data?.records || []
console.log('加载到设备数量:', devices.length)
// 将设备数据转换为统一格式
positionList.value = devices.map(transformDeviceData)
// 基于地图中心计算距离
calculateDistances(center)
// 可以选择性过滤距离过远的设备(比如超过10km的)
const maxDistanceInMeters = 10000 // 最大显示距离,单位米(10km)
filteredPositions.value = positionList.value.filter(item => {
return !item.distanceInMeters || item.distanceInMeters <= maxDistanceInMeters
})
console.log('过滤后设备数量:', filteredPositions.value.length)
} else {
console.error('根据地图中心加载设备失败:', res.msg)
positionList.value = []
filteredPositions.value = []
}
} catch (error) {
console.error('根据地图中心加载设备异常:', error)
// 如果请求失败,保持当前的设备列表不变
}
}
const handleRelocate = async () => {
// 防抖:如果正在定位中,直接返回
if (isRelocating.value) {
console.log('正在定位中,请勿重复点击')
return
}
try {
isRelocating.value = true
// uni.showLoading({
// title: t('home.relocating'),
// mask: true
// })
// 重新获取用户真实位置
const loc = await getUserLocation()
if (!loc || !loc.longitude || !loc.latitude) {
throw new Error('location failed')
}
const newLocation = {
longitude: Number(loc.longitude),
latitude: Number(loc.latitude)
}
console.log('重新定位成功,新位置:', newLocation)
console.log('当前位置:', userLocation.value)
// 更新用户位置(触发地图组件的watch,自动移动地图)
userLocation.value = {
...newLocation
}
// 保存到本地缓存
try {
uni.setStorageSync('userLocation', newLocation)
} catch (e) {
console.warn('缓存位置失败:', e)
}
// 确保地图移动到新位置
if (mapRef.value && typeof mapRef.value.moveToLocation === 'function') {
mapRef.value.moveToLocation(newLocation)
}
// 延迟一下,等待地图移动完成后再查询场地
await new Promise(resolve => setTimeout(resolve, 300))
// 加载新位置的场地
await loadPositionsByCenter(newLocation)
uni.hideLoading()
// uni.showToast({
// title: t('home.locateSuccess'),
// icon: 'success',
// duration: 1500
// })
} catch (e) {
console.error('定位失败:', e)
uni.hideLoading()
// uni.showToast({
// title: e.errMsg || t('home.locateFailed'),
// icon: 'none',
// duration: 2000
// })
} finally {
// 1秒后解除防抖锁定
setTimeout(() => {
isRelocating.value = false
}, 1000)
}
}
const onMapCenterChange = (center) => {
if (center && typeof center.longitude !== 'undefined' && typeof center.latitude !== 'undefined') {
userLocation.value = {
longitude: Number(center.longitude),
latitude: Number(center.latitude)
}
try {
uni.setStorageSync('userLocation', userLocation.value)
} catch (_) {}
// 调用加载场地方法
loadPositionsByCenter(center)
} else {
console.warn('onMapCenterChange: 无效的中心点', center)
}
}
const selectPosition = (position) => {
uni.showActionSheet({
itemList: [t('home.scanToUse'), t('home.navigate')],
success: (res) => {
switch (res.tapIndex) {
case 0:
handleScan()
break
case 1:
navigateToPosition(position)
break
}
}
})
}
const goMy = () => {
uni.navigateTo({
url: '/subPackages/user/my/index'
})
}
const goToBuy = async () => {
try {
uni.showLoading({
title: t('common.loading'),
mask: true
})
// 查询商品列表
const res = await getProductList({
pageNum: 1,
pageSize: 10
})
uni.hideLoading()
console.log('商品列表查询结果:', res)
// 根据实际返回格式:data.rows 是商品列表数组
if (res && res.code === 200 && res.data && res.data.rows && res.data.rows.length > 0) {
// 获取第一个商品的ID
const firstProduct = res.data.rows[0]
const productId = firstProduct.id
console.log('获取到商品ID:', productId)
// 跳转到商品详情页面,传递商品ID
uni.navigateTo({
url: `/subPackages/business/device-goods?productId=${productId}`
})
} else {
console.warn('没有查询到商品数据')
// 如果没有商品数据,仍然跳转到商品页面(显示空状态)
uni.navigateTo({
url: '/subPackages/business/device-goods'
})
}
} catch (error) {
console.error('查询商品列表失败:', error)
uni.hideLoading()
// 即使查询失败,也跳转到商品页面
uni.navigateTo({
url: '/subPackages/business/device-goods'
})
}
}
const selectPositionFromPopup = (position) => {
// 先关闭弹窗
hideLocationList()
// 延迟一点时间再显示选择菜单,让弹窗关闭动画完成
setTimeout(() => {
selectPosition(position)
}, 200)
}
const navigateToPosition = (position) => {
const latitude = parseFloat(position.latitude)
const longitude = parseFloat(position.longitude)
uni.openLocation({
latitude,
longitude,
name: position.name,
address: position.location
})
}
const toggleSheet = () => {
isExpanded.value = !isExpanded.value
}
const onSearchInput = () => {
if (!searchKeyword.value.trim()) {
filteredPositions.value = [...positionList.value]
} else {
filteredPositions.value = positionList.value.filter(item => {
return item.name.includes(searchKeyword.value) ||
item.describe.includes(searchKeyword.value) ||
item.location.includes(searchKeyword.value)
})
}
}
const handleScan = async () => {
// #ifdef H5
uni.navigateTo({
url: '/pages/scan/index'
});
return;
// #endif
try {
const scanResult = await new Promise((resolve, reject) => {
uni.scanCode({
success: resolve,
fail: reject
})
})
console.log(scanResult);
processScanResult(scanResult);
} catch (error) {
console.error('扫码处理失败:', error)
}
}
const processScanResult = async (scanResult) => {
try {
console.log('===== 处理扫码结果 =====');
console.log('扫码结果对象:', scanResult);
console.log('scanType:', scanResult.scanType);
console.log('result:', scanResult.result);
console.log('path:', scanResult.path);
let deviceNo;
if (scanResult.scanType == 'MANUAL') {
deviceNo = scanResult.result;
console.log('手动输入模式,设备号:', deviceNo);
} else if (scanResult.scanType == 'QR_CODE') {
// 修复:移除多余的引号
deviceNo = getQueryString(scanResult.result, 'deviceNo');
console.log('二维码扫描模式,提取设备号:', deviceNo);
} else {
deviceNo = getQueryString(scanResult.path || scanResult.result, 'deviceNo');
console.log('其他模式,提取设备号:', deviceNo);
}
console.log('最终设备号:', deviceNo);
if (!deviceNo) {
console.warn('未能提取到设备号');
uni.showToast({
title: t('home.invalidQRCode'),
icon: 'none'
});
// 关闭扫码页面
const pages = getCurrentPages();
if (pages.length > 1) {
uni.navigateBack();
}
return;
}
// 检查是否有使用中的订单
const inUseRes = await getInUseOrder()
if (inUseRes && inUseRes.code === 200 && inUseRes.data) {
const inUseOrder = inUseRes.data
// 先关闭扫码页,再跳转
const pages = getCurrentPages();
if (pages.length > 1 && pages[pages.length - 1].route.includes('scan')) {
uni.navigateBack({
success: () => {
setTimeout(() => {
uni.reLaunch({
url: `/pages/order/detail?orderId=${inUseOrder.orderId}&deviceId=${deviceNo || inUseOrder.deviceNo}`
});
}, 100);
}
});
} else {
uni.reLaunch({
url: `/pages/order/detail?orderId=${inUseOrder.orderId}&deviceId=${deviceNo || inUseOrder.deviceNo}`
});
}
return;
}
// 检查是否有待支付订单
const orderRes = await getUnpaidOrder()
if (orderRes && orderRes.code === 200 && orderRes.data) {
const unpaidOrder = orderRes.data
// 先关闭扫码页,再跳转
const pages = getCurrentPages();
if (pages.length > 1 && pages[pages.length - 1].route.includes('scan')) {
uni.navigateBack({
success: () => {
setTimeout(() => {
uni.navigateTo({
url: `/subPackages/order/payment?orderId=${unpaidOrder.orderId}`
});
}, 100);
}
});
} else {
uni.navigateTo({
url: `/subPackages/order/payment?orderId=${unpaidOrder.orderId}`
});
}
} else {
try {
const deviceInfoRes = await getDeviceInfo(deviceNo)
if (deviceInfoRes.code == 200 && deviceInfoRes.data && deviceInfoRes.data.device) {
const deviceInfo = deviceInfoRes.data.device
// 先关闭扫码页,再跳转
const pages = getCurrentPages();
const closeScanPageAndNavigate = (url) => {
if (pages.length > 1 && pages[pages.length - 1].route.includes('scan')) {
uni.navigateBack({
success: () => {
setTimeout(() => {
uni.navigateTo({ url });
}, 100);
}
});
} else {
uni.navigateTo({ url });
}
};
if (deviceInfo.feeConfig) {
try {
const feeConfig = JSON.parse(deviceInfo.feeConfig)
closeScanPageAndNavigate(`/pages/device/detail?deviceNo=${deviceNo}&feeConfig=${encodeURIComponent(deviceInfo.feeConfig)}`);
} catch (e) {
closeScanPageAndNavigate(`/pages/device/detail?deviceNo=${deviceNo}`);
}
} else {
closeScanPageAndNavigate(`/pages/device/detail?deviceNo=${deviceNo}`);
}
} else {
// 先关闭扫码页,再跳转
const pages = getCurrentPages();
if (pages.length > 1 && pages[pages.length - 1].route.includes('scan')) {
uni.navigateBack({
success: () => {
setTimeout(() => {
uni.navigateTo({
url: `/pages/device/detail?deviceNo=${deviceNo}`
});
}, 100);
}
});
} else {
uni.navigateTo({
url: `/pages/device/detail?deviceNo=${deviceNo}`
});
}
}
} catch (error) {
console.error('获取设备信息异常:', error)
// 先关闭扫码页,再跳转
const pages = getCurrentPages();
if (pages.length > 1 && pages[pages.length - 1].route.includes('scan')) {
uni.navigateBack({
success: () => {
setTimeout(() => {
uni.navigateTo({
url: `/pages/device/detail?deviceNo=${deviceNo}`
});
}, 100);
}
});
} else {
uni.navigateTo({
url: `/pages/device/detail?deviceNo=${deviceNo}`
});
}
}
}
} catch (error) {
console.error('处理扫码结果失败:', error)
// 关闭扫码页面
const pages = getCurrentPages();
if (pages.length > 1 && pages[pages.length - 1].route.includes('scan')) {
uni.navigateBack();
}
}
}
const showLocationList = () => {
showLocationPopup.value = true
}
const hideLocationList = () => {
showLocationPopup.value = false
}
const onGetPhoneNumber = (e) => {
if (e.detail.errMsg === 'getPhoneNumber:ok') {
showPhoneAuthPopup.value = false
}
}
// 使用指南弹窗控制
const openPopup = () => {
uni.navigateTo({
url:'/subPackages/business/device-goods'
})
// try {
// showGuidePopup.value = true
// guidePopup.value && typeof guidePopup.value.open === 'function' && guidePopup.value.open()
// } catch (e) {}
}
const openGuidePopup = () => {
try {
showGuidePopup.value = true
guidePopup.value && typeof guidePopup.value.open === 'function' && guidePopup.value.open()
} catch (e) {}
}
const closeGuidePopup = () => {
try {
showGuidePopup.value = false
guidePopup.value && typeof guidePopup.value.close === 'function' && guidePopup.value.close()
} catch (e) {}
}
// 通知弹窗控制
const openNoticePopup = () => {
try {
showNoticePopup.value = true
noticePopup.value && typeof noticePopup.value.open === 'function' && noticePopup.value.open()
} catch (e) {}
}
const closeNoticePopup = () => {
try {
showNoticePopup.value = false
noticePopup.value && typeof noticePopup.value.close === 'function' && noticePopup.value.close()
} catch (e) {}
}
// 活动弹窗控制
const openActivityPopup = () => {
try {
showActivityPopup.value = true
activityPopup.value && typeof activityPopup.value.open === 'function' && activityPopup.value.open()
} catch (e) {}
}
const closeActivityPopup = () => {
try {
showActivityPopup.value = false
activityPopup.value && typeof activityPopup.value.close === 'function' && activityPopup.value.close()
// 重置索引
currentActivityIndex.value = 0
} catch (e) {}
}
// 活动轮播图切换
const onActivitySwiperChange = (e) => {
currentActivityIndex.value = e.detail.current
}
</script>
<script>
// 选用 Page 级分享钩子(uni-app 需普通 script 导出)
export default {
// 分享给朋友
onShareAppMessage() {
const $t = this.$t || ((key) => key)
return {
title: $t('share.title'),
path: '/pages/index/index',
// imageUrl: '/static/logo.png'
}
},
// 朋友圈
onShareTimeline() {
const $t = this.$t || ((key) => key)
return {
title: $t('share.title'),
query: '',
// imageUrl: '/static/logo.png'
}
}
}
</script>
<style lang="scss" scoped>
.container {
height: 100vh;
width: 100vw;
background-color: #fff;
display: flex;
flex-direction: column;
}
.fullscreen {
padding: 0;
}
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
background-color: #ffffff;
z-index: 1000;
}
.navbar-content {
display: flex;
align-items: center;
justify-content: flex-start;
padding: 0 20rpx;
}
.navbar-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
/* 主内容区域 */
.main-content {
flex: 1;
position: relative;
width: 100%;
height: 100%;
padding-bottom: 180rpx;
/* 为底部按钮留出空间 */
box-sizing: border-box;
}
/* 顶部Logo和通知栏 */
.header-section {
width: 92%;
margin: 0 auto 20rpx;
}
.logo-container {
display: flex;
align-items: center;
// margin-bottom: 16rpx;
.logo-image {
width: 80rpx;
height: 80rpx;
margin-right: 8rpx;
}
.app-name {
font-size: 36rpx;
font-weight: 600;
color: #333;
}
}
/* 地图标题 */
.map-title {
width: 92%;
margin: 0 auto 10rpx;
padding: 10rpx 0;
text {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
}
/* 顶部搜索栏 */
.header-search {
padding: 20rpx 30rpx;
background: #ffffff;
border-bottom: 1px solid #f0f0f0;
z-index: 10;
.search-box {
display: flex;
align-items: center;
background: #f8f9fa;
border-radius: 50rpx;
padding: 0 20rpx;
height: 80rpx;
.search-icon {
width: 32rpx;
height: 32rpx;
margin-right: 16rpx;
}
.search-input {
flex: 1;
font-size: 28rpx;
color: #333;
}
.location-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
background: #2196F3;
border-radius: 50%;
margin-left: 16rpx;
.location-icon {
width: 24rpx;
height: 24rpx;
}
}
}
}
/* 场地列表弹窗 */
.location-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 2000;
display: flex;
align-items: flex-end;
justify-content: center;
.popup-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
}
.location-sheet {
background: #ffffff;
border-radius: 32rpx 32rpx 0 0;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.1);
max-height: 70vh;
transition: all 0.3s ease;
z-index: 1;
position: relative;
width: 100%;
display: flex;
flex-direction: column;
animation: slideUp 0.3s ease-out;
&.expanded {
max-height: 85vh;
}
.sheet-handle {
display: flex;
justify-content: center;
padding: 20rpx 0;
cursor: pointer;
.handle-bar {
width: 80rpx;
height: 8rpx;
background: #e0e0e0;
border-radius: 4rpx;
}
}
.sheet-header {
padding: 20rpx 30rpx;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
.sheet-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.close-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
background: #f0f0f0;
border-radius: 50%;
transition: all 0.2s ease;
&:active {
background: #e0e0e0;
transform: scale(0.95);
}
.close-icon {
width: 24rpx;
height: 24rpx;
}
}
}
.sheet-content {
padding: 20rpx 0;
height: 60vh;
/* 固定高度以保证小程序端 scroll-view 正常滚动 */
}
}
}
@keyframes slideUp {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
/* 场地列表项 */
.position-item {
display: flex;
align-items: center;
padding: 24rpx 30rpx;
border-bottom: 1px solid #f8f9fa;
.position-info {
flex: 1;
.position-name {
font-size: 32rpx;
font-weight: 500;
color: #333;
margin-bottom: 8rpx;
}
.position-desc {
font-size: 26rpx;
color: #666;
margin-bottom: 8rpx;
}
.position-location {
display: flex;
align-items: center;
margin-bottom: 8rpx;
.location-icon-small {
width: 24rpx;
height: 24rpx;
margin-right: 8rpx;
}
text {
font-size: 24rpx;
color: #999;
}
}
.position-time {
font-size: 24rpx;
color: #999;
}
}
.position-actions {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 8rpx;
.distance-info {
font-size: 24rpx;
color: #2196F3;
font-weight: 500;
}
.status-tag {
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 22rpx;
&.online {
background: #e8f5e8;
color: #4caf50;
}
&.offline {
background: #ffeaea;
color: #f44336;
}
}
.nav-btn {
padding: 12rpx 20rpx;
background: #2196F3;
border-radius: 20rpx;
font-size: 24rpx;
color: #ffffff;
}
}
}
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 0;
.empty-icon {
width: 120rpx;
height: 120rpx;
margin-bottom: 24rpx;
opacity: 0.5;
}
.empty-text {
font-size: 28rpx;
color: #999;
}
}
.status-tag {
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 22rpx;
width: fit-content;
&.online {
background: #e8f5e8;
color: #4caf50;
}
&.offline {
background: #ffeaea;
color: #f44336;
}
&.wait {
background: #ffeaea;
color: #f44336;
}
}
/* 底部操作栏 */
.bottom-actions {
position: fixed;
left: 20rpx;
right: 20rpx;
bottom: 40rpx;
z-index: 1200;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 16rpx;
padding: 0;
.action-btn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 24rpx;
font-weight: 500;
transition: all 0.3s ease;
&:active {
transform: scale(0.95);
opacity: 0.8;
}
&.primary {
background: #3EAB64;
color: #fff;
border-radius: 56rpx;
height: 112rpx;
flex: 1;
max-width: 400rpx;
padding: 0 24rpx;
// box-shadow: 0 8rpx 24rpx rgba(62, 171, 100, 0.3);
.icon-wrap {
width: 52rpx;
height: 52rpx;
display: flex;
align-items: center;
justify-content: center;
}
}
&.secondary {
// background: rgba(255, 255, 255, 0.95);
color: #333;
border-radius: 24rpx;
height: 100rpx;
width: 140rpx;
flex-shrink: 0;
padding: 8rpx 12rpx;
// box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
// border: 1rpx solid #f0f0f0;
.icon-wrap {
width: 36rpx;
height: 36rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 6rpx;
}
}
}
.btn-scan {
/* 中间扫码按钮:横向布局 */
flex-direction: row;
gap: 8rpx;
.icon-wrap {
width: 48rpx;
height: 48rpx;
margin: 0;
}
.action-icon {
width: 32rpx;
height: 32rpx;
filter: brightness(0) invert(1);
}
.primary-label {
font-size: 28rpx;
font-weight: 600;
}
}
}
/* 加载状态 */
.loading-overlay {
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: 1000;
.loading-content {
background: #ffffff;
border-radius: 16rpx;
padding: 40rpx;
display: flex;
flex-direction: column;
align-items: center;
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 4rpx solid #f0f0f0;
border-top: 4rpx solid #2196F3;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 24rpx;
}
text {
font-size: 28rpx;
color: #666;
}
}
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* 地图加载状态 */
.map-loading-placeholder {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 180rpx;
/* 为底部按钮留出空间 */
background: #f6f7fb;
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
.loading-content {
background: #ffffff;
border-radius: 16rpx;
padding: 40rpx;
display: flex;
flex-direction: column;
align-items: center;
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 4rpx solid #f0f0f0;
border-top: 4rpx solid #2196F3;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 24rpx;
}
text {
font-size: 28rpx;
color: #666;
}
}
}
/* 手机号授权弹窗 */
.phone-auth-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 2000;
display: flex;
align-items: center;
justify-content: center;
.popup-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
}
.popup-content {
background: #ffffff;
border-radius: 24rpx;
margin: 0 60rpx;
padding: 40rpx;
position: relative;
z-index: 1;
.popup-header {
text-align: center;
margin-bottom: 30rpx;
.popup-title {
font-size: 36rpx;
font-weight: 600;
color: #333;
}
}
.popup-body {
.auth-desc {
text-align: center;
margin-bottom: 40rpx;
text {
font-size: 28rpx;
color: #666;
line-height: 1.6;
}
}
.auth-btn {
width: 100%;
height: 88rpx;
background: #2196F3;
border-radius: 44rpx;
border: none;
color: #ffffff;
font-size: 32rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20rpx;
}
.auth-cancel {
text-align: center;
padding: 20rpx;
text {
font-size: 28rpx;
color: #999;
}
}
}
}
}
.action-icon {
width: 36rpx;
height: 36rpx;
filter: none;
}
.action-label {
line-height: 1.2;
text-align: center;
}
.primary-label {
color: #ffffff;
line-height: 1;
}
/* 顶部信息区域 */
.top-info-section {
position: fixed;
left: 0;
right: 0;
z-index: 998;
background: transparent;
}
.notice-wrapper {
margin: 0 20rpx;
padding: 10rpx 0;
border-radius: 20rpx;
}
/* 弹窗样式 */
.guide-popup {
width: 640rpx;
max-width: 86vw;
background: #ffffff;
border-radius: 24rpx;
padding: 24rpx 24rpx 16rpx;
box-sizing: border-box;
// padding-bottom:100rpx;
}
.guide-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 8rpx;
}
.guide-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.guide-close {
position: absolute;
right: 0;
top: 0;
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
border-radius: 50%;
}
.guide-content {
max-height: 600rpx;
padding: 8rpx 4rpx 4rpx;
box-sizing: border-box;
}
.guide-step {
display: flex;
align-items: flex-start;
gap: 16rpx;
padding: 16rpx 0;
border-bottom: 1px solid #f5f5f5;
}
.step-index {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background: #07c160;
color: #ffffff;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
}
.step-info {
flex: 1;
}
.step-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 6rpx;
}
.step-desc {
font-size: 24rpx;
color: #666;
line-height: 1.6;
}
.guide-actions {
margin-top: 20rpx;
}
.primary-btn {
// width: 100%;
width: 80rpx;
height: 80rpx;
padding: 20rpx;
border-radius: 40rpx;
background: #DDEFE3;
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
margin: auto;
font-size: 28rpx;
font-weight: 600;
}
.primary-label {
color: #ffffff;
font-size: 32rpx;
font-weight: 600;
}
/* 通知详情弹窗样式 */
.notice-popup {
width: 640rpx;
max-width: 86vw;
background: #ffffff;
border-radius: 24rpx;
padding: 24rpx 24rpx 16rpx;
box-sizing: border-box;
}
.notice-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 16rpx;
}
.notice-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.notice-content {
max-height: 600rpx;
overflow-y: auto;
padding: 16rpx 12rpx;
box-sizing: border-box;
background: #F8F9FA;
border-radius: 12rpx;
}
.notice-text {
font-size: 28rpx;
color: #333;
line-height: 1.8;
word-break: break-all;
white-space: pre-wrap;
}
.notice-actions {
margin-top: 20rpx;
}
/* 活动弹窗样式 - 支持轮播 */
.activity-popup {
position: relative;
width: 600rpx;
max-width: 80vw;
background: transparent;
border-radius: 16rpx;
overflow: visible;
padding-bottom: 120rpx;
}
/* 覆盖 uv-popup 的默认背景色 */
:deep(.uv-popup__content) {
background: transparent !important;
}
.activity-swiper {
width: 100%;
height: 800rpx;
max-height: 70vh;
border-radius: 16rpx;
background: transparent;
:deep(swiper) {
background: transparent;
}
:deep(swiper-item) {
background: transparent;
}
}
.activity-poster-wrapper {
width: 100%;
height: 100%;
border-radius: 16rpx;
overflow: visible;
background: transparent;
display: flex;
align-items: center;
justify-content: center;
}
.activity-poster {
width: 100%;
height: 100%;
display: block;
object-fit: contain;
}
/* 自定义指示器 */
.activity-indicators {
position: absolute;
bottom: 140rpx;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 12rpx;
z-index: 10;
}
.indicator-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.5);
transition: all 0.3s;
}
.indicator-dot.active {
width: 32rpx;
border-radius: 8rpx;
background: #ffffff;
}
.activity-close-btn {
position: absolute;
bottom: 20rpx;
left: 50%;
transform: translateX(-50%);
padding: 16rpx 48rpx;
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
cursor: pointer;
transition: all 0.3s ease;
background: rgba(0, 0, 0, 0.6);
border-radius: 40rpx;
.close-text {
font-size: 28rpx;
color: #ffffff;
font-weight: 500;
}
&:active {
transform: translateX(-50%) scale(0.95);
background: rgba(0, 0, 0, 0.8);
}
}
</style>