Files
one-pipe-card-h5/pages/index/index.vue
sexygoat 6bf56a4b4c first
2026-01-22 17:25:30 +08:00

1102 lines
27 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">
<!-- 用户信息卡片 -->
<view class="card user-info-card interactive" role="banner">
<view class="flex-row-g20">
<view class="user-avatar">
<image
:src="userInfo.avatar || 'https://img1.baidu.com/it/u=2462918877,1866131262&fm=253&fmt=auto&app=138&f=JPEG?w=506&h=500'"
mode="aspectFill" alt="用户头像" />
</view>
<view class="user-details flex-col-g8">
<view class="title">{{userInfo.nickname || "单卡用户"}}</view>
<view class="caption">设备已运行{{formatSecondsToTime(deviceInfo.run_time)}}</view>
</view>
<view class="tag-apple" :class="onlineStatus === '在线' ? 'tag-success' : 'tag-warning'">
{{onlineStatus}}
</view>
</view>
</view>
<!-- 设备信息卡片 -->
<view class="card device-status-card">
<view class="card-header flex-row-sb">
<view class="title">设备信息</view>
<view class="info-values flex-row-g20">
<view class="tag-apple tag-success">{{getOperator(deviceInfo.currentIccid)}}</view>
</view>
</view>
<view class="device-info flex-col-g16">
<view class="info-row flex-row-sb">
<view class="info-label">
<view class="subtitle">当前使用卡{{deviceInfo.currentIccid || "无"}}</view>
</view>
<view class="info-values flex-row-g20">
<view class="tag-apple tag-success">{{isRealName ? "已实名" : "未实名"}}</view>
</view>
</view>
<view class="info-row flex-row-sb">
<view class="info-label">
<view class="subtitle">到期时间{{deviceInfo.expireDate}}</view>
</view>
<view class="info-values flex-row-g20">
<view class="tag-apple tag-primary">{{deviceInfo.statusStr}}</view>
</view>
</view>
</view>
<view class="device-metrics flex-row-sb mt-md">
<view class="metric-item flex-col-center">
<view class="caption mb-xs">信号强度</view>
<view class="metric-value">{{getSignalText(deviceInfo.rssi)}}</view>
</view>
<view class="metric-item flex-col-center">
<view class="caption mb-xs">设备电量</view>
<view class="metric-value">{{deviceInfo.battery || 0 }} %</view>
</view>
<view class="metric-item flex-col-center">
<view class="caption mb-xs">连接数量</view>
<view class="metric-value">{{deviceInfo.connCnt || 0}} </view>
</view>
</view>
</view>
<!-- 流量监控卡片 -->
<view class="card traffic-card">
<view class="card-header flex-row-sb mb-lg">
<view class="title">本月流量</view>
<view class="usage-percent">
<text class="title">{{usedFlowPercent}}</text>
<text class="caption">%</text>
</view>
</view>
<view class="progress-section mb-lg">
<view class="progress-apple">
<view class="progress-fill" :style="{width: usedFlowPercent + '%'}"></view>
</view>
</view>
<view class="traffic-stats flex-row-sb">
<view class="stat-item flex-col-center">
<view class="caption mb-xs">已使用</view>
<view class="stat-value-container">
<text class="stat-value">{{formatDataSize(deviceInfo.totalBytesCnt)}}</text>
<text class="stat-unit">GB</text>
</view>
</view>
<view class="stat-item flex-col-center">
<view class="caption mb-xs">总流量</view>
<view class="stat-value-container">
<text class="stat-value">{{formatDataSize(deviceInfo.flowSize)}}</text>
<text class="stat-unit">GB</text>
</view>
</view>
<view class="stat-item flex-col-center">
<view class="caption mb-xs">剩余</view>
<view class="stat-value-container">
<text
class="stat-value">{{formatDataSize(deviceInfo.flowSize - deviceInfo.totalBytesCnt)}}</text>
<text class="stat-unit">GB</text>
</view>
</view>
</view>
</view>
<!-- WiFi配置卡片 -->
<view class="card wifi-card">
<view class="card-header flex-row-sb">
<view class="title">WiFi配置</view>
<view class="btn-group">
<button class="btn-apple btn-primary btn-mini" style="padding: 20rpx;"
@tap="modifyWifi">修改配置</button>
</view>
</view>
<view class="wifi-info flex-col-g16 mt-30">
<view class="wifi-item flex-row-sb">
<view class="wifi-details flex-col-g8">
<view class="caption">网络名称</view>
<view class="subtitle">{{deviceInfo.ssidName}}</view>
</view>
<button class="btn-apple btn-primary btn-mini" @tap="copy(deviceInfo.ssidName)">
复制名称
</button>
</view>
<view class="wifi-item flex-row-sb">
<view class="wifi-details flex-col-g8">
<view class="caption">连接密码</view>
<view class="subtitle">{{deviceInfo.ssidPwd}}</view>
</view>
<button class="btn-apple btn-primary btn-mini" @tap="copy(deviceInfo.ssidPwd)">
复制密码
</button>
</view>
</view>
</view>
<!-- 功能菜单卡片 -->
<view class="card function-card">
<view class="card-header">
<view class="title">功能菜单</view>
</view>
<view class="function-grid">
<view class="function-item interactive card-interactive" @tap="enterDetail('package-order')"
role="button" tabindex="0">
<view class="function-icon">
<image src="/static/shop.png" mode="aspectFit" alt="套餐订购"></image>
</view>
<view class="function-name">套餐订购</view>
</view>
<view class="function-item interactive card-interactive" @tap="enterDetail('intelligent-diagnosis')"
role="button" tabindex="0">
<view class="function-icon">
<image src="/static/diagnosis.png" mode="aspectFit" alt="智能诊断"></image>
</view>
<view class="function-name">智能诊断</view>
</view>
<view class="function-item interactive card-interactive" @tap="enterDetail('back')" role="button"
tabindex="0">
<view class="function-icon">
<image src="/static/back.png" mode="aspectFit" alt="后台管理"></image>
</view>
<view class="function-name">后台管理</view>
</view>
<!-- <view class="function-item interactive card-interactive" @tap="enterDetail('switch')" role="button"
tabindex="0">
<view class="function-icon">
<image src="/static/change.png" mode="aspectFit" alt="切换运营商"></image>
</view>
<view class="function-name">切换运营商</view>
</view> -->
<view class="function-item interactive card-interactive" @tap="enterDetail('bind')" role="button"
tabindex="0">
<view class="function-icon">
<image src="/static/bind-phone.png" mode="aspectFit" alt="绑定手机号"></image>
</view>
<view class="function-name">
{{alreadyBindPhone ? "已绑定" : "未绑定" }}
</view>
</view>
<view class="function-item interactive card-interactive" @tap="enterDetail('authentication')"
role="button" tabindex="0">
<view class="function-icon">
<image src="/static/authentication.png" mode="aspectFit" alt="实名认证"></image>
</view>
<view class="function-name">
{{isRealName ? "已实名" : "未实名"}}
</view>
</view>
<view class="function-item interactive card-interactive" @tap="enterDetail('recover')" role="button"
tabindex="0">
<view class="function-icon">
<image src="/static/recover.png" mode="aspectFit" alt="重启设备"></image>
</view>
<view class="function-name">重启设备</view>
</view>
<view class="function-item interactive card-interactive" @tap="enterDetail('restart')" role="button"
tabindex="0">
<view class="function-icon">
<image src="/static/restart.png" mode="aspectFit" alt="恢复出厂"></image>
</view>
<view class="function-name">恢复出厂</view>
</view>
<view class="function-item interactive card-interactive" @tap="enterDetail('out')" role="button"
tabindex="0">
<view class="function-icon">
<image src="/static/out.png" mode="aspectFit" alt="退出登录"></image>
</view>
<view class="function-name">退出登录</view>
</view>
</view>
<!-- 修改WIFI -->
<up-popup :show="showModifyWifi" mode="center" @close="showModifyWifi=false">
<view class="container mt-30" style="width: 600rpx; padding: 30rpx;">
<view class="title">
修改WIFI
</view>
<view class="flex-row-g20">
<label for="">名称: </label>
<up-input placeholder="WIFI名称" border="surround" v-model="wifi_info.ssid" />
</view>
<view class="flex-row-g20">
<label for="">密码: </label>
<up-input placeholder="WIFI密码" border="surround" v-model="wifi_info.pwd" />
</view>
<view class="btn-group mt-20">
<button class="btn-apple btn-secondary" @tap="showModifyWifi=false">取消修改</button>
<button class="btn-apple btn-primary" @tap="confirmModify">确认修改</button>
</view>
</view>
</up-popup>
<!-- 重启设备 -->
<up-modal title="您确定要重启设备吗?" :show="restartShow" showCancelButton @confirm="YesRestart"
@cancel="restartShow=false" />
<!-- 恢复出厂设置 -->
<up-modal title="您确定要恢复出厂设置吗?" :show="recoverShow" showCancelButton @confirm="YesRecover"
@cancel="recoverShow=false" />
<!-- 退出登录 -->
<up-modal title="您确定要退出登录吗?" :show="logoutShow" showCancelButton @confirm="YesLogout"
@cancel="logoutShow=false" />
</view>
<!-- 悬浮联系客服按钮 -->
<view class="floating-customer-service flex-col-g20" @tap="connectKF">
<image src="/static/link.png"></image>
<text style="font-size: 24rpx;">在线客服</text>
</view>
</view>
</template>
<script setup>
import {
ref,
reactive,
onMounted,
computed
} from 'vue';
import {
userStore
} from '@/store/index.js';
import {
convertToGB,
formatSecondsToTime,
getSignalText
} from "@/utils/common.js"
let showModifyWifi = ref(false);
let restartShow = ref(false)
let recoverShow = ref(false)
let logoutShow = ref(false)
// 设备信息
let deviceInfo = reactive({
battery: 0, // 电池
connect: 0, // 连接数量
statusStr: "", // 状态
expireDate: "", // 过期时间
currentIccid: "", // 当前卡号
category: "", // 所属运营商
phone: "", // 实名需要phone
flowSize: 0, // 总流量
totalBytesCnt: 0, // 已用流量
ssidName: "", // wifi名称
ssidPwd: "", // wifi密码
rssi: "", // 信号强度
onlineStatus: "", // 在线状态 2在线 1离线
connCnt: 0, // 连接数
run_time: 0, // 运行时间
last_online_time: "", // 上次在线时间
lan_ip: "", // 后台地址
kf_url: "https://work.weixin.qq.com/kfid/kfc0fd79f27686fb65e?enc_scene=ENCfR9AVui6UfvvxySGVuvaG&scene_param=commonlink", // 客服
mchList: [], // 列表
})
let opratorList = reactive([{
category: "124", // 中国电信
logo: "https://img2.baidu.com/it/u=139558247,3893370039&fm=253&fmt=auto?w=529&h=500",
name: "中国电信"
},
{
category: "125", // 中国联通
logo: "https://img1.baidu.com/it/u=2816777816,1756344384&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=500",
name: "中国联通"
},
{
category: "126", // 中国移动
logo: "https://img2.baidu.com/it/u=915783975,1594870591&fm=253&fmt=auto&app=120&f=PNG?w=182&h=182",
name: "中国移动"
}
])
let isRealName = ref(false)
let alreadyBindPhone = ref(false)
let wifi_info = reactive({
ssid: "",
pwd: ""
})
let device_id = uni.getStorageSync("device_id")
// 获取用户信息
const getUserInfo = async () => {
await userStore.actions.getUserInfo()
}
const userInfo = computed(() => {
return userStore.state.userInfo
})
// 获取设备信息
const getCardInfo = async () => {
const result = await userStore.actions.getCardInfo()
if (result?.entity) {
deviceInfo.statusStr = result.entity.statusStr
deviceInfo.expireDate = result.entity.expireDate
}
const mainInfo = await userStore.actions.getSwitchCardList()
if (mainInfo?.cardEntity) {
deviceInfo.category = mainInfo.cardEntity.category
deviceInfo.phone = mainInfo.cardEntity.phone
deviceInfo.flowSize = convertToGB(mainInfo.cardEntity.flowSize, "flowSize")
deviceInfo.totalBytesCnt = convertToGB(mainInfo.cardEntity.totalBytesCnt, "totalBytesCnt")
}
if (mainInfo?.gswlinfo) {
deviceInfo.connCnt = mainInfo.gswlinfo.connCnt
deviceInfo.onlineStatus = mainInfo.gswlinfo.onlineStatus
deviceInfo.currentIccid = mainInfo.gswlinfo.iccid
}
if (mainInfo?.mchList.length > 0) {
deviceInfo.mchList = mainInfo.mchList
}
const {
data
} = await userStore.actions.getDeviceInfoAdmin(device_id)
const result_admin = data.list || []
if (result_admin.length > 0) {
const adminInfo = result_admin[0]
deviceInfo.ssidName = adminInfo.ssid
deviceInfo.ssidPwd = adminInfo.wifi_password
deviceInfo.rssi = adminInfo.rssi
deviceInfo.battery = adminInfo.battery_level
deviceInfo.run_time = adminInfo.run_time
deviceInfo.last_online_time = adminInfo.last_online_time
deviceInfo.lan_ip = adminInfo.lan_ip
}
}
// 获取实名信息
const realName = async () => {
const {
alreadyRealName,
alreadyBind
} = await userStore.actions.getRealNameInfo()
isRealName.value = alreadyRealName
alreadyBindPhone.value = alreadyBind
if (!alreadyRealName) {
uni.showToast({
title: '未实名跳转实名页面',
icon: 'none'
})
uni.navigateTo({
url: '/pages/auth/auth'
})
}
}
// 修改WiFi
const modifyWifi = () => {
wifi_info.ssid = deviceInfo.ssidName
wifi_info.pwd = deviceInfo.ssidPwd
showModifyWifi.value = true
}
// 确认修改
const confirmModify = async () => {
if (!wifi_info.ssid || !wifi_info.pwd) {
uni.showToast({
title: 'WIFI名称和密码不能为空',
icon: 'none'
})
return
}
const result = await userStore.actions.modifyWifi({
cardNo: device_id,
ssid: wifi_info.ssid,
password: wifi_info.pwd
})
if (result.code === 200) {
uni.showToast({
title: result.message,
icon: 'none'
})
} else {
uni.showToast({
title: result.message,
icon: 'none'
})
}
showModifyWifi.value = false
}
// 连接客服
const connectKF = () => {
uni.showToast({
title: '正在跳转客服',
icon: 'none'
})
window.location.href = deviceInfo.kf_url
}
const intelligentDiagnosis = async () => {
await userStore.actions.intelligentDiagnosis()
uni.showToast({
title: '设备可以正常使用',
icon: 'none'
})
}
const enterBack = () => {
if (!deviceInfo.lan_ip) {
uni.showToast({
title: '后台地址为空',
icon: 'none'
})
}
window.location.href = `http://${deviceInfo.lan_ip}`
uni.showToast({
title: '正在跳转后台: http://' + deviceInfo.lan_ip,
icon: 'none'
})
}
// 重启设备
const YesRestart = async () => {
const {
code,
msg
} = await userStore.actions.restartDevice(device_id)
if (code === 200) {
uni.showToast({
title: '设备重启成功!',
icon: 'none'
})
} else {
uni.showToast({
title: msg,
icon: 'none'
})
}
restartShow.value = false
}
const copy = (content) => {
uni.setClipboardData({
data: content,
success: () => {
uni.showToast({
title: '复制成功',
icon: 'none'
})
},
fail: () => {
uni.showToast({
title: '复制失败',
icon: 'none'
})
},
showToast: false
})
}
// 恢复出厂
const YesRecover = async () => {
const result = await userStore.actions.restDevice(device_id)
uni.showToast({
title: '恢复出厂',
icon: 'none'
})
recoverShow.value = false
}
// 退出登录
const YesLogout = async () => {
try {
await userStore.actions.logout()
// 状态清除和跳转逻辑已在store中处理
logoutShow.value = false
} catch (error) {
console.error('退出登录出错:', error);
uni.showToast({
title: '退出登录失败',
icon: 'none'
})
logoutShow.value = false
}
}
// 已使用流量占比 保留两位小数
const usedFlowPercent = computed(() => {
if (deviceInfo.flowSize && deviceInfo.totalBytesCnt) {
const usedFlow = parseFloat(deviceInfo.totalBytesCnt)
const totalFlow = parseFloat(deviceInfo.flowSize)
const percent = (usedFlow / totalFlow) * 100
return percent.toFixed(2)
}
return 0
})
const getOperator = computed(() => {
return (iccid) => {
const currentInfo = deviceInfo.mchList.find(item => item.iccidMark === iccid);
if(currentInfo){
const operator = opratorList.find(item => item.category === currentInfo.category)
return operator ? operator.name : ''; // 返回运营商名称
}
return ''; // 如果没有找到匹配的运营商,返回空字符串
}
})
// 格式化数据大小为更易读的格式
const formatDataSize = (size) => {
if (!size) return '0'
const num = parseFloat(size)
if (num < 0.01) return '0'
return num.toFixed(2)
}
const onlineStatus = computed(() => {
return deviceInfo.onlineStatus == "1" ? '在线' : '离线'
})
// 清理 URL 中的 code 参数
const removeCodeFromUrl = () => {
// 获取当前 URL
const url = window.location.href;
const urlParams = new URLSearchParams(window.location.search);
// 如果 URL 中包含 code 参数,则清除它
if (urlParams.has('code')) {
urlParams.delete('code'); // 删除 code 参数
// 使用 replaceState 修改当前的 URL不会重新加载页面
window.history.replaceState({}, '', `${url.split('?')[0]}?${urlParams.toString()}`);
}
};
// 跳转到微信授权页面
const toAuth = async () => {
try {
const result = await userStore.actions.getWxUrl();
if (result.wxAuthUrl) {
// 获取当前页面的URL
const currentUrl = window.location.href;
// 检查当前URL是否已经包含 code 参数
if (!currentUrl.includes("code=")) {
// 如果没有code参数跳转到微信授权页面
let originalUrl = result.wxAuthUrl;
// 目标替换的 redirect_uri
const newRedirectUri = "https://m1.whjhft.com/pages/index/index";
// 替换 "https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxea8c599fe100ce8a&redirect_uri=https://m1.whjhft.com/my/my&response_type=code&scope=snsapi_userinfo&state=cardlogin#wechat_redirect"
// 使用正则表达式替换 redirect_uri 的值
const updatedUrl = originalUrl.replace(/(redirect_uri=)[^&]*/,
`$1${encodeURIComponent(newRedirectUri)}`);
if (updatedUrl) {
window.location.href = updatedUrl;
}
} else {
// 如果已经包含code参数, 就进行授权
authoration()
}
} else {
uni.showToast({
title: '该用户已授权',
icon: 'none'
})
}
} catch (e) {
uni.showToast({
title: e,
icon: 'none'
});
}
};
// 授权
const authoration = async () => {
try {
// 获取当前页面的完整 URL https://m1.whjhft.com/pages/index/index?code=081KSnml23G2Ug4XViml2VFHyJ0KSnmF&state=cardlogin
// const url = window.location.href;
const code = (location.search.match(/[?&]code=([^&]*)/) || [])[1];
if (code) {
// 如果 code 存在,则调用获取授权的动作
await userStore.actions.getAuthorize(code);
removeCodeFromUrl()
} else {
// 如果 code 不存在
uni.showToast({
title: "微信授权失败!",
icon: "none"
});
}
} catch (error) {
// 错误处理
console.error('授权失败:', error);
uni.showToast({
title: "授权失败,请重试",
icon: "none"
});
}
};
// 实名认证
const authentication = async () => {
if (isRealName) {
uni.showToast({
title: "您已实名!",
icon: "none"
})
} else {
const result = await userStore.actions.getRealNameAddress(deviceInfo.currentIccid)
let url = result.accountEntity.realNameUrl
if (url) {
// 替换url里面的 ${iccid} 替换为deviceInfo.currentIccid ${phone}替换为 deviceInfo.phone
url = url.replace("${iccid}", deviceInfo.currentIccid).replace("${phone}", deviceInfo.phone)
uni.showToast({
title: "正在跳转实名",
icon: "none"
})
window.location.href = url
} else {
uni.showToast({
title: '未获取到实名地址',
icon: 'none'
})
}
}
}
const enterDetail = (name) => {
switch (name) {
case 'package-order':
uni.navigateTo({
url: '/pages/package-order/package-order'
})
break;
case 'intelligent-diagnosis':
intelligentDiagnosis()
break;
case 'back':
enterBack()
break;
case 'switch':
uni.navigateTo({
url: '/pages/switch/switch?mchList=' + JSON.stringify(deviceInfo.mchList) +
"&currentIccid=" + deviceInfo.currentIccid
})
break;
case 'bind':
uni.navigateTo({
url: '/pages/bind/bind'
})
break;
case 'authentication':
authentication()
break;
case 'recover':
restartShow.value = true
break;
case 'restart':
restartShow.value = true
break;
case 'out':
logoutShow.value = true
break;
default:
break;
}
}
onMounted(async () => {
await toAuth()
// 实名
await realName()
// 获取用户信息
await getUserInfo()
// 获取卡信息
await getCardInfo()
})
</script>
<style scoped lang="scss">
/* 用户信息卡片 */
.user-info-card {
color: black;
.user-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 16rpx;
overflow: hidden;
border: 2rpx solid rgba(255, 255, 255, 0.3);
image {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.user-details {
flex: 1;
.title {
font-size: 32rpx;
font-weight: 600;
}
.caption {
font-size: 24rpx;
font-weight: 400;
}
}
}
/* 设备状态卡片 */
.device-status-card {
.card-header {
.title {
color: var(--text-primary);
}
}
.info-row {
padding: var(--space-sm) 0;
border-bottom: 1rpx solid var(--system-gray-6);
&:last-child {
border-bottom: none;
}
.info-label {
min-width: 200rpx;
}
.info-values {
flex: 1;
justify-content: flex-end;
}
}
.device-metrics {
background: var(--system-gray-6);
border-radius: 12rpx;
padding: 20rpx;
.metric-item {
flex: 1;
.caption {
color: var(--text-tertiary);
font-size: 22rpx;
font-weight: 500;
}
.metric-value {
font-size: 30rpx;
font-weight: 600;
color: var(--text-primary);
margin-top: 8rpx;
}
}
}
}
/* 流量监控卡片 */
.traffic-card {
.usage-percent {
display: flex;
align-items: baseline;
gap: 4rpx;
.title {
font-size: 35rpx;
font-weight: 700;
margin-right: 5rpx;
}
.caption {
color: var(--text-tertiary);
font-weight: 500;
}
}
.traffic-stats {
background: var(--system-gray-6);
border-radius: 12rpx;
padding: 20rpx;
margin-top: 16rpx;
.stat-item {
flex: 1;
.caption {
color: var(--text-tertiary);
font-size: 22rpx;
font-weight: 500;
}
.stat-value-container {
display: flex;
align-items: baseline;
justify-content: center;
gap: 4rpx;
margin-top: 8rpx;
.stat-value {
font-size: 32rpx;
font-weight: 700;
color: var(--text-primary);
}
.stat-unit {
font-size: 18rpx;
font-weight: 500;
color: var(--text-tertiary);
margin-top: 2rpx;
}
}
}
}
}
/* WiFi配置卡片 */
.wifi-card {
.wifi-item {
padding: var(--space-md);
background: var(--system-gray-6);
border-radius: var(--radius-medium);
margin-bottom: var(--space-sm);
&:last-child {
margin-bottom: 0;
}
.wifi-details {
flex: 1;
.caption {
color: var(--text-tertiary);
font-size: 22rpx;
}
.subtitle {
color: var(--text-primary);
font-weight: 600;
font-size: 30rpx;
}
}
}
}
/* 功能菜单卡片 */
.function-card {
.function-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
width: 100%;
.function-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20rpx;
.function-icon {
width: 120rpx;
height: 120rpx;
display: flex;
align-items: center;
justify-content: center;
image {
width: 100%;
height: 100%;
}
}
.function-name {
font-size: 28rpx;
font-weight: 500;
color: var(--text-primary);
text-align: center;
margin-top: 15rpx;
}
}
}
}
/* 响应式设计 */
@media screen and (max-width: 750rpx) {
.function-grid {
grid-template-columns: repeat(2, 1fr);
gap: 12rpx;
.function-item {
.function-icon {
width: 56rpx;
height: 56rpx;
margin-bottom: 12rpx;
image {
width: 56rpx;
height: 56rpx;
}
}
.function-name {
font-size: 24rpx;
}
}
}
.traffic-stats {
flex-direction: column;
gap: var(--space-md);
.stat-item {
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: var(--space-sm) 0;
.stat-value-container {
justify-content: flex-end;
gap: 6rpx;
.stat-value {
font-size: 32rpx;
}
.stat-unit {
font-size: 18rpx;
}
}
}
}
}
/* 动画增强 */
.function-item {
will-change: transform, opacity;
backface-visibility: hidden;
}
/* 悬浮联系客服按钮 */
.floating-customer-service {
position: fixed;
bottom: 40rpx;
right: 30rpx;
width: 150rpx;
height: 150rpx;
gap: 10rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.95);
border: none;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15),
0 4rpx 12rpx rgba(0, 0, 0, 0.1);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
transform: scale(1);
animation: float 3s ease-in-out infinite, pulse 2s ease-in-out infinite;
backdrop-filter: blur(10rpx);
-webkit-backdrop-filter: blur(10rpx);
cursor: pointer;
image {
width: 70rpx;
height: 60rpx;
object-fit: contain;
}
}
.floating-customer-service:active {
transform: scale(0.95);
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.12),
0 2rpx 8rpx rgba(0, 0, 0, 0.08);
}
.floating-customer-service:hover {
transform: scale(1.05);
box-shadow: 0 12rpx 32rpx rgba(0, 0, 0, 0.18),
0 6rpx 16rpx rgba(0, 0, 0, 0.12);
background: rgba(255, 255, 255, 1);
}
/* 悬浮动画 */
@keyframes float {
0%,
100% {
transform: translateY(0rpx) scale(1);
}
50% {
transform: translateY(-10rpx) scale(1);
}
}
/* 呼吸脉冲动画 */
@keyframes pulse {
0%,
100% {
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15),
0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
50% {
box-shadow: 0 12rpx 32rpx rgba(0, 0, 0, 0.2),
0 6rpx 16rpx rgba(0, 0, 0, 0.15);
}
}
/* 适配小屏幕 */
@media screen and (max-width: 750rpx) {
.floating-customer-service {
width: 100rpx;
height: 100rpx;
bottom: 40rpx;
right: 40rpx;
image {
width: 60rpx;
height: 60rpx;
}
}
}
</style>