Skip to content

Badge 徽章

概述

Badge 徽章组件用于显示消息数量或状态,通常出现在图标或文字的右上角。Element Plus 的 Badge 组件支持多种样式、类型和自定义配置,适用于消息提醒、状态标识、数量显示等场景。

学习目标

  • 掌握 Badge 组件的基础用法
  • 理解不同类型和样式的徽章
  • 学会使用小红点和自定义内容徽章
  • 掌握徽章的位置和显示控制
  • 了解徽章在实际项目中的应用

基础用法

基础徽章

最简单的徽章使用,通过 value 属性设置显示的数字:

vue
<template>
  <div class="badge-demo">
    <el-badge :value="12" class="item">
      <el-button size="small">评论</el-button>
    </el-badge>
    
    <el-badge :value="3" class="item">
      <el-button size="small">回复</el-button>
    </el-badge>
    
    <el-badge :value="1" class="item" type="primary">
      <el-button size="small">评论</el-button>
    </el-badge>
    
    <el-badge :value="2" class="item" type="warning">
      <el-button size="small">回复</el-button>
    </el-badge>
  </div>
</template>

<style>
.badge-demo .item {
  margin-top: 10px;
  margin-right: 40px;
}
</style>

最大值

通过 max 属性设置最大值,超过最大值会显示 {max}+

vue
<template>
  <div class="badge-demo">
    <el-badge :value="200" :max="99" class="item">
      <el-button size="small">评论</el-button>
    </el-badge>
    
    <el-badge :value="100" :max="10" class="item">
      <el-button size="small">回复</el-button>
    </el-badge>
  </div>
</template>

自定义内容

通过 value 属性设置自定义内容,也可以通过默认插槽设置:

vue
<template>
  <div class="badge-demo">
    <el-badge value="new" class="item">
      <el-button size="small">评论</el-button>
    </el-badge>
    
    <el-badge value="hot" class="item">
      <el-button size="small">回复</el-button>
    </el-badge>
    
    <el-badge class="item">
      <template #default>
        <el-icon color="#f56c6c"><ChatDotRound /></el-icon>
      </template>
      <el-button size="small">消息</el-button>
    </el-badge>
  </div>
</template>

<script setup>
import { ChatDotRound } from '@element-plus/icons-vue'
</script>

小红点

小红点徽章

通过 is-dot 属性设置为小红点模式:

vue
<template>
  <div class="badge-demo">
    <el-badge is-dot class="item">
      <el-button class="share-button" icon="Share" />
    </el-badge>
    
    <el-badge is-dot class="item">
      <el-button class="share-button" icon="ChatDotRound" />
    </el-badge>
    
    <el-badge is-dot class="item">
      数据查询
    </el-badge>
    
    <el-badge is-dot class="item">
      <el-icon><Message /></el-icon>
    </el-badge>
  </div>
</template>

<script setup>
import { Share, ChatDotRound, Message } from '@element-plus/icons-vue'
</script>

不同类型

类型样式

Badge 组件提供了多种类型:primarysuccesswarningdangerinfo

vue
<template>
  <div class="badge-demo">
    <el-badge :value="1" class="item">
      <el-button size="small">默认</el-button>
    </el-badge>
    
    <el-badge :value="2" class="item" type="primary">
      <el-button size="small">主要</el-button>
    </el-badge>
    
    <el-badge :value="3" class="item" type="success">
      <el-button size="small">成功</el-button>
    </el-badge>
    
    <el-badge :value="4" class="item" type="warning">
      <el-button size="small">警告</el-button>
    </el-badge>
    
    <el-badge :value="5" class="item" type="danger">
      <el-button size="small">危险</el-button>
    </el-badge>
    
    <el-badge :value="6" class="item" type="info">
      <el-button size="small">信息</el-button>
    </el-badge>
  </div>
</template>

动态徽章

动态数值

结合响应式数据实现动态徽章:

vue
<template>
  <div class="dynamic-badge-demo">
    <el-badge :value="dynamicValue" :max="99" class="item">
      <el-button size="small">消息</el-button>
    </el-badge>
    
    <el-badge :value="notifications" :max="999" class="item" type="danger">
      <el-button size="small">通知</el-button>
    </el-badge>
    
    <el-badge :is-dot="showDot" class="item">
      <el-button size="small">待办事项</el-button>
    </el-badge>
    
    <div class="demo-controls">
      <el-button-group>
        <el-button @click="increase">增加消息</el-button>
        <el-button @click="decrease">减少消息</el-button>
        <el-button @click="addNotification">添加通知</el-button>
        <el-button @click="toggleDot">切换小红点</el-button>
        <el-button @click="reset">重置</el-button>
      </el-button-group>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const dynamicValue = ref(5)
const notifications = ref(12)
const showDot = ref(true)

const increase = () => {
  dynamicValue.value++
}

const decrease = () => {
  if (dynamicValue.value > 0) {
    dynamicValue.value--
  }
}

const addNotification = () => {
  notifications.value += Math.floor(Math.random() * 10) + 1
}

const toggleDot = () => {
  showDot.value = !showDot.value
}

const reset = () => {
  dynamicValue.value = 0
  notifications.value = 0
  showDot.value = false
}
</script>

<style>
.dynamic-badge-demo .item {
  margin: 10px 20px 10px 0;
}

.dynamic-badge-demo .demo-controls {
  margin-top: 20px;
}

.dynamic-badge-demo .demo-controls .el-button {
  margin: 5px;
}
</style>

隐藏徽章

条件显示

通过 hidden 属性控制徽章的显示和隐藏:

vue
<template>
  <div class="hidden-badge-demo">
    <el-badge :value="messageCount" :hidden="messageCount === 0" class="item">
      <el-button size="small">消息</el-button>
    </el-badge>
    
    <el-badge :value="taskCount" :hidden="hideTask" class="item" type="warning">
      <el-button size="small">任务</el-button>
    </el-badge>
    
    <el-badge :is-dot="showAlert" :hidden="!showAlert" class="item">
      <el-button size="small">提醒</el-button>
    </el-badge>
    
    <div class="demo-controls">
      <el-button @click="addMessage">添加消息</el-button>
      <el-button @click="clearMessages">清空消息</el-button>
      <el-button @click="toggleTask">切换任务显示</el-button>
      <el-button @click="toggleAlert">切换提醒</el-button>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const messageCount = ref(0)
const taskCount = ref(3)
const hideTask = ref(false)
const showAlert = ref(false)

const addMessage = () => {
  messageCount.value++
}

const clearMessages = () => {
  messageCount.value = 0
}

const toggleTask = () => {
  hideTask.value = !hideTask.value
}

const toggleAlert = () => {
  showAlert.value = !showAlert.value
}
</script>

实际应用示例

消息中心徽章系统

一个完整的消息中心徽章管理示例:

vue
<template>
  <div class="message-center">
    <!-- 顶部导航栏 -->
    <div class="navbar">
      <div class="nav-left">
        <h2>我的工作台</h2>
      </div>
      
      <div class="nav-right">
        <!-- 消息通知 -->
        <el-badge :value="totalMessages" :max="99" :hidden="totalMessages === 0" class="nav-item">
          <el-button 
            :icon="Message" 
            circle 
            @click="showMessagePanel = !showMessagePanel"
          />
        </el-badge>
        
        <!-- 任务提醒 -->
        <el-badge :value="urgentTasks" :max="9" type="danger" :hidden="urgentTasks === 0" class="nav-item">
          <el-button 
            :icon="Bell" 
            circle 
            @click="showTaskPanel = !showTaskPanel"
          />
        </el-badge>
        
        <!-- 系统通知 -->
        <el-badge :is-dot="hasSystemNotice" class="nav-item">
          <el-button 
            :icon="Warning" 
            circle 
            @click="showNoticePanel = !showNoticePanel"
          />
        </el-badge>
        
        <!-- 用户菜单 -->
        <el-badge :is-dot="hasProfileUpdate" class="nav-item">
          <el-avatar 
            :size="40" 
            src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
            @click="showUserMenu = !showUserMenu"
          />
        </el-badge>
      </div>
    </div>
    
    <!-- 消息面板 -->
    <el-drawer v-model="showMessagePanel" title="消息中心" direction="rtl" size="400px">
      <div class="message-panel">
        <div class="message-tabs">
          <el-tabs v-model="activeMessageTab">
            <el-tab-pane label="全部" name="all">
              <el-badge :value="allMessages.length" class="tab-badge" />
            </el-tab-pane>
            <el-tab-pane label="未读" name="unread">
              <el-badge :value="unreadMessages.length" type="danger" class="tab-badge" />
            </el-tab-pane>
            <el-tab-pane label="系统" name="system">
              <el-badge :value="systemMessages.length" type="warning" class="tab-badge" />
            </el-tab-pane>
          </el-tabs>
        </div>
        
        <div class="message-list">
          <div 
            v-for="message in currentMessages" 
            :key="message.id" 
            class="message-item"
            :class="{ 'unread': !message.read }"
            @click="markAsRead(message)"
          >
            <div class="message-avatar">
              <el-badge :is-dot="!message.read" :offset="[0, 0]">
                <el-avatar :size="40" :src="message.avatar" />
              </el-badge>
            </div>
            
            <div class="message-content">
              <div class="message-title">{{ message.title }}</div>
              <div class="message-text">{{ message.content }}</div>
              <div class="message-time">{{ formatTime(message.time) }}</div>
            </div>
            
            <div class="message-actions">
              <el-badge :value="message.priority" v-if="message.priority" type="danger" />
            </div>
          </div>
        </div>
        
        <div class="message-actions">
          <el-button @click="markAllAsRead" :disabled="unreadMessages.length === 0">
            全部标记为已读
          </el-button>
          <el-button @click="clearAllMessages" type="danger">
            清空所有消息
          </el-button>
        </div>
      </div>
    </el-drawer>
    
    <!-- 任务面板 -->
    <el-drawer v-model="showTaskPanel" title="任务中心" direction="rtl" size="400px">
      <div class="task-panel">
        <div class="task-summary">
          <div class="summary-item">
            <el-badge :value="totalTasks" class="summary-badge">
              <div class="summary-label">总任务</div>
            </el-badge>
          </div>
          <div class="summary-item">
            <el-badge :value="urgentTasks" type="danger" class="summary-badge">
              <div class="summary-label">紧急任务</div>
            </el-badge>
          </div>
          <div class="summary-item">
            <el-badge :value="completedTasks" type="success" class="summary-badge">
              <div class="summary-label">已完成</div>
            </el-badge>
          </div>
        </div>
        
        <div class="task-list">
          <div 
            v-for="task in tasks" 
            :key="task.id" 
            class="task-item"
            :class="{ 'urgent': task.urgent, 'completed': task.completed }"
          >
            <div class="task-status">
              <el-badge 
                :is-dot="task.urgent && !task.completed" 
                type="danger"
                :offset="[0, 0]"
              >
                <el-checkbox 
                  v-model="task.completed" 
                  @change="updateTaskStatus(task)"
                />
              </el-badge>
            </div>
            
            <div class="task-content">
              <div class="task-title">{{ task.title }}</div>
              <div class="task-desc">{{ task.description }}</div>
              <div class="task-meta">
                <span class="task-deadline">{{ formatDeadline(task.deadline) }}</span>
                <el-badge 
                  v-if="task.priority" 
                  :value="task.priority" 
                  :type="getPriorityType(task.priority)"
                />
              </div>
            </div>
          </div>
        </div>
      </div>
    </el-drawer>
    
    <!-- 主要内容区域 -->
    <div class="main-content">
      <div class="content-cards">
        <el-card class="content-card">
          <template #header>
            <div class="card-header">
              <span>快捷操作</span>
              <el-badge :value="quickActions.length" type="primary" />
            </div>
          </template>
          
          <div class="quick-actions">
            <div 
              v-for="action in quickActions" 
              :key="action.id" 
              class="action-item"
              @click="executeAction(action)"
            >
              <el-badge :value="action.count" :hidden="action.count === 0" :type="action.type">
                <div class="action-icon">
                  <el-icon :size="24"><component :is="action.icon" /></el-icon>
                </div>
              </el-badge>
              <div class="action-label">{{ action.label }}</div>
            </div>
          </div>
        </el-card>
        
        <el-card class="content-card">
          <template #header>
            <div class="card-header">
              <span>数据统计</span>
              <el-badge :is-dot="hasNewData" />
            </div>
          </template>
          
          <div class="statistics">
            <div class="stat-item">
              <div class="stat-value">
                <el-badge :value="todayVisits" :max="9999">
                  <span class="stat-number">{{ todayVisits }}</span>
                </el-badge>
              </div>
              <div class="stat-label">今日访问</div>
            </div>
            
            <div class="stat-item">
              <div class="stat-value">
                <el-badge :value="newUsers" type="success">
                  <span class="stat-number">{{ newUsers }}</span>
                </el-badge>
              </div>
              <div class="stat-label">新增用户</div>
            </div>
            
            <div class="stat-item">
              <div class="stat-value">
                <el-badge :value="errorCount" type="danger" :hidden="errorCount === 0">
                  <span class="stat-number">{{ errorCount }}</span>
                </el-badge>
              </div>
              <div class="stat-label">系统错误</div>
            </div>
          </div>
        </el-card>
      </div>
      
      <!-- 模拟操作按钮 -->
      <div class="demo-controls">
        <h3>模拟操作</h3>
        <el-button-group>
          <el-button @click="addMessage">新增消息</el-button>
          <el-button @click="addTask">新增任务</el-button>
          <el-button @click="addUrgentTask">新增紧急任务</el-button>
          <el-button @click="toggleSystemNotice">切换系统通知</el-button>
          <el-button @click="simulateError">模拟错误</el-button>
          <el-button @click="resetAll" type="danger">重置所有</el-button>
        </el-button-group>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { 
  Message, 
  Bell, 
  Warning, 
  Document, 
  User, 
  DataAnalysis, 
  Setting,
  Monitor
} from '@element-plus/icons-vue'

// 面板显示状态
const showMessagePanel = ref(false)
const showTaskPanel = ref(false)
const showNoticePanel = ref(false)
const showUserMenu = ref(false)

// 消息相关
const activeMessageTab = ref('all')
const messages = ref([
  {
    id: 1,
    title: '系统更新通知',
    content: '系统将在今晚进行维护更新,请提前保存工作内容',
    time: new Date(Date.now() - 1000 * 60 * 30),
    read: false,
    type: 'system',
    priority: 1,
    avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png'
  },
  {
    id: 2,
    title: '新任务分配',
    content: '您有一个新的项目任务需要处理',
    time: new Date(Date.now() - 1000 * 60 * 60),
    read: true,
    type: 'task',
    avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png'
  }
])

// 任务相关
const tasks = ref([
  {
    id: 1,
    title: '完成项目文档',
    description: '编写项目技术文档和用户手册',
    deadline: new Date(Date.now() + 1000 * 60 * 60 * 24),
    urgent: true,
    completed: false,
    priority: 'high'
  },
  {
    id: 2,
    title: '代码审查',
    description: '审查团队成员提交的代码',
    deadline: new Date(Date.now() + 1000 * 60 * 60 * 48),
    urgent: false,
    completed: true,
    priority: 'medium'
  }
])

// 系统状态
const hasSystemNotice = ref(true)
const hasProfileUpdate = ref(false)
const hasNewData = ref(true)
const todayVisits = ref(1234)
const newUsers = ref(56)
const errorCount = ref(0)

// 快捷操作
const quickActions = ref([
  { id: 1, label: '文档管理', icon: Document, count: 5, type: 'primary' },
  { id: 2, label: '用户管理', icon: User, count: 2, type: 'success' },
  { id: 3, label: '数据分析', icon: DataAnalysis, count: 0, type: 'info' },
  { id: 4, label: '系统设置', icon: Setting, count: 1, type: 'warning' },
  { id: 5, label: '监控中心', icon: Monitor, count: 3, type: 'danger' }
])

// 计算属性
const totalMessages = computed(() => messages.value.length)
const unreadMessages = computed(() => messages.value.filter(m => !m.read))
const allMessages = computed(() => messages.value)
const systemMessages = computed(() => messages.value.filter(m => m.type === 'system'))

const currentMessages = computed(() => {
  switch (activeMessageTab.value) {
    case 'unread':
      return unreadMessages.value
    case 'system':
      return systemMessages.value
    default:
      return allMessages.value
  }
})

const totalTasks = computed(() => tasks.value.length)
const urgentTasks = computed(() => tasks.value.filter(t => t.urgent && !t.completed).length)
const completedTasks = computed(() => tasks.value.filter(t => t.completed).length)

// 方法
const markAsRead = (message) => {
  message.read = true
  ElMessage.success('消息已标记为已读')
}

const markAllAsRead = () => {
  messages.value.forEach(m => m.read = true)
  ElMessage.success('所有消息已标记为已读')
}

const clearAllMessages = () => {
  messages.value = []
  ElMessage.success('所有消息已清空')
}

const updateTaskStatus = (task) => {
  if (task.completed) {
    ElMessage.success(`任务"${task.title}"已完成`)
  }
}

const addMessage = () => {
  const newMessage = {
    id: Date.now(),
    title: '新消息',
    content: '这是一条新的消息通知',
    time: new Date(),
    read: false,
    type: 'normal',
    avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png'
  }
  messages.value.unshift(newMessage)
  ElMessage.success('新消息已添加')
}

const addTask = () => {
  const newTask = {
    id: Date.now(),
    title: '新任务',
    description: '这是一个新的任务',
    deadline: new Date(Date.now() + 1000 * 60 * 60 * 24 * 3),
    urgent: false,
    completed: false,
    priority: 'low'
  }
  tasks.value.unshift(newTask)
  ElMessage.success('新任务已添加')
}

const addUrgentTask = () => {
  const urgentTask = {
    id: Date.now(),
    title: '紧急任务',
    description: '这是一个紧急任务,需要立即处理',
    deadline: new Date(Date.now() + 1000 * 60 * 60 * 2),
    urgent: true,
    completed: false,
    priority: 'high'
  }
  tasks.value.unshift(urgentTask)
  ElMessage.warning('紧急任务已添加')
}

const toggleSystemNotice = () => {
  hasSystemNotice.value = !hasSystemNotice.value
  ElMessage.info(`系统通知已${hasSystemNotice.value ? '开启' : '关闭'}`)
}

const simulateError = () => {
  errorCount.value++
  ElMessage.error('模拟系统错误')
}

const resetAll = () => {
  messages.value = []
  tasks.value = []
  errorCount.value = 0
  hasSystemNotice.value = false
  hasNewData.value = false
  ElMessage.success('所有数据已重置')
}

const executeAction = (action) => {
  ElMessage.info(`执行操作:${action.label}`)
}

const formatTime = (time) => {
  const now = new Date()
  const diff = now - time
  const minutes = Math.floor(diff / (1000 * 60))
  const hours = Math.floor(diff / (1000 * 60 * 60))
  const days = Math.floor(diff / (1000 * 60 * 60 * 24))
  
  if (days > 0) return `${days}天前`
  if (hours > 0) return `${hours}小时前`
  if (minutes > 0) return `${minutes}分钟前`
  return '刚刚'
}

const formatDeadline = (deadline) => {
  return deadline.toLocaleDateString('zh-CN')
}

const getPriorityType = (priority) => {
  switch (priority) {
    case 'high': return 'danger'
    case 'medium': return 'warning'
    case 'low': return 'info'
    default: return 'info'
  }
}
</script>

<style scoped>
.message-center {
  min-height: 100vh;
  background-color: #f5f7fa;
}

.navbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 20px;
  height: 60px;
  background-color: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.nav-left h2 {
  margin: 0;
  color: #303133;
}

.nav-right {
  display: flex;
  align-items: center;
  gap: 15px;
}

.nav-item {
  position: relative;
}

.main-content {
  padding: 20px;
}

.content-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
  gap: 20px;
  margin-bottom: 30px;
}

.content-card {
  height: fit-content;
}

.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.quick-actions {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
  gap: 15px;
}

.action-item {
  text-align: center;
  padding: 15px;
  border-radius: 8px;
  background-color: #f8f9fa;
  cursor: pointer;
  transition: all 0.3s;
}

.action-item:hover {
  background-color: #e9ecef;
  transform: translateY(-2px);
}

.action-icon {
  margin-bottom: 8px;
}

.action-label {
  font-size: 14px;
  color: #606266;
}

.statistics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 20px;
}

.stat-item {
  text-align: center;
}

.stat-value {
  margin-bottom: 8px;
}

.stat-number {
  font-size: 24px;
  font-weight: bold;
  color: #303133;
}

.stat-label {
  font-size: 14px;
  color: #606266;
}

.message-panel {
  height: 100%;
  display: flex;
  flex-direction: column;
}

.message-tabs {
  margin-bottom: 20px;
}

.tab-badge {
  margin-left: 8px;
}

.message-list {
  flex: 1;
  overflow-y: auto;
}

.message-item {
  display: flex;
  padding: 15px;
  border-bottom: 1px solid #ebeef5;
  cursor: pointer;
  transition: background-color 0.3s;
}

.message-item:hover {
  background-color: #f5f7fa;
}

.message-item.unread {
  background-color: #f0f9ff;
}

.message-avatar {
  margin-right: 12px;
}

.message-content {
  flex: 1;
}

.message-title {
  font-weight: 500;
  margin-bottom: 4px;
}

.message-text {
  color: #606266;
  font-size: 14px;
  margin-bottom: 4px;
}

.message-time {
  color: #909399;
  font-size: 12px;
}

.message-actions {
  margin-top: 20px;
  text-align: center;
}

.message-actions .el-button {
  margin: 0 5px;
}

.task-panel {
  height: 100%;
  display: flex;
  flex-direction: column;
}

.task-summary {
  display: flex;
  justify-content: space-around;
  margin-bottom: 20px;
  padding: 15px;
  background-color: #f8f9fa;
  border-radius: 8px;
}

.summary-item {
  text-align: center;
}

.summary-badge {
  margin-bottom: 8px;
}

.summary-label {
  font-size: 14px;
  color: #606266;
}

.task-list {
  flex: 1;
  overflow-y: auto;
}

.task-item {
  display: flex;
  padding: 15px;
  margin-bottom: 10px;
  background-color: #fafafa;
  border-radius: 8px;
  border-left: 4px solid #e4e7ed;
}

.task-item.urgent {
  border-left-color: #f56c6c;
}

.task-item.completed {
  opacity: 0.6;
}

.task-status {
  margin-right: 12px;
}

.task-content {
  flex: 1;
}

.task-title {
  font-weight: 500;
  margin-bottom: 4px;
}

.task-desc {
  color: #606266;
  font-size: 14px;
  margin-bottom: 8px;
}

.task-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.task-deadline {
  color: #909399;
  font-size: 12px;
}

.demo-controls {
  padding: 20px;
  background-color: white;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.demo-controls h3 {
  margin: 0 0 15px 0;
  color: #303133;
}

.demo-controls .el-button {
  margin: 5px;
}
</style>

Element Plus Study Guide