|
|
@@ -1,707 +1,625 @@
|
|
|
<template>
|
|
|
<view class="page-container">
|
|
|
- <!-- 顶部简洁卡片 -->
|
|
|
- <view class="top-card">
|
|
|
- <view class="user-section">
|
|
|
- <view class="user-avatar">
|
|
|
- <text class="iconfont icon-user">👤</text>
|
|
|
- </view>
|
|
|
- <view class="user-info">
|
|
|
- <text class="greeting">你好,{{ userInfo?.name || '管理员' }}</text>
|
|
|
- <text class="station-name">{{ currentStation?.stationName || '暂无站点' }}</text>
|
|
|
+ <NavBar title="首页" :showBack="false">
|
|
|
+ <template #right>
|
|
|
+ <view class="nav-btn" @click="handleLogout">
|
|
|
+ <AppIcon name="log-out" :size="28" color="#FFFFFF" />
|
|
|
</view>
|
|
|
+ </template>
|
|
|
+ </NavBar>
|
|
|
+
|
|
|
+ <!-- 加载骨架 -->
|
|
|
+ <view class="content" v-if="loading && !hasData">
|
|
|
+ <view class="skeleton-greeting"></view>
|
|
|
+ <view class="skeleton-hero">
|
|
|
+ <view class="skeleton-line hero-num"></view>
|
|
|
+ <view class="skeleton-line hero-sub"></view>
|
|
|
</view>
|
|
|
-
|
|
|
- <view class="actions">
|
|
|
- <picker
|
|
|
- mode="selector"
|
|
|
- :range="stationList"
|
|
|
- range-key="stationName"
|
|
|
- :value="selectedStationIndex"
|
|
|
- @change="onStationChange"
|
|
|
- >
|
|
|
- <view class="action-btn">
|
|
|
- <text class="icon">🏢</text>
|
|
|
- </view>
|
|
|
- </picker>
|
|
|
-
|
|
|
- <view class="action-btn" @click="toggleUserMenu">
|
|
|
- <text class="icon">⚙️</text>
|
|
|
- </view>
|
|
|
+ <view class="skeleton-line station-sk"></view>
|
|
|
+ <view class="skeleton-grid">
|
|
|
+ <view class="skeleton-cell"></view>
|
|
|
+ <view class="skeleton-cell"></view>
|
|
|
+ <view class="skeleton-cell sm"></view>
|
|
|
+ <view class="skeleton-cell sm"></view>
|
|
|
+ </view>
|
|
|
+ <view class="skeleton-line section-title"></view>
|
|
|
+ <view class="skeleton-actions">
|
|
|
+ <view class="skeleton-action"></view>
|
|
|
+ <view class="skeleton-action"></view>
|
|
|
+ <view class="skeleton-action"></view>
|
|
|
</view>
|
|
|
</view>
|
|
|
-
|
|
|
- <!-- 用户菜单 -->
|
|
|
- <view class="user-menu" v-if="showUserMenu" @click="handleLogout">
|
|
|
- <text class="menu-text">退出登录</text>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 今日数据概览 -->
|
|
|
- <view class="section">
|
|
|
- <view class="section-title">今日数据</view>
|
|
|
- <view class="data-cards">
|
|
|
- <view class="data-card purple">
|
|
|
- <text class="data-value">{{ todayOrders }}</text>
|
|
|
- <text class="data-label">订单数</text>
|
|
|
- <view class="card-icon">📋</view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <view class="data-card pink">
|
|
|
- <text class="data-value">¥{{ formatAmount(todayRevenue) }}</text>
|
|
|
- <text class="data-label">营业额</text>
|
|
|
- <view class="card-icon">💰</view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <view class="data-card blue">
|
|
|
- <text class="data-value">{{ todayRegisteredMembers }}</text>
|
|
|
- <text class="data-label">新会员</text>
|
|
|
- <view class="card-icon">👥</view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <view class="data-card green">
|
|
|
- <text class="data-value">{{ onlineDevices }}/{{ totalDevices }}</text>
|
|
|
- <text class="data-label">在线设备</text>
|
|
|
- <view class="card-icon">📱</view>
|
|
|
+
|
|
|
+ <!-- 错误态 -->
|
|
|
+ <view class="content" v-else-if="error && !hasData">
|
|
|
+ <view class="error-state">
|
|
|
+ <AppIcon name="inbox" :size="48" color="#BFBFBF" />
|
|
|
+ <text class="error-text">数据加载失败</text>
|
|
|
+ <text class="error-hint">下拉页面或点击下方按钮重试</text>
|
|
|
+ <view class="retry-btn" @click="refreshData">
|
|
|
+ <text class="retry-btn-text">重新加载</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
-
|
|
|
- <!-- 详细统计 -->
|
|
|
- <view class="section">
|
|
|
- <view class="section-title">数据统计</view>
|
|
|
- <view class="stat-list">
|
|
|
- <view class="stat-item">
|
|
|
- <view class="stat-icon red">💳</view>
|
|
|
- <view class="stat-info">
|
|
|
- <text class="stat-label">今日消费金额</text>
|
|
|
- <text class="stat-value">¥{{ formatAmount(todayConsumptionAmount) }}</text>
|
|
|
+
|
|
|
+ <!-- 正常内容 -->
|
|
|
+ <view class="content" v-else>
|
|
|
+ <!-- 问候语 -->
|
|
|
+ <text class="greeting">{{ timeGreeting }}</text>
|
|
|
+
|
|
|
+ <!-- Hero -->
|
|
|
+ <view class="hero-section">
|
|
|
+ <text class="hero-value">¥{{ formatAmount(todayRevenue) }}</text>
|
|
|
+ <text class="hero-label">今日收入</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 站点栏 -->
|
|
|
+ <picker
|
|
|
+ v-if="stationList.length > 0"
|
|
|
+ mode="selector"
|
|
|
+ :range="stationList"
|
|
|
+ range-key="stationName"
|
|
|
+ :value="selectedStationIndex"
|
|
|
+ @change="onStationChange"
|
|
|
+ >
|
|
|
+ <view class="station-card">
|
|
|
+ <view class="station-card-row">
|
|
|
+ <AppIcon name="building" :size="18" color="#C6171E" />
|
|
|
+ <text class="station-card-name">{{ currentStation?.stationName || '选择站点' }}</text>
|
|
|
+ <AppIcon name="chevron-down" :size="14" color="#999999" />
|
|
|
</view>
|
|
|
+ <text class="station-card-date">{{ todayDate }}</text>
|
|
|
</view>
|
|
|
-
|
|
|
- <view class="stat-item">
|
|
|
- <view class="stat-icon blue">💵</view>
|
|
|
- <view class="stat-info">
|
|
|
- <text class="stat-label">平均订单金额</text>
|
|
|
- <text class="stat-value">¥{{ formatAmount(avgOrderPrice) }}</text>
|
|
|
+ </picker>
|
|
|
+
|
|
|
+ <!-- 主指标:订单 + 消费 -->
|
|
|
+ <view class="metrics-primary">
|
|
|
+ <view class="metric-card-lg">
|
|
|
+ <text class="metric-lg-value">{{ todayOrders }}</text>
|
|
|
+ <text class="metric-lg-label">今日订单</text>
|
|
|
+ </view>
|
|
|
+ <view class="metric-card-lg">
|
|
|
+ <text class="metric-lg-value">¥{{ formatAmount(todayConsumption) }}</text>
|
|
|
+ <text class="metric-lg-label">消费总额</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 次指标:均价 + 时长 -->
|
|
|
+ <view class="metrics-secondary">
|
|
|
+ <view class="metric-card-sm">
|
|
|
+ <view class="metric-sm-icon">
|
|
|
+ <AppIcon name="trending-up" :size="18" color="#C6171E" />
|
|
|
</view>
|
|
|
+ <text class="metric-sm-value">¥{{ formatAmount(avgOrderPrice) }}</text>
|
|
|
+ <text class="metric-sm-label">平均消费</text>
|
|
|
</view>
|
|
|
-
|
|
|
- <view class="stat-item">
|
|
|
- <view class="stat-icon purple">⏱️</view>
|
|
|
- <view class="stat-info">
|
|
|
- <text class="stat-label">平均洗车时长</text>
|
|
|
- <text class="stat-value">{{ avgOrderDuration }} 分钟</text>
|
|
|
+ <view class="metric-card-sm">
|
|
|
+ <view class="metric-sm-icon">
|
|
|
+ <AppIcon name="clock" :size="18" color="#C6171E" />
|
|
|
</view>
|
|
|
+ <text class="metric-sm-value">{{ avgDuration }} 分钟</text>
|
|
|
+ <text class="metric-sm-label">平均时长</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 快捷操作 -->
|
|
|
- <view class="section">
|
|
|
- <view class="section-title">快捷操作</view>
|
|
|
- <view class="action-grid">
|
|
|
- <view class="action-item purple" @click="navigateTo('/pages/order/list')">
|
|
|
- <view class="action-icon">📋</view>
|
|
|
+
|
|
|
+ <!-- 快捷入口标题 -->
|
|
|
+ <view class="section-header">
|
|
|
+ <view class="section-dot"></view>
|
|
|
+ <text class="section-title">快捷功能</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 快捷入口 -->
|
|
|
+ <view class="actions-row">
|
|
|
+ <view class="action-item" @click="navigateTo('/pages/order/list')">
|
|
|
+ <view class="action-icon-wrap">
|
|
|
+ <AppIcon name="clipboard" :size="26" color="#FFFFFF" />
|
|
|
+ </view>
|
|
|
<text class="action-label">订单管理</text>
|
|
|
</view>
|
|
|
-
|
|
|
- <view class="action-item pink" @click="navigateTo('/pages/device/list')">
|
|
|
- <view class="action-icon">📺</view>
|
|
|
+ <view class="action-item" @click="navigateTo('/pages/device/list')">
|
|
|
+ <view class="action-icon-wrap">
|
|
|
+ <AppIcon name="monitor" :size="26" color="#FFFFFF" />
|
|
|
+ </view>
|
|
|
<text class="action-label">设备监控</text>
|
|
|
</view>
|
|
|
-
|
|
|
- <view class="action-item blue" @click="navigateTo('/pages/finance/index')">
|
|
|
- <view class="action-icon">💰</view>
|
|
|
+ <view class="action-item" @click="navigateTo('/pages/finance/index')">
|
|
|
+ <view class="action-icon-wrap">
|
|
|
+ <AppIcon name="dollar" :size="26" color="#FFFFFF" />
|
|
|
+ </view>
|
|
|
<text class="action-label">财务管理</text>
|
|
|
</view>
|
|
|
-
|
|
|
- <view class="action-item green" @click="navigateToWithdraw">
|
|
|
- <view class="action-icon">💸</view>
|
|
|
- <text class="action-label">提现审核</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 底栏信息 -->
|
|
|
+ <view class="info-bar">
|
|
|
+ <view class="info-item">
|
|
|
+ <AppIcon name="users" :size="16" color="#999999" />
|
|
|
+ <text class="info-text">今日注册会员 <text class="info-num">{{ todayMembers }}</text> 人</text>
|
|
|
</view>
|
|
|
-
|
|
|
- <view class="action-item orange" @click="navigateTo('/pages/setting/device-config')">
|
|
|
- <view class="action-icon">⚙️</view>
|
|
|
- <text class="action-label">设备配置</text>
|
|
|
+ <view class="info-item">
|
|
|
+ <AppIcon name="smartphone" :size="16" color="#999999" />
|
|
|
+ <text class="info-text">在线设备 <text class="info-num">{{ onlineDevices }}</text>/<text class="info-num">{{ totalDevices }}</text></text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
-
|
|
|
- <!-- 加载指示器 -->
|
|
|
- <view class="loading" v-if="loading">
|
|
|
- <view class="spinner"></view>
|
|
|
- </view>
|
|
|
+
|
|
|
+ <custom-tab-bar />
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, onMounted } from 'vue'
|
|
|
+import { ref, computed, onMounted } from 'vue'
|
|
|
+import { onPullDownRefresh } from '@dcloudio/uni-app'
|
|
|
+import CustomTabBar from '../../custom-tab-bar/index.vue'
|
|
|
import { logout } from '../../api/auth.js'
|
|
|
import { getDashboardData, getDeviceStatus } from '../../api/stat.js'
|
|
|
import { getStationList } from '../../api/station.js'
|
|
|
-import { getDataDictList } from '../../api/dict.js'
|
|
|
+import { loadDicts } from '../../utils/dict.js'
|
|
|
+import dictUtil from '../../utils/dict.js'
|
|
|
import { storage, showToast, formatAmount } from '../../utils/index.js'
|
|
|
|
|
|
-const showUserMenu = ref(false)
|
|
|
const userInfo = ref(storage.get('userInfo'))
|
|
|
const loading = ref(false)
|
|
|
+const error = ref(false)
|
|
|
+const hasData = ref(false)
|
|
|
|
|
|
-// 站点相关数据
|
|
|
const stationList = ref([])
|
|
|
const currentStation = ref(null)
|
|
|
const selectedStationIndex = ref(0)
|
|
|
|
|
|
-// 数据状态
|
|
|
+const todayRevenue = ref(0)
|
|
|
const todayOrders = ref(0)
|
|
|
+const todayConsumption = ref(0)
|
|
|
+const avgOrderPrice = ref(0)
|
|
|
+const avgDuration = ref(0)
|
|
|
+const todayMembers = ref(0)
|
|
|
const totalDevices = ref(0)
|
|
|
const onlineDevices = ref(0)
|
|
|
-const todayRevenue = ref(0)
|
|
|
-const todayRegisteredMembers = ref(0)
|
|
|
-const todayConsumptionAmount = ref(0)
|
|
|
-const avgOrderPrice = ref(0)
|
|
|
-const avgOrderDuration = ref(0)
|
|
|
|
|
|
-// 切换用户菜单
|
|
|
-const toggleUserMenu = () => {
|
|
|
- showUserMenu.value = !showUserMenu.value
|
|
|
-}
|
|
|
+const todayDate = computed(() => {
|
|
|
+ const d = new Date()
|
|
|
+ return `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日`
|
|
|
+})
|
|
|
+
|
|
|
+const timeGreeting = computed(() => {
|
|
|
+ const h = new Date().getHours()
|
|
|
+ if (h < 6) return '夜深了'
|
|
|
+ if (h < 9) return '早上好'
|
|
|
+ if (h < 12) return '上午好'
|
|
|
+ if (h < 14) return '中午好'
|
|
|
+ if (h < 18) return '下午好'
|
|
|
+ return '晚上好'
|
|
|
+})
|
|
|
|
|
|
-// 页面加载时获取数据
|
|
|
onMounted(async () => {
|
|
|
- // 并行加载站点列表和系统字典数据
|
|
|
- await Promise.all([
|
|
|
- loadStationList(),
|
|
|
- loadDictionaries()
|
|
|
- ])
|
|
|
+ await Promise.all([loadStationList(), loadDictionaries()])
|
|
|
})
|
|
|
|
|
|
-// 加载站点列表
|
|
|
const loadStationList = async () => {
|
|
|
try {
|
|
|
- const res = await getStationList({
|
|
|
- pageNum: 1,
|
|
|
- pageSize: 1024
|
|
|
- })
|
|
|
-
|
|
|
+ const res = await getStationList({ pageNum: 1, pageSize: 1024 })
|
|
|
if (res && res.code === 200 && res.data && res.data.list) {
|
|
|
stationList.value = res.data.list
|
|
|
-
|
|
|
- // 默认选中第一个站点
|
|
|
if (stationList.value.length > 0) {
|
|
|
currentStation.value = stationList.value[0]
|
|
|
selectedStationIndex.value = 0
|
|
|
-
|
|
|
- // 保存当前选中的站点ID到缓存
|
|
|
storage.set('currentStationId', currentStation.value.stationId)
|
|
|
-
|
|
|
- // 加载该站点的数据
|
|
|
await loadData(currentStation.value.stationId)
|
|
|
}
|
|
|
} else {
|
|
|
- showToast('获取站点列表失败')
|
|
|
+ error.value = true
|
|
|
}
|
|
|
- } catch (error) {
|
|
|
- console.error('加载站点列表失败:', error)
|
|
|
- showToast('加载站点列表失败')
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载站点列表失败:', e)
|
|
|
+ error.value = true
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// 加载系统字典数据
|
|
|
const loadDictionaries = async () => {
|
|
|
- try {
|
|
|
- const res = await getDataDictList()
|
|
|
- if (res && res.code === 200) {
|
|
|
- const list = res.data
|
|
|
- // 按code分组
|
|
|
- const dictGroup = {}
|
|
|
- list.forEach(item => {
|
|
|
- if (!dictGroup[item.code]) {
|
|
|
- dictGroup[item.code] = []
|
|
|
- }
|
|
|
- dictGroup[item.code].push(item)
|
|
|
- })
|
|
|
- // 保存到缓存
|
|
|
- storage.set('dicts', dictGroup)
|
|
|
- console.log('系统字典数据加载成功:', dictGroup)
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('加载系统字典数据失败:', error)
|
|
|
- }
|
|
|
+ await loadDicts()
|
|
|
}
|
|
|
|
|
|
-// 站点切换事件
|
|
|
const onStationChange = async (e) => {
|
|
|
const index = e.detail.value
|
|
|
selectedStationIndex.value = index
|
|
|
currentStation.value = stationList.value[index]
|
|
|
-
|
|
|
- // 保存当前选中的站点ID
|
|
|
storage.set('currentStationId', currentStation.value.stationId)
|
|
|
-
|
|
|
- // 加载新站点的数据
|
|
|
+
|
|
|
await loadData(currentStation.value.stationId)
|
|
|
-
|
|
|
showToast(`已切换至${currentStation.value.stationName}`, 'success')
|
|
|
}
|
|
|
|
|
|
-// 加载首页数据(根据站点ID)
|
|
|
const loadData = async (stationId) => {
|
|
|
- if (!stationId) {
|
|
|
- console.error('站点ID为空')
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- loading.value = true
|
|
|
+ if (!stationId) return
|
|
|
+ error.value = false
|
|
|
+ if (!hasData.value) loading.value = true
|
|
|
+
|
|
|
try {
|
|
|
- // 并发请求两个接口
|
|
|
const [dashboardRes, deviceRes] = await Promise.all([
|
|
|
- getDashboardData(stationId).catch(err => {
|
|
|
- console.error('获取仪表盘数据失败:', err)
|
|
|
- return null
|
|
|
- }),
|
|
|
- getDeviceStatus(stationId).catch(err => {
|
|
|
- console.error('获取设备状态失败:', err)
|
|
|
- return null
|
|
|
- })
|
|
|
+ getDashboardData(stationId).catch(err => { console.error('获取仪表盘数据失败:', err); return null }),
|
|
|
+ getDeviceStatus(stationId).catch(err => { console.error('获取设备状态失败:', err); return null })
|
|
|
])
|
|
|
-
|
|
|
- // 处理仪表盘数据
|
|
|
+
|
|
|
+ if (!dashboardRes && !deviceRes) { error.value = true; return }
|
|
|
+
|
|
|
if (dashboardRes && dashboardRes.code === 200) {
|
|
|
const data = dashboardRes.data
|
|
|
- todayOrders.value = data.todayWashOrders || 0
|
|
|
todayRevenue.value = data.todayIncome || 0
|
|
|
- todayRegisteredMembers.value = data.todayRegisteredMembers || 0
|
|
|
- todayConsumptionAmount.value = data.todayConsumptionAmount || 0
|
|
|
+ todayOrders.value = data.todayWashOrders || 0
|
|
|
+ todayConsumption.value = data.todayConsumptionAmount || 0
|
|
|
avgOrderPrice.value = data.avgOrderPrice || 0
|
|
|
- avgOrderDuration.value = data.avgOrderDuration || 0
|
|
|
+ avgDuration.value = data.avgOrderDuration || 0
|
|
|
+ todayMembers.value = data.todayRegisteredMembers || 0
|
|
|
}
|
|
|
-
|
|
|
- // 处理设备状态数据
|
|
|
+
|
|
|
if (deviceRes && deviceRes.code === 200) {
|
|
|
const deviceData = deviceRes.data
|
|
|
totalDevices.value = Object.values(deviceData).reduce((sum, count) => sum + count, 0)
|
|
|
- // 从字典获取在线状态值,而不是硬编码 '1'
|
|
|
- const dicts = storage.get('dicts')
|
|
|
- let onlineCount = 0
|
|
|
- if (dicts && dicts['WashDevice.status']) {
|
|
|
- const onlineStatuses = dicts['WashDevice.status']
|
|
|
- .filter(item => item.color === '#52C41A')
|
|
|
+ const onlineStatuses = dictUtil.getDictList('WashDevice.status')
|
|
|
+ .filter(item => item.name === '在线')
|
|
|
.map(item => String(item.value))
|
|
|
- onlineCount = Object.entries(deviceData)
|
|
|
+ const onlineCount = Object.entries(deviceData)
|
|
|
.filter(([key]) => onlineStatuses.includes(key))
|
|
|
.reduce((sum, [, count]) => sum + count, 0)
|
|
|
- } else {
|
|
|
- onlineCount = deviceData['1'] || 0
|
|
|
- }
|
|
|
- onlineDevices.value = onlineCount
|
|
|
}
|
|
|
- } catch (error) {
|
|
|
- console.error('加载首页数据失败:', error)
|
|
|
+ hasData.value = true
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载首页数据失败:', e)
|
|
|
+ if (!hasData.value) error.value = true
|
|
|
} finally {
|
|
|
loading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// 页面导航
|
|
|
+const refreshData = async () => {
|
|
|
+ if (currentStation.value) await loadData(currentStation.value.stationId)
|
|
|
+}
|
|
|
+
|
|
|
const navigateTo = (url) => {
|
|
|
- showUserMenu.value = false
|
|
|
-
|
|
|
- // 统一处理路径格式:确保以斜杠开头
|
|
|
+
|
|
|
let fullUrl = url
|
|
|
- if (!fullUrl.startsWith('/')) {
|
|
|
- fullUrl = `/${fullUrl}`
|
|
|
- }
|
|
|
-
|
|
|
- // 定义tabBar页面列表
|
|
|
- const tabBarPages = [
|
|
|
- '/pages/index/index',
|
|
|
- '/pages/order/list',
|
|
|
- '/pages/device/list',
|
|
|
- '/pages/finance/index'
|
|
|
- ]
|
|
|
-
|
|
|
- console.log('导航到:', fullUrl, '是否为TabBar页面:', tabBarPages.includes(fullUrl))
|
|
|
-
|
|
|
+ if (!fullUrl.startsWith('/')) fullUrl = `/${fullUrl}`
|
|
|
+ const tabBarPages = ['/pages/index/index', '/pages/order/list', '/pages/device/list', '/pages/finance/index']
|
|
|
if (tabBarPages.includes(fullUrl)) {
|
|
|
- // 对于tabBar页面,使用switchTab
|
|
|
- uni.switchTab({
|
|
|
- url: fullUrl,
|
|
|
- fail: (err) => {
|
|
|
- console.error('switchTab失败:', err)
|
|
|
- showToast('页面跳转失败')
|
|
|
- }
|
|
|
- })
|
|
|
+ uni.switchTab({ url: fullUrl, fail: (err) => { console.error('switchTab失败:', err); showToast('页面跳转失败') } })
|
|
|
} else {
|
|
|
- // 对于非tabBar页面,使用navigateTo
|
|
|
- uni.navigateTo({
|
|
|
- url: fullUrl,
|
|
|
- fail: (err) => {
|
|
|
- console.error('navigateTo失败:', err)
|
|
|
- showToast('页面跳转失败')
|
|
|
- }
|
|
|
- })
|
|
|
+ uni.navigateTo({ url: fullUrl, fail: (err) => { console.error('navigateTo失败:', err); showToast('页面跳转失败') } })
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// 导航到提现管理页面
|
|
|
-const navigateToWithdraw = () => {
|
|
|
- showUserMenu.value = false
|
|
|
-
|
|
|
- if (currentStation.value) {
|
|
|
- const url = `/pages/finance/withdraw?stationId=${currentStation.value.stationId}&stationName=${currentStation.value.stationName}`
|
|
|
- uni.navigateTo({
|
|
|
- url: url,
|
|
|
- fail: (err) => {
|
|
|
- console.error('navigateTo失败:', err)
|
|
|
- showToast('页面跳转失败')
|
|
|
- }
|
|
|
- })
|
|
|
- } else {
|
|
|
- showToast('请先选择站点')
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 退出登录
|
|
|
const handleLogout = async () => {
|
|
|
- showUserMenu.value = false
|
|
|
-
|
|
|
uni.showModal({
|
|
|
title: '提示',
|
|
|
content: '确定要退出登录吗?',
|
|
|
success: async (res) => {
|
|
|
if (res.confirm) {
|
|
|
- try {
|
|
|
- await logout()
|
|
|
- } catch (error) {
|
|
|
- console.error('登出接口调用失败:', error)
|
|
|
- } finally {
|
|
|
- storage.remove('token')
|
|
|
- storage.remove('userInfo')
|
|
|
- storage.remove('currentStationId')
|
|
|
-
|
|
|
- showToast('已退出登录', 'success')
|
|
|
-
|
|
|
- setTimeout(() => {
|
|
|
- uni.reLaunch({
|
|
|
- url: '/pages/login/login'
|
|
|
- })
|
|
|
- }, 500)
|
|
|
- }
|
|
|
+ try { await logout() } catch (e) { console.error('登出接口调用失败:', e) }
|
|
|
+ storage.remove('token')
|
|
|
+ storage.remove('userInfo')
|
|
|
+ storage.remove('currentStationId')
|
|
|
+ showToast('已退出登录', 'success')
|
|
|
+ setTimeout(() => { uni.reLaunch({ url: '/pages/login/login' }) }, 500)
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
+
|
|
|
+onPullDownRefresh(async () => {
|
|
|
+ await refreshData()
|
|
|
+ uni.stopPullDownRefresh()
|
|
|
+})
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
-/* 全局容器 */
|
|
|
.page-container {
|
|
|
min-height: 100vh;
|
|
|
- background: #F8F9FA;
|
|
|
+ background: #F5F7FA;
|
|
|
padding-bottom: 120rpx;
|
|
|
}
|
|
|
|
|
|
-/* ===== 顶部卡片 ===== */
|
|
|
-.top-card {
|
|
|
- background: linear-gradient(135deg, #667EEA 0%, #764BA2 100%);
|
|
|
- padding: 40rpx 30rpx 30rpx;
|
|
|
+.nav-btn {
|
|
|
+ width: 56rpx;
|
|
|
+ height: 56rpx;
|
|
|
display: flex;
|
|
|
- justify-content: space-between;
|
|
|
align-items: center;
|
|
|
- border-radius: 0 0 40rpx 40rpx;
|
|
|
- box-shadow: 0 4rpx 20rpx rgba(102, 126, 234, 0.3);
|
|
|
+ justify-content: center;
|
|
|
+ border-radius: 50%;
|
|
|
+ transition: background 0.25s;
|
|
|
}
|
|
|
+.nav-btn:active { background: rgba(255, 255, 255, 0.2); }
|
|
|
|
|
|
-.user-section {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 20rpx;
|
|
|
-}
|
|
|
+/* ===== Content ===== */
|
|
|
+.content { padding: 0 28rpx; }
|
|
|
|
|
|
-.user-avatar {
|
|
|
- width: 80rpx;
|
|
|
- height: 80rpx;
|
|
|
- background: rgba(255, 255, 255, 0.2);
|
|
|
- border-radius: 50%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- font-size: 40rpx;
|
|
|
- backdrop-filter: blur(10px);
|
|
|
- border: 2rpx solid rgba(255, 255, 255, 0.3);
|
|
|
+/* ===== Greeting ===== */
|
|
|
+.greeting {
|
|
|
+ display: block;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 30rpx;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #666666;
|
|
|
+ padding: 32rpx 0 32rpx;
|
|
|
}
|
|
|
|
|
|
-.user-info {
|
|
|
+/* ===== Hero ===== */
|
|
|
+.hero-section {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
- gap: 8rpx;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 0 44rpx;
|
|
|
}
|
|
|
-
|
|
|
-.greeting {
|
|
|
- font-size: 32rpx;
|
|
|
- font-weight: 600;
|
|
|
- color: #FFFFFF;
|
|
|
+.hero-value {
|
|
|
+ font-size: 64rpx;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1A1A1A;
|
|
|
+ line-height: 1;
|
|
|
+ letter-spacing: -1px;
|
|
|
}
|
|
|
-
|
|
|
-.station-name {
|
|
|
+.hero-label {
|
|
|
font-size: 24rpx;
|
|
|
- color: rgba(255, 255, 255, 0.85);
|
|
|
+ color: #999999;
|
|
|
+ margin-top: 18rpx;
|
|
|
+ letter-spacing: 1px;
|
|
|
}
|
|
|
|
|
|
-.actions {
|
|
|
+/* ===== Station Card ===== */
|
|
|
+.station-card {
|
|
|
+ background: #FFFFFF;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ padding: 24rpx;
|
|
|
display: flex;
|
|
|
- gap: 15rpx;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10rpx;
|
|
|
+ margin-bottom: 24rpx;
|
|
|
}
|
|
|
-
|
|
|
-.action-btn {
|
|
|
- width: 70rpx;
|
|
|
- height: 70rpx;
|
|
|
- background: rgba(255, 255, 255, 0.2);
|
|
|
- border-radius: 50%;
|
|
|
+.station-card:active { background: #F5F7FA; }
|
|
|
+.station-card-row {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
- backdrop-filter: blur(10px);
|
|
|
- border: 2rpx solid rgba(255, 255, 255, 0.3);
|
|
|
- transition: all 0.3s;
|
|
|
-}
|
|
|
-
|
|
|
-.action-btn:active {
|
|
|
- background: rgba(255, 255, 255, 0.35);
|
|
|
- transform: scale(0.92);
|
|
|
-}
|
|
|
-
|
|
|
-.action-btn .icon {
|
|
|
- font-size: 36rpx;
|
|
|
-}
|
|
|
-
|
|
|
-/* ===== 用户菜单 ===== */
|
|
|
-.user-menu {
|
|
|
- position: fixed;
|
|
|
- top: 140rpx;
|
|
|
- right: 30rpx;
|
|
|
- background: #FFFFFF;
|
|
|
- padding: 24rpx 32rpx;
|
|
|
- border-radius: 16rpx;
|
|
|
- box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.12);
|
|
|
- z-index: 100;
|
|
|
- animation: fadeIn 0.3s;
|
|
|
+ gap: 10rpx;
|
|
|
}
|
|
|
-
|
|
|
-@keyframes fadeIn {
|
|
|
- from { opacity: 0; transform: translateY(-10rpx); }
|
|
|
- to { opacity: 1; transform: translateY(0); }
|
|
|
-}
|
|
|
-
|
|
|
-.menu-text {
|
|
|
+.station-card-name {
|
|
|
font-size: 28rpx;
|
|
|
- color: #667EEA;
|
|
|
- font-weight: 500;
|
|
|
-}
|
|
|
-
|
|
|
-/* ===== 区域样式 ===== */
|
|
|
-.section {
|
|
|
- padding: 30rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.section-title {
|
|
|
- font-size: 32rpx;
|
|
|
font-weight: 600;
|
|
|
color: #1A1A1A;
|
|
|
- margin-bottom: 24rpx;
|
|
|
- padding-left: 20rpx;
|
|
|
- border-left: 6rpx solid #667EEA;
|
|
|
}
|
|
|
-
|
|
|
-/* ===== 数据卡片 ===== */
|
|
|
-.data-cards {
|
|
|
- display: grid;
|
|
|
- grid-template-columns: repeat(2, 1fr);
|
|
|
- gap: 20rpx;
|
|
|
+.station-card-date {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #999999;
|
|
|
}
|
|
|
|
|
|
-.data-card {
|
|
|
+/* ===== Primary Metrics ===== */
|
|
|
+.metrics-primary {
|
|
|
+ display: flex;
|
|
|
+ gap: 16rpx;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+}
|
|
|
+.metric-card-lg {
|
|
|
+ flex: 1;
|
|
|
background: #FFFFFF;
|
|
|
- padding: 40rpx 30rpx;
|
|
|
border-radius: 24rpx;
|
|
|
- position: relative;
|
|
|
- overflow: hidden;
|
|
|
- box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
|
|
- transition: all 0.3s;
|
|
|
+ padding: 32rpx 20rpx;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12rpx;
|
|
|
+ transition: box-shadow 0.25s cubic-bezier(0.4, 0, 0.2, 1), transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
}
|
|
|
-
|
|
|
-.data-card:active {
|
|
|
- transform: translateY(-4rpx);
|
|
|
- box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
|
|
|
+.metric-card-lg:active {
|
|
|
+ transform: translateY(-2rpx);
|
|
|
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
|
}
|
|
|
-
|
|
|
-.data-card.purple { background: linear-gradient(135deg, #667EEA 0%, #764BA2 100%); }
|
|
|
-.data-card.pink { background: linear-gradient(135deg, #F093FB 0%, #F5576C 100%); }
|
|
|
-.data-card.blue { background: linear-gradient(135deg, #4FACFE 0%, #00F2FE 100%); }
|
|
|
-.data-card.green { background: linear-gradient(135deg, #43E97B 0%, #38F9D7 100%); }
|
|
|
-
|
|
|
-.data-value {
|
|
|
- display: block;
|
|
|
- font-size: 48rpx;
|
|
|
+.metric-lg-value {
|
|
|
+ font-size: 44rpx;
|
|
|
font-weight: 700;
|
|
|
- color: #FFFFFF;
|
|
|
- margin-bottom: 8rpx;
|
|
|
+ color: #1A1A1A;
|
|
|
+ line-height: 1;
|
|
|
}
|
|
|
-
|
|
|
-.data-label {
|
|
|
- display: block;
|
|
|
+.metric-lg-label {
|
|
|
font-size: 24rpx;
|
|
|
- color: rgba(255, 255, 255, 0.9);
|
|
|
- font-weight: 500;
|
|
|
+ color: #999999;
|
|
|
}
|
|
|
|
|
|
-.card-icon {
|
|
|
- position: absolute;
|
|
|
- right: 20rpx;
|
|
|
- bottom: 20rpx;
|
|
|
- font-size: 56rpx;
|
|
|
- opacity: 0.3;
|
|
|
+/* ===== Secondary Metrics ===== */
|
|
|
+.metrics-secondary {
|
|
|
+ display: flex;
|
|
|
+ gap: 16rpx;
|
|
|
+ margin-bottom: 40rpx;
|
|
|
}
|
|
|
-
|
|
|
-/* ===== 统计列表 ===== */
|
|
|
-.stat-list {
|
|
|
+.metric-card-sm {
|
|
|
+ flex: 1;
|
|
|
background: #FFFFFF;
|
|
|
- border-radius: 24rpx;
|
|
|
- overflow: hidden;
|
|
|
- box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
|
|
-}
|
|
|
-
|
|
|
-.stat-item {
|
|
|
+ border-radius: 20rpx;
|
|
|
+ padding: 28rpx 20rpx;
|
|
|
display: flex;
|
|
|
+ flex-direction: column;
|
|
|
align-items: center;
|
|
|
- padding: 32rpx 30rpx;
|
|
|
- border-bottom: 1rpx solid #F0F0F0;
|
|
|
- transition: background 0.3s;
|
|
|
+ gap: 10rpx;
|
|
|
+ transition: box-shadow 0.25s cubic-bezier(0.4, 0, 0.2, 1), transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
}
|
|
|
-
|
|
|
-.stat-item:last-child {
|
|
|
- border-bottom: none;
|
|
|
+.metric-card-sm:active {
|
|
|
+ transform: translateY(-2rpx);
|
|
|
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
|
}
|
|
|
-
|
|
|
-.stat-item:active {
|
|
|
- background: #F8F9FA;
|
|
|
-}
|
|
|
-
|
|
|
-.stat-icon {
|
|
|
- width: 80rpx;
|
|
|
- height: 80rpx;
|
|
|
- border-radius: 20rpx;
|
|
|
+.metric-sm-icon {
|
|
|
+ width: 56rpx;
|
|
|
+ height: 56rpx;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ background: #F5F7FA;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
- font-size: 40rpx;
|
|
|
- margin-right: 24rpx;
|
|
|
+}
|
|
|
+.metric-sm-value {
|
|
|
+ font-size: 30rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1A1A1A;
|
|
|
+ line-height: 1.2;
|
|
|
+}
|
|
|
+.metric-sm-label {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #999999;
|
|
|
}
|
|
|
|
|
|
-.stat-icon.red { background: #FFE8E8; }
|
|
|
-.stat-icon.blue { background: #E6F7FF; }
|
|
|
-.stat-icon.purple { background: #F0F5FF; }
|
|
|
-
|
|
|
-.stat-info {
|
|
|
- flex: 1;
|
|
|
+/* ===== Section Header ===== */
|
|
|
+.section-header {
|
|
|
display: flex;
|
|
|
- justify-content: space-between;
|
|
|
align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 12rpx;
|
|
|
+ padding: 0 0 20rpx;
|
|
|
}
|
|
|
-
|
|
|
-.stat-label {
|
|
|
- font-size: 28rpx;
|
|
|
- color: #666666;
|
|
|
+.section-dot {
|
|
|
+ width: 10rpx;
|
|
|
+ height: 10rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #C6171E;
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
-
|
|
|
-.stat-value {
|
|
|
- font-size: 32rpx;
|
|
|
+.section-title {
|
|
|
+ font-size: 28rpx;
|
|
|
font-weight: 600;
|
|
|
color: #1A1A1A;
|
|
|
}
|
|
|
|
|
|
-/* ===== 快捷操作 ===== */
|
|
|
-.action-grid {
|
|
|
- display: grid;
|
|
|
- grid-template-columns: repeat(2, 1fr);
|
|
|
- gap: 20rpx;
|
|
|
+/* ===== Quick Actions ===== */
|
|
|
+.actions-row {
|
|
|
+ display: flex;
|
|
|
+ gap: 16rpx;
|
|
|
+ margin-bottom: 28rpx;
|
|
|
}
|
|
|
-
|
|
|
.action-item {
|
|
|
+ flex: 1;
|
|
|
background: #FFFFFF;
|
|
|
- padding: 50rpx 30rpx;
|
|
|
border-radius: 24rpx;
|
|
|
+ padding: 32rpx 12rpx 24rpx;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
- gap: 20rpx;
|
|
|
- box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
|
|
- position: relative;
|
|
|
- overflow: hidden;
|
|
|
- transition: all 0.3s;
|
|
|
-}
|
|
|
-
|
|
|
-.action-item:active {
|
|
|
- transform: translateY(-4rpx);
|
|
|
- box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
|
|
|
+ gap: 18rpx;
|
|
|
}
|
|
|
-
|
|
|
-.action-item::before {
|
|
|
- content: '';
|
|
|
- position: absolute;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- height: 6rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.action-item.purple::before { background: linear-gradient(90deg, #667EEA, #764BA2); }
|
|
|
-.action-item.pink::before { background: linear-gradient(90deg, #F093FB, #F5576C); }
|
|
|
-.action-item.blue::before { background: linear-gradient(90deg, #4FACFE, #00F2FE); }
|
|
|
-.action-item.green::before { background: linear-gradient(90deg, #43E97B, #38F9D7); }
|
|
|
-.action-item.orange::before { background: linear-gradient(90deg, #FF9A9E, #FAD0C4); }
|
|
|
-
|
|
|
-.action-icon {
|
|
|
- width: 96rpx;
|
|
|
- height: 96rpx;
|
|
|
- border-radius: 24rpx;
|
|
|
+.action-icon-wrap {
|
|
|
+ width: 88rpx;
|
|
|
+ height: 88rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #C6171E;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
- font-size: 56rpx;
|
|
|
- background: #F8F9FA;
|
|
|
}
|
|
|
-
|
|
|
+.action-item:active .action-icon-wrap {
|
|
|
+ background: #A81212;
|
|
|
+ transform: scale(0.94);
|
|
|
+}
|
|
|
.action-label {
|
|
|
- font-size: 28rpx;
|
|
|
+ font-size: 26rpx;
|
|
|
font-weight: 500;
|
|
|
color: #1A1A1A;
|
|
|
}
|
|
|
|
|
|
-/* ===== 加载指示器 ===== */
|
|
|
-.loading {
|
|
|
- position: fixed;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- bottom: 0;
|
|
|
- background: rgba(0, 0, 0, 0.3);
|
|
|
+/* ===== Info Bar ===== */
|
|
|
+.info-bar {
|
|
|
display: flex;
|
|
|
- align-items: center;
|
|
|
justify-content: center;
|
|
|
- z-index: 999;
|
|
|
+ gap: 40rpx;
|
|
|
+ padding: 16rpx 0 40rpx;
|
|
|
}
|
|
|
-
|
|
|
-.spinner {
|
|
|
- width: 80rpx;
|
|
|
- height: 80rpx;
|
|
|
- border: 6rpx solid rgba(255, 255, 255, 0.3);
|
|
|
- border-top-color: #FFFFFF;
|
|
|
- border-radius: 50%;
|
|
|
- animation: spin 0.8s linear infinite;
|
|
|
+.info-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8rpx;
|
|
|
+}
|
|
|
+.info-text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #999999;
|
|
|
+}
|
|
|
+.info-num {
|
|
|
+ font-weight: 600;
|
|
|
+ color: #666666;
|
|
|
}
|
|
|
|
|
|
-@keyframes spin {
|
|
|
- to { transform: rotate(360deg); }
|
|
|
+/* ===== Skeleton ===== */
|
|
|
+.skeleton-greeting {
|
|
|
+ width: 180rpx;
|
|
|
+ height: 30rpx;
|
|
|
+ background: #E8E8E8;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ margin: 32rpx auto;
|
|
|
}
|
|
|
+.skeleton-hero {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 0 44rpx;
|
|
|
+}
|
|
|
+.skeleton-line {
|
|
|
+ background: #E8E8E8;
|
|
|
+ border-radius: 8rpx;
|
|
|
+}
|
|
|
+.skeleton-line.hero-num {
|
|
|
+ width: 360rpx;
|
|
|
+ height: 64rpx;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+}
|
|
|
+.skeleton-line.hero-sub {
|
|
|
+ width: 160rpx;
|
|
|
+ height: 26rpx;
|
|
|
+}
|
|
|
+.skeleton-line.station-sk {
|
|
|
+ width: 100%;
|
|
|
+ height: 90rpx;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ margin-bottom: 24rpx;
|
|
|
+}
|
|
|
+.skeleton-line.section-title {
|
|
|
+ width: 160rpx;
|
|
|
+ height: 28rpx;
|
|
|
+ margin: 0 auto 20rpx;
|
|
|
+}
|
|
|
+.skeleton-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ gap: 16rpx;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+}
|
|
|
+.skeleton-cell {
|
|
|
+ height: 120rpx;
|
|
|
+ background: #E8E8E8;
|
|
|
+ border-radius: 24rpx;
|
|
|
+}
|
|
|
+.skeleton-cell.sm {
|
|
|
+ height: 100rpx;
|
|
|
+ border-radius: 20rpx;
|
|
|
+}
|
|
|
+.skeleton-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 16rpx;
|
|
|
+ margin-bottom: 28rpx;
|
|
|
+}
|
|
|
+.skeleton-action {
|
|
|
+ flex: 1;
|
|
|
+ height: 170rpx;
|
|
|
+ background: #E8E8E8;
|
|
|
+ border-radius: 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== Error ===== */
|
|
|
+.error-state {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ padding: 120rpx 0 0;
|
|
|
+}
|
|
|
+.error-text { font-size: 28rpx; color: #999999; margin-top: 24rpx; }
|
|
|
+.error-hint { font-size: 24rpx; color: #B0B0B0; margin-top: 8rpx; }
|
|
|
+.retry-btn {
|
|
|
+ margin-top: 40rpx;
|
|
|
+ background: #C6171E;
|
|
|
+ padding: 16rpx 48rpx;
|
|
|
+ border-radius: 44rpx;
|
|
|
+}
|
|
|
+.retry-btn:active { background: #A81212; transform: scale(0.97); }
|
|
|
+.retry-btn-text { font-size: 28rpx; color: #FFFFFF; font-weight: 500; }
|
|
|
</style>
|