Vue 3 与 Battery Status API

1. 概述

Battery Status API(也称为 Battery API)是一个现代浏览器 API,用于获取设备的电池状态信息,如电池电量、充电状态、充电时间和放电时间等。它允许 Web 应用根据设备的电池状态调整其行为,提供更好的用户体验和电池寿命优化。

在 Vue 3 应用中,Battery Status API 可以用于:

  • 根据电池电量调整应用功能和性能
  • 优化电池使用,延长设备续航时间
  • 提供电池状态反馈给用户
  • 实现智能的电源管理策略
  • 在低电量时减少资源消耗

2. 核心知识

2.1 Battery Status API 基础

Battery Status API 提供了以下核心属性和事件:

属性/事件 描述
level 电池电量,范围从 0.0(空)到 1.0(满)
charging 指示设备是否正在充电
chargingTime 电池充满所需的时间(秒),如果不在充电则为 0
dischargingTime 电池耗尽所需的时间(秒),如果正在充电则为 Infinity
chargingchange 事件 当充电状态发生变化时触发
levelchange 事件 当电池电量发生变化时触发
chargingtimechange 事件 当充电时间发生变化时触发
dischargingtimechange 事件 当放电时间发生变化时触发

2.2 获取 Battery 对象

// 获取电池状态对象
navigator.getBattery()
  .then(battery => {
    // 电池信息 API 可用
    console.log('电池电量:', battery.level * 100, '%');
    console.log('是否充电:', battery.charging);
    console.log('充满所需时间:', battery.chargingTime, '秒');
    console.log('放电剩余时间:', battery.dischargingTime, '秒');
  })
  .catch(error => {
    console.log('Battery Status API 不可用:', error);
  });

2.3 监听电池状态变化

navigator.getBattery()
  .then(battery => {
    // 监听充电状态变化
    battery.addEventListener('chargingchange', () => {
      console.log('充电状态已变化:', battery.charging ? '正在充电' : '未充电');
    });
    
    // 监听电池电量变化
    battery.addEventListener('levelchange', () => {
      console.log('电池电量已变化:', battery.level * 100, '%');
    });
    
    // 监听充电时间变化
    battery.addEventListener('chargingtimechange', () => {
      console.log('充满所需时间已变化:', battery.chargingTime, '秒');
    });
    
    // 监听放电时间变化
    battery.addEventListener('dischargingtimechange', () => {
      console.log('放电剩余时间已变化:', battery.dischargingTime, '秒');
    });
  });

3. Vue 3 与 Battery Status API 集成

3.1 基础使用示例

<template>
  <div>
    <h2>Battery Status API 示例</h2>
    <div class="battery-container" :class="batteryClass">
      <h3>当前电池状态</h3>
      <div v-if="batteryInfo.isSupported">
        <div class="battery-status">
          <div class="battery-level">
            <div class="level-bar" :style="{ width: batteryInfo.level * 100 + '%' }"></div>
            <span class="level-text">{{ Math.round(batteryInfo.level * 100) }}%</span>
          </div>
          <div class="battery-details">
            <div class="detail-item">
              <span class="label">充电状态:</span>
              <span class="value">{{ batteryInfo.charging ? '正在充电' : '未充电' }}</span>
            </div>
            <div class="detail-item" v-if="batteryInfo.charging">
              <span class="label">充满所需时间:</span>
              <span class="value">{{ formatTime(batteryInfo.chargingTime) }}</span>
            </div>
            <div class="detail-item" v-else>
              <span class="label">放电剩余时间:</span>
              <span class="value">{{ formatTime(batteryInfo.dischargingTime) }}</span>
            </div>
          </div>
        </div>
      </div>
      <div v-else class="not-supported">
        <p>Battery Status API 不可用</p>
      </div>
    </div>
    <div class="power-usage">
      <h3>电源使用建议</h3>
      <div class="suggestions">
        <div v-if="batteryInfo.level < 0.2 && !batteryInfo.charging" class="warning-suggestion">
          ⚠️ 电池电量低于 20%,建议开启省电模式
        </div>
        <div v-if="batteryInfo.charging" class="info-suggestion">
          🔌 设备正在充电,可以正常使用所有功能
        </div>
        <div v-if="batteryInfo.level > 0.8 && batteryInfo.charging" class="success-suggestion">
          ✅ 电池电量充足,建议移除充电器以延长电池寿命
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue';

const batteryInfo = ref({
  isSupported: false,
  level: 1.0,
  charging: false,
  chargingTime: 0,
  dischargingTime: Infinity
});

let battery = null;

// 格式化时间(秒 -> 小时:分钟:秒)
const formatTime = (seconds) => {
  if (seconds === Infinity || seconds === 0) {
    return 'N/A';
  }
  
  const hours = Math.floor(seconds / 3600);
  const minutes = Math.floor((seconds % 3600) / 60);
  const secs = Math.floor(seconds % 60);
  
  if (hours > 0) {
    return `${hours}h ${minutes}m`;
  } else if (minutes > 0) {
    return `${minutes}m ${secs}s`;
  } else {
    return `${secs}s`;
  }
};

// 更新电池信息
const updateBatteryInfo = (batteryObj) => {
  batteryInfo.value = {
    isSupported: true,
    level: batteryObj.level,
    charging: batteryObj.charging,
    chargingTime: batteryObj.chargingTime,
    dischargingTime: batteryObj.dischargingTime
  };
};

// 电池状态变化的事件监听器
const handleChargingChange = () => updateBatteryInfo(battery);
const handleLevelChange = () => updateBatteryInfo(battery);
const handleChargingTimeChange = () => updateBatteryInfo(battery);
const handleDischargingTimeChange = () => updateBatteryInfo(battery);

// 计算电池状态的 CSS 类
const batteryClass = computed(() => {
  if (!batteryInfo.value.isSupported) return 'not-supported';
  
  const level = batteryInfo.value.level;
  if (level > 0.8) return 'battery-high';
  if (level > 0.3) return 'battery-medium';
  return 'battery-low';
});

// 组件挂载时初始化
onMounted(() => {
  // 获取电池信息对象
  navigator.getBattery()
    .then(batteryObj => {
      battery = batteryObj;
      
      // 更新初始电池信息
      updateBatteryInfo(battery);
      
      // 添加事件监听器
      battery.addEventListener('chargingchange', handleChargingChange);
      battery.addEventListener('levelchange', handleLevelChange);
      battery.addEventListener('chargingtimechange', handleChargingTimeChange);
      battery.addEventListener('dischargingtimechange', handleDischargingTimeChange);
    })
    .catch(error => {
      console.log('Battery Status API 不可用:', error);
    });
});

// 组件卸载时清理
onUnmounted(() => {
  if (battery) {
    battery.removeEventListener('chargingchange', handleChargingChange);
    battery.removeEventListener('levelchange', handleLevelChange);
    battery.removeEventListener('chargingtimechange', handleChargingTimeChange);
    battery.removeEventListener('dischargingtimechange', handleDischargingTimeChange);
    battery = null;
  }
});
</script>

<style scoped>
.battery-container {
  margin: 20px 0;
  padding: 20px;
  border-radius: 8px;
  background-color: #f0f8ff;
  border: 1px solid #add8e6;
  transition: all 0.3s ease;
}

.battery-high {
  background-color: #f0fff0;
  border-color: #ccffcc;
}

.battery-medium {
  background-color: #fff8e1;
  border-color: #ffe0b2;
}

.battery-low {
  background-color: #fff0f0;
  border-color: #ffcccc;
}

.not-supported {
  background-color: #f5f5f5;
  border-color: #e0e0e0;
  text-align: center;
  padding: 20px;
}

.battery-status {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.battery-level {
  position: relative;
  height: 40px;
  background-color: #e0e0e0;
  border-radius: 8px;
  overflow: hidden;
}

.level-bar {
  height: 100%;
  background-color: #4caf50;
  transition: width 0.5s ease;
}

.battery-low .level-bar {
  background-color: #f44336;
}

.battery-medium .level-bar {
  background-color: #ff9800;
}

.level-text {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-weight: bold;
  color: white;
  text-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
}

.battery-details {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.detail-item {
  display: flex;
  justify-content: space-between;
  padding: 5px 0;
  border-bottom: 1px solid #eee;
}

.detail-item:last-child {
  border-bottom: none;
}

.label {
  font-weight: bold;
  color: #666;
}

.value {
  font-weight: bold;
  color: #333;
}

.power-usage {
  margin-top: 30px;
  padding: 20px;
  background-color: #f8f9fa;
  border-radius: 8px;
  border: 1px solid #e9ecef;
}

.suggestions {
  margin-top: 15px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.warning-suggestion {
  padding: 10px;
  background-color: #fff3cd;
  border: 1px solid #ffeeba;
  border-radius: 4px;
  color: #856404;
  font-weight: bold;
}

.info-suggestion {
  padding: 10px;
  background-color: #d1ecf1;
  border: 1px solid #bee5eb;
  border-radius: 4px;
  color: #0c5460;
}

.success-suggestion {
  padding: 10px;
  background-color: #d4edda;
  border: 1px solid #c3e6cb;
  border-radius: 4px;
  color: #155724;
}
</style>

3.2 创建可复用的 Battery Status 组合式函数

// src/composables/useBatteryStatus.js
import { ref, onMounted, onUnmounted } from 'vue';

export function useBatteryStatus() {
  const batteryInfo = ref({
    isSupported: false,
    level: 1.0,
    charging: false,
    chargingTime: 0,
    dischargingTime: Infinity
  });

  let battery = null;
  let eventListeners = [];

  // 更新电池信息
  const updateBatteryInfo = (batteryObj) => {
    batteryInfo.value = {
      isSupported: true,
      level: batteryObj.level,
      charging: batteryObj.charging,
      chargingTime: batteryObj.chargingTime,
      dischargingTime: batteryObj.dischargingTime
    };
  };

  // 初始化电池信息
  const initBatteryInfo = async () => {
    try {
      battery = await navigator.getBattery();
      
      // 更新初始电池信息
      updateBatteryInfo(battery);
      
      // 添加事件监听器
      const handlers = {
        chargingchange: () => updateBatteryInfo(battery),
        levelchange: () => updateBatteryInfo(battery),
        chargingtimechange: () => updateBatteryInfo(battery),
        dischargingtimechange: () => updateBatteryInfo(battery)
      };
      
      // 保存事件监听器引用
      eventListeners = Object.entries(handlers).map(([event, handler]) => {
        battery.addEventListener(event, handler);
        return { event, handler };
      });
    } catch (error) {
      console.log('Battery Status API 不可用:', error);
      batteryInfo.value.isSupported = false;
    }
  };

  // 清理资源
  const cleanup = () => {
    if (battery && eventListeners.length > 0) {
      // 移除所有事件监听器
      eventListeners.forEach(({ event, handler }) => {
        battery.removeEventListener(event, handler);
      });
      eventListeners = [];
      battery = null;
    }
  };

  // 组件挂载时初始化
  onMounted(() => {
    initBatteryInfo();
  });

  // 组件卸载时清理
  onUnmounted(() => {
    cleanup();
  });

  return {
    batteryInfo
  };
}

3.3 使用组合式函数的示例

<template>
  <div>
    <h2>使用 useBatteryStatus 组合式函数</h2>
    
    <!-- 电池状态卡片 -->
    <div class="battery-card" :class="batteryClass">
      <div class="battery-header">
        <h3>电池状态</h3>
        <div class="battery-icon" :class="chargingClass">
          {{ chargingIcon }}
        </div>
      </div>
      
      <div v-if="batteryInfo.isSupported" class="battery-content">
        <!-- 电池电量指示器 -->
        <div class="battery-indicator">
          <div class="indicator-bar" :style="{ width: batteryPercentage + '%' }"></div>
          <span class="indicator-text">{{ batteryPercentage }}%</span>
        </div>
        
        <!-- 电池详情 -->
        <div class="battery-details">
          <div class="detail-item">
            <span class="detail-label">充电状态:</span>
            <span class="detail-value">{{ batteryInfo.charging ? '正在充电' : '未充电' }}</span>
          </div>
          <div class="detail-item">
            <span class="detail-label">充满时间:</span>
            <span class="detail-value">{{ formatTime(batteryInfo.chargingTime) }}</span>
          </div>
          <div class="detail-item">
            <span class="detail-label">剩余时间:</span>
            <span class="detail-value">{{ formatTime(batteryInfo.dischargingTime) }}</span>
          </div>
        </div>
      </div>
      
      <div v-else class="not-supported">
        <p>Battery Status API 不可用</p>
      </div>
    </div>
    
    <!-- 电源管理设置 -->
    <div class="power-settings">
      <h3>电源管理设置</h3>
      <div class="settings-section">
        <h4>根据电池状态自动调整</h4>
        <div class="setting-item">
          <label class="setting-label">
            <input type="checkbox" v-model="autoAdjustSettings" />
            启用自动电源管理
          </label>
        </div>
        
        <div v-if="autoAdjustSettings" class="auto-settings">
          <div class="setting-item">
            <label class="setting-label">
              <input type="checkbox" v-model="reduceAnimation" />
              低电量时减少动画效果
            </label>
          </div>
          <div class="setting-item">
            <label class="setting-label">
              <input type="checkbox" v-model="reduceUpdates" />
              低电量时减少数据更新频率
            </label>
          </div>
          <div class="setting-item">
            <label class="setting-label">
              <input type="checkbox" v-model="disableBackgroundTasks" />
              低电量时禁用后台任务
            </label>
          </div>
        </div>
      </div>
      
      <!-- 当前电源模式 -->
      <div class="power-mode">
        <h4>当前电源模式</h4>
        <div class="mode-badge" :class="currentModeClass">
          {{ currentPowerMode }}
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';
import { useBatteryStatus } from './composables/useBatteryStatus';

// 使用电池状态组合式函数
const { batteryInfo } = useBatteryStatus();

// 电源管理设置
const autoAdjustSettings = ref(true);
const reduceAnimation = ref(true);
const reduceUpdates = ref(true);
const disableBackgroundTasks = ref(true);

// 计算电池百分比
const batteryPercentage = computed(() => {
  return Math.round(batteryInfo.value.level * 100);
});

// 计算充电状态的图标和类
const chargingIcon = computed(() => {
  return batteryInfo.value.charging ? '🔌' : '🔋';
});

const chargingClass = computed(() => {
  return batteryInfo.value.charging ? 'charging' : 'discharging';
});

// 计算电池状态的 CSS 类
const batteryClass = computed(() => {
  if (!batteryInfo.value.isSupported) return 'not-supported';
  
  const level = batteryInfo.value.level;
  if (level > 0.8) return 'battery-high';
  if (level > 0.3) return 'battery-medium';
  return 'battery-low';
});

// 格式化时间
const formatTime = (seconds) => {
  if (seconds === Infinity || seconds === 0) {
    return 'N/A';
  }
  
  const hours = Math.floor(seconds / 3600);
  const minutes = Math.floor((seconds % 3600) / 60);
  
  if (hours > 0) {
    return `${hours}h ${minutes}m`;
  } else {
    return `${minutes}m`;
  }
};

// 计算当前电源模式
const currentPowerMode = computed(() => {
  if (!batteryInfo.value.isSupported) return '标准模式';
  
  const level = batteryInfo.value.level;
  const charging = batteryInfo.value.charging;
  
  if (charging) {
    return '高性能模式';
  } else if (level < 0.2) {
    return '超省电模式';
  } else if (level < 0.5) {
    return '省电模式';
  } else {
    return '标准模式';
  }
});

// 计算当前模式的 CSS 类
const currentModeClass = computed(() => {
  const mode = currentPowerMode.value;
  switch (mode) {
    case '高性能模式': return 'mode-high';
    case '超省电模式': return 'mode-extreme';
    case '省电模式': return 'mode-low';
    default: return 'mode-standard';
  }
});
</script>

<style scoped>
.battery-card {
  margin: 20px 0;
  padding: 20px;
  border-radius: 8px;
  background-color: #f8f9fa;
  border: 1px solid #e9ecef;
  transition: all 0.3s ease;
}

.battery-high {
  background-color: #d4edda;
  border-color: #c3e6cb;
}

.battery-medium {
  background-color: #fff3cd;
  border-color: #ffeeba;
}

.battery-low {
  background-color: #f8d7da;
  border-color: #f5c6cb;
}

.not-supported {
  background-color: #e9ecef;
  border-color: #dee2e6;
  text-align: center;
  padding: 20px;
}

.battery-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.battery-icon {
  font-size: 2em;
  transition: all 0.3s ease;
}

.battery-icon.charging {
  animation: pulse 1.5s infinite;
}

@keyframes pulse {
  0% { opacity: 1; }
  50% { opacity: 0.5; }
  100% { opacity: 1; }
}

.battery-content {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.battery-indicator {
  position: relative;
  height: 50px;
  background-color: #e0e0e0;
  border-radius: 8px;
  overflow: hidden;
}

.indicator-bar {
  height: 100%;
  background-color: #4caf50;
  transition: width 0.5s ease;
}

.battery-low .indicator-bar {
  background-color: #f44336;
}

.battery-medium .indicator-bar {
  background-color: #ff9800;
}

.indicator-text {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-weight: bold;
  color: white;
  text-shadow: 0 0 3px rgba(0, 0, 0, 0.7);
  font-size: 1.2em;
}

.battery-details {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.detail-item {
  display: flex;
  justify-content: space-between;
  padding: 8px 0;
  border-bottom: 1px solid #e9ecef;
}

.detail-item:last-child {
  border-bottom: none;
}

.detail-label {
  color: #6c757d;
  font-weight: 500;
}

.detail-value {
  font-weight: bold;
  color: #495057;
}

.power-settings {
  margin: 20px 0;
  padding: 20px;
  background-color: #f8f9fa;
  border: 1px solid #e9ecef;
  border-radius: 8px;
}

.settings-section {
  margin: 20px 0;
}

.setting-item {
  margin: 10px 0;
}

.setting-label {
  display: flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  user-select: none;
}

.auto-settings {
  margin: 15px 0 0 25px;
  padding: 15px;
  background-color: #e9ecef;
  border-radius: 4px;
}

.power-mode {
  margin-top: 20px;
}

.mode-badge {
  display: inline-block;
  padding: 8px 16px;
  border-radius: 20px;
  font-weight: bold;
  margin-top: 10px;
}

.mode-high {
  background-color: #d4edda;
  color: #155724;
}

.mode-standard {
  background-color: #d1ecf1;
  color: #0c5460;
}

.mode-low {
  background-color: #fff3cd;
  color: #856404;
}

.mode-extreme {
  background-color: #f8d7da;
  color: #721c24;
}
</style>

3.4 结合其他 API 的高级示例

<template>
  <div>
    <h2>Battery Status API 高级应用</h2>
    
    <!-- 电池状态仪表板 -->
    <div class="dashboard">
      <div class="dashboard-card battery-card" :class="batteryClass">
        <h3>电池状态</h3>
        <div class="battery-info">
          <div class="battery-level">
            <div class="level-circle" :style="{ '--battery-level': batteryInfo.level * 100 + '%' }"></div>
            <span class="level-percentage">{{ Math.round(batteryInfo.level * 100) }}%</span>
          </div>
          <div class="battery-details">
            <div class="detail-item">
              <span class="detail-label">充电:</span>
              <span class="detail-value">{{ batteryInfo.charging ? '是' : '否' }}</span>
            </div>
            <div class="detail-item">
              <span class="detail-label">模式:</span>
              <span class="detail-value">{{ currentPowerMode }}</span>
            </div>
          </div>
        </div>
      </div>
      
      <div class="dashboard-card power-usage-card">
        <h3>电源使用统计</h3>
        <div class="usage-stats">
          <div class="stat-item">
            <span class="stat-label">屏幕亮度:</span>
            <div class="stat-value">
              <input 
                type="range" 
                min="10" 
                max="100" 
                v-model.number="screenBrightness" 
                @input="updateScreenBrightness"
              />
              <span>{{ screenBrightness }}%</span>
            </div>
          </div>
          <div class="stat-item">
            <span class="stat-label">动画效果:</span>
            <div class="stat-value">
              <button @click="toggleAnimations" :class="{ active: animationsEnabled }">
                {{ animationsEnabled ? '启用' : '禁用' }}
              </button>
            </div>
          </div>
          <div class="stat-item">
            <span class="stat-label">后台更新:</span>
            <div class="stat-value">
              <button @click="toggleBackgroundUpdates" :class="{ active: backgroundUpdatesEnabled }">
                {{ backgroundUpdatesEnabled ? '启用' : '禁用' }}
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
    
    <!-- 智能电源管理建议 -->
    <div class="suggestions-section">
      <h3>智能电源管理建议</h3>
      <div class="suggestions-list">
        <div v-for="(suggestion, index) in suggestions" :key="index" class="suggestion-item" :class="suggestion.type">
          <span class="suggestion-icon">{{ suggestion.icon }}</span>
          <div class="suggestion-content">
            <h4>{{ suggestion.title }}</h4>
            <p>{{ suggestion.description }}</p>
            <button v-if="suggestion.action" @click="suggestion.action" class="suggestion-action">
              {{ suggestion.actionText }}
            </button>
          </div>
        </div>
      </div>
    </div>
    
    <!-- 电池历史记录 -->
    <div class="history-section">
      <h3>电池历史记录</h3>
      <div class="history-chart">
        <div class="chart-container">
          <div class="chart-line" :style="{ height: '100%', width: '100%' }">
            <!-- 简化的历史图表 -->
            <div class="history-points">
              <div 
                v-for="(point, index) in historyPoints" 
                :key="index"
                class="history-point"
                :style="{ 
                  left: index * (100 / (historyPoints.length - 1)) + '%',
                  bottom: point * 100 + '%'
                }"
                :title="`${Math.round(point * 100)}%`"
              ></div>
            </div>
          </div>
        </div>
        <div class="chart-legend">
          <div class="legend-item">
            <span class="legend-color" style="background-color: #4caf50;"></span>
            <span>电池电量历史</span>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue';
import { useBatteryStatus } from './composables/useBatteryStatus';

// 使用电池状态组合式函数
const { batteryInfo } = useBatteryStatus();

// 电源管理设置
const screenBrightness = ref(70);
const animationsEnabled = ref(true);
const backgroundUpdatesEnabled = ref(true);

// 电池历史记录(模拟数据)
const historyPoints = ref([0.9, 0.85, 0.8, 0.75, 0.7, 0.65, 0.6, 0.55, 0.5, 0.45, 0.4, 0.35, 0.3, 0.25, 0.2]);

// 更新电池历史记录
const updateHistory = () => {
  historyPoints.value.push(batteryInfo.value.level);
  if (historyPoints.value.length > 15) {
    historyPoints.value.shift();
  }
};

// 计算电池状态的 CSS 类
const batteryClass = computed(() => {
  if (!batteryInfo.value.isSupported) return 'not-supported';
  
  const level = batteryInfo.value.level;
  if (level > 0.8) return 'battery-high';
  if (level > 0.3) return 'battery-medium';
  return 'battery-low';
});

// 当前电源模式
const currentPowerMode = computed(() => {
  if (!batteryInfo.value.isSupported) return '未知';
  
  const level = batteryInfo.value.level;
  if (level > 0.8) return '高性能';
  if (level > 0.5) return '平衡';
  if (level > 0.3) return '省电';
  return '超省电';
});

// 生成智能建议
const suggestions = computed(() => {
  const list = [];
  
  if (!batteryInfo.value.isSupported) {
    list.push({
      type: 'info',
      icon: 'ℹ️',
      title: '电池信息不可用',
description: '您的浏览器不支持 Battery Status API,无法提供智能电源管理建议。'
    });
    return list;
  }
  
  const level = batteryInfo.value.level;
  const charging = batteryInfo.value.charging;
  
  // 低电量建议
  if (level < 0.2 && !charging) {
    list.push({
      type: 'warning',
      icon: '⚠️',
      title: '电池电量低',
description: '您的电池电量低于 20%,建议开启超省电模式以延长续航时间。',
      action: () => enableExtremePowerSaving(),
      actionText: '开启超省电模式'
    });
  }
  
  // 充电建议
  if (level > 0.9 && charging) {
    list.push({
      type: 'info',
      icon: '🔌',
      title: '电池即将充满',
description: '您的电池电量已超过 90%,建议移除充电器以延长电池寿命。',
      action: () => {
        alert('请手动移除充电器');
      },
      actionText: '了解更多'
    });
  }
  
  // 电源使用建议
  if (animationsEnabled.value && level < 0.3) {
    list.push({
      type: 'suggestion',
      icon: '💡',
      title: '减少动画效果',
description: '当前电池电量较低,建议禁用动画效果以节省电量。',
      action: () => {
        animationsEnabled.value = false;
        applyPowerSavingSettings();
      },
      actionText: '禁用动画'
    });
  }
  
  if (backgroundUpdatesEnabled.value && level < 0.4) {
    list.push({
      type: 'suggestion',
      icon: '🔄',
      title: '减少后台更新',
description: '当前电池电量较低,建议减少后台数据更新频率以节省电量。',
      action: () => {
        backgroundUpdatesEnabled.value = false;
        applyPowerSavingSettings();
      },
      actionText: '减少更新'
    });
  }
  
  // 高性能建议
  if (level > 0.7 && !charging && !animationsEnabled.value) {
    list.push({
      type: 'suggestion',
      icon: '🚀',
      title: '恢复高性能模式',
description: '您的电池电量充足,可以恢复高性能模式以获得更好的用户体验。',
      action: () => {
        animationsEnabled.value = true;
        backgroundUpdatesEnabled.value = true;
        applyPowerSavingSettings();
      },
      actionText: '恢复高性能'
    });
  }
  
  return list;
});

// 应用电源节省设置
const applyPowerSavingSettings = () => {
  // 这里可以添加实际的电源节省逻辑
  console.log('应用电源节省设置:', {
    animationsEnabled: animationsEnabled.value,
    backgroundUpdatesEnabled: backgroundUpdatesEnabled.value,
    screenBrightness: screenBrightness.value
  });
};

// 启用超省电模式
const enableExtremePowerSaving = () => {
  animationsEnabled.value = false;
  backgroundUpdatesEnabled.value = false;
  screenBrightness.value = 30;
  applyPowerSavingSettings();
  alert('超省电模式已开启');
};

// 更新屏幕亮度
const updateScreenBrightness = () => {
  applyPowerSavingSettings();
};

// 切换动画效果
const toggleAnimations = () => {
  animationsEnabled.value = !animationsEnabled.value;
  applyPowerSavingSettings();
};

// 切换后台更新
const toggleBackgroundUpdates = () => {
  backgroundUpdatesEnabled.value = !backgroundUpdatesEnabled.value;
  applyPowerSavingSettings();
};

// 组件挂载时初始化
onMounted(() => {
  // 初始添加当前电池电量到历史记录
  historyPoints.value.push(batteryInfo.value.level);
  
  // 定期更新历史记录(模拟)
  const interval = setInterval(() => {
    updateHistory();
  }, 10000);
  
  // 清理定时器
  return () => clearInterval(interval);
});
</script>

<style scoped>
.dashboard {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 20px;
  margin: 20px 0;
}

.dashboard-card {
  padding: 20px;
  background-color: #f8f9fa;
  border: 1px solid #e9ecef;
  border-radius: 8px;
  transition: all 0.3s ease;
}

.battery-card {
  background-color: #f0f8ff;
  border-color: #add8e6;
}

.battery-high {
  background-color: #f0fff0;
  border-color: #ccffcc;
}

.battery-medium {
  background-color: #fff8e1;
  border-color: #ffe0b2;
}

.battery-low {
  background-color: #fff0f0;
  border-color: #ffcccc;
}

.battery-info {
  display: flex;
  align-items: center;
  justify-content: space-around;
  margin: 20px 0;
}

.battery-level {
  position: relative;
  width: 120px;
  height: 120px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.level-circle {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background: conic-gradient(
    #4caf50 var(--battery-level),
    #e0e0e0 var(--battery-level)
  );
  position: relative;
}

.level-circle::before {
  content: '';
  position: absolute;
  width: 80%;
  height: 80%;
  border-radius: 50%;
  background-color: white;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.level-percentage {
  position: absolute;
  font-weight: bold;
  font-size: 1.5em;
  color: #333;
}

.battery-details {
  display: flex;
  flex-direction: column;
  gap: 15px;
}

.detail-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px;
  background-color: #e9ecef;
  border-radius: 4px;
}

.detail-label {
  color: #6c757d;
  font-weight: 500;
}

.detail-value {
  font-weight: bold;
  color: #495057;
  padding: 4px 12px;
  background-color: white;
  border-radius: 12px;
}

.power-usage-card {
  background-color: #fafafa;
}

.usage-stats {
  margin: 20px 0;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.stat-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.stat-label {
  color: #6c757d;
  font-weight: 500;
  min-width: 120px;
}

.stat-value {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1;
}

.stat-value input[type="range"] {
  flex: 1;
}

.stat-value button {
  padding: 6px 12px;
  background-color: #e0e0e0;
  border: 1px solid #ddd;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.3s ease;
}

.stat-value button:hover {
  background-color: #d0d0d0;
}

.stat-value button.active {
  background-color: #42b883;
  color: white;
  border-color: #42b883;
}

.suggestions-section {
  margin: 30px 0;
  padding: 20px;
  background-color: #f8f9fa;
  border: 1px solid #e9ecef;
  border-radius: 8px;
}

.suggestions-list {
  margin-top: 20px;
  display: flex;
  flex-direction: column;
  gap: 15px;
}

.suggestion-item {
  padding: 15px;
  border-radius: 8px;
  display: flex;
  gap: 15px;
  transition: all 0.3s ease;
}

.suggestion-item.warning {
  background-color: #fff3cd;
  border: 1px solid #ffeeba;
}

.suggestion-item.info {
  background-color: #d1ecf1;
  border: 1px solid #bee5eb;
}

.suggestion-item.suggestion {
  background-color: #d4edda;
  border: 1px solid #c3e6cb;
}

.suggestion-icon {
  font-size: 2em;
  margin-top: 5px;
}

.suggestion-content {
  flex: 1;
}

.suggestion-content h4 {
  margin: 0 0 5px 0;
  color: #333;
}

.suggestion-content p {
  margin: 0 0 10px 0;
  color: #666;
  font-size: 0.9em;
}

.suggestion-action {
  padding: 6px 12px;
  background-color: #42b883;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.9em;
  transition: background-color 0.3s ease;
}

.suggestion-action:hover {
  background-color: #35495e;
}

.history-section {
  margin: 30px 0;
  padding: 20px;
  background-color: #f8f9fa;
  border: 1px solid #e9ecef;
  border-radius: 8px;
}

.history-chart {
  margin-top: 20px;
}

.chart-container {
  height: 200px;
  background-color: white;
  border: 1px solid #e9ecef;
  border-radius: 8px;
  position: relative;
  overflow: hidden;
}

.history-points {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: flex-end;
  justify-content: space-around;
  padding: 0 10px;
}

.history-point {
  width: 8px;
  height: 8px;
  background-color: #4caf50;
  border-radius: 50%;
  transition: all 0.3s ease;
}

.history-point:hover {
  transform: scale(1.5);
  background-color: #2196f3;
}

.chart-legend {
  margin-top: 15px;
  display: flex;
  gap: 20px;
}

.legend-item {
  display: flex;
  align-items: center;
  gap: 8px;
}

.legend-color {
  width: 16px;
  height: 16px;
  border-radius: 3px;
}
</style>

4. 最佳实践

4.1 性能优化

  1. 仅在必要时使用 API

    • 不要在每个组件中都实例化电池状态监听
    • 考虑使用全局状态管理来共享电池状态
    • 避免在性能关键路径上频繁查询电池状态
  2. 合理处理不支持的情况

    • 为不支持 Battery Status API 的浏览器提供回退策略
    • 不要假设 API 总是可用
    • 使用特性检测来检查 API 可用性
  3. 优化电池使用

    • 根据电池电量调整应用功能和性能
    • 在低电量时减少资源消耗
    • 提供电源管理设置选项
  4. 合理处理事件

    • 避免在事件回调中执行复杂计算
    • 考虑使用防抖或节流来处理频繁的状态变化

4.2 代码组织

  1. 使用组合式函数封装

    • 将电池状态逻辑封装到可复用的组合式函数中
    • 提供清晰的 API 接口
    • 自动处理组件生命周期
  2. 结合其他 API 使用

    • 与 Network Information API 结合优化资源加载
    • 与 Screen Wake Lock API 结合管理屏幕唤醒
    • 与 Web Push API 结合调整通知策略
  3. 提供用户控制

    • 允许用户调整电源管理设置
    • 提供清晰的电池状态反馈
    • 允许用户选择电源使用模式

4.3 浏览器兼容性

Battery Status API 具有良好的浏览器支持,但存在一些差异:

  • Chrome 38+
  • Firefox 43+
  • Safari 10.1+
  • Edge 79+

对于不支持的浏览器,可以使用以下方式处理:

// 特性检测
try {
  const battery = await navigator.getBattery();
  // Battery Status API 可用
} catch (error) {
  // 提供回退策略
  console.log('Battery Status API 不可用:', error);
}

5. 常见问题与解决方案

5.1 API 不可用

问题:Battery Status API 在某些浏览器或环境中不可用

解决方案

  • 使用 try-catch 块处理 API 调用
  • 为不支持的浏览器提供默认行为
  • 考虑使用第三方库来模拟 API 功能

5.2 电池状态检测不准确

问题:Battery Status API 报告的电池状态不准确

解决方案

  • 理解 API 报告的是系统提供的估计值
  • 避免过度依赖精确的电量值
  • 考虑使用范围判断(如 > 80%, < 20%)而非精确值

5.3 频繁的状态变化

问题:电池状态频繁变化导致性能问题

解决方案

  • 使用防抖或节流来处理频繁的状态变化
  • 仅在关键决策时查询电池状态
  • 考虑使用缓存的电池状态,定期更新

5.4 不同设备的实现差异

问题:不同设备对 Battery Status API 的实现存在差异

解决方案

  • 测试在不同设备上的行为
  • 处理不同设备的属性差异
  • 考虑使用 polyfill 来统一 API

6. 高级学习资源

6.1 官方文档

6.2 深入学习

6.3 相关工具和库

7. 实践练习

7.1 练习 1:实现智能电源管理应用

目标:创建一个智能电源管理应用,根据电池状态调整应用行为

要求

  1. 使用 Battery Status API 获取电池状态
  2. 根据电池电量自动调整应用功能
  3. 提供电源管理设置选项
  4. 显示电池状态和电源使用统计
  5. 实现不同电源模式的切换

7.2 练习 2:实现电池状态监控仪表板

目标:创建一个电池状态监控仪表板,可视化显示电池信息

要求

  1. 使用 Battery Status API 获取电池状态
  2. 实时显示电池电量、充电状态等信息
  3. 可视化显示电池电量变化历史
  4. 提供电源使用建议
  5. 实现响应式设计,适配不同设备

7.3 练习 3:结合其他 API 的高级应用

目标:结合 Battery Status API 和其他 API 实现高级应用

要求

  1. 结合 Network Information API 优化资源加载
  2. 结合 Screen Wake Lock API 管理屏幕唤醒
  3. 结合 Web Push API 调整通知策略
  4. 根据电池状态和网络条件智能调整应用行为
  5. 提供完整的电源管理解决方案

8. 总结

Battery Status API 是一个强大的工具,用于获取和监控设备的电池状态信息。在 Vue 3 应用中,它可以帮助我们根据电池条件优化应用行为,提供更好的用户体验和电池寿命。

通过创建可复用的组合式函数,我们可以将 Battery Status API 的复杂性封装起来,提供简洁的 API 供组件使用。同时,我们需要注意性能优化、合理处理组件生命周期,以确保应用的高效运行。

在实际开发中,Battery Status API 可以与其他现代浏览器 API(如 Network Information API、Screen Wake Lock API、Web Push API 等)结合使用,实现更复杂的功能和更优的用户体验。

通过深入学习和实践 Battery Status API,你将能够构建更智能、更高效的 Vue 3 应用,适应不同电源条件下的使用场景,为用户提供更好的体验。

9. 代码示例下载

10. 后续学习建议

  1. 学习 Screen Wake Lock API 以管理屏幕唤醒
  2. 深入研究电源管理最佳实践
  3. 学习如何优化 Web 应用的电池使用
  4. 探索 PWA 中的电源管理策略
  5. 研究不同设备和平台的电池管理差异

通过综合运用这些技术,你将能够构建出在各种电源条件下都能提供出色用户体验的 Vue 3 应用,延长设备续航时间,提升用户满意度。

« 上一篇 Vue 3 与 Network Information API 下一篇 » Vue 3 与 Device Orientation API