Java全栈项目--校园快递管理与配送系统(4)

源代码续

/**
 * 通知工具类
 */

// 通知类型常量
export const NotificationType = {
  SYSTEM: 1,
  EXPRESS: 2,
  ACTIVITY: 3
}

// 通知类型名称映射
export const NotificationTypeNames = {
  [NotificationType.SYSTEM]: '系统通知',
  [NotificationType.EXPRESS]: '快递通知',
  [NotificationType.ACTIVITY]: '活动通知'
}

// 通知渠道常量
export const NotificationChannel = {
  IN_APP: 1,
  SMS: 2,
  EMAIL: 3,
  PUSH: 4
}

// 通知渠道名称映射
export const NotificationChannelNames = {
  [NotificationChannel.IN_APP]: '站内信',
  [NotificationChannel.SMS]: '短信',
  [NotificationChannel.EMAIL]: '邮件',
  [NotificationChannel.PUSH]: '推送'
}

// 通知状态常量
export const NotificationStatus = {
  DRAFT: 0,
  SENT: 1,
  FAILED: 2
}

// 通知状态名称映射
export const NotificationStatusNames = {
  [NotificationStatus.DRAFT]: '草稿',
  [NotificationStatus.SENT]: '已发送',
  [NotificationStatus.FAILED]: '发送失败'
}

/**
 * 获取通知类型对应的标签类型
 * @param {number} type 通知类型
 * @returns {string} 标签类型
 */
export function getTypeTagType(type) {
  switch (type) {
    case NotificationType.SYSTEM:
      return 'primary'
    case NotificationType.EXPRESS:
      return 'success'
    case NotificationType.ACTIVITY:
      return 'warning'
    default:
      return 'info'
  }
}

/**
 * 获取通知类型对应的颜色
 * @param {number} type 通知类型
 * @returns {string} 颜色
 */
export function getTypeColor(type) {
  switch (type) {
    case NotificationType.SYSTEM:
      return '#409EFF'
    case NotificationType.EXPRESS:
      return '#67C23A'
    case NotificationType.ACTIVITY:
      return '#E6A23C'
    default:
      return '#909399'
  }
}

/**
 * 获取通知渠道对应的标签类型
 * @param {number} channel 通知渠道
 * @returns {string} 标签类型
 */
export function getChannelTagType(channel) {
  switch (channel) {
    case NotificationChannel.IN_APP:
      return 'primary'
    case NotificationChannel.SMS:
      return 'success'
    case NotificationChannel.EMAIL:
      return 'warning'
    case NotificationChannel.PUSH:
      return 'danger'
    default:
      return 'info'
  }
}

/**
 * 获取通知状态对应的标签类型
 * @param {number} status 通知状态
 * @returns {string} 标签类型
 */
export function getStatusTagType(status) {
  switch (status) {
    case NotificationStatus.DRAFT:
      return 'info'
    case NotificationStatus.SENT:
      return 'success'
    case NotificationStatus.FAILED:
      return 'danger'
    default:
      return 'info'
  }
}

/**
 * 提取模板变量
 * @param {string} content 模板内容
 * @returns {Array} 变量数组
 */
export function extractTemplateVariables(content) {
  if (!content) return []
  
  const regex = /\{\{([^}]+)\}\}/g
  let match
  const variables = []
  
  while ((match = regex.exec(content)) !== null) {
    variables.push({
      name: match[1].trim(),
      placeholder: match[0],
      value: ''
    })
  }
  
  // 去重
  return variables.filter((v, i, a) => a.findIndex(t => t.name === v.name) === i)
}

/**
 * 替换模板变量
 * @param {string} content 模板内容
 * @param {Array} variables 变量数组
 * @returns {string} 替换后的内容
 */
export function replaceTemplateVariables(content, variables) {
  if (!content || !variables || variables.length === 0) return content
  
  let result = content
  
  variables.forEach(variable => {
    if (variable.value) {
      const regex = new RegExp(variable.placeholder, 'g')
      result = result.replace(regex, variable.value)
    }
  })
  
  return result
}

/**
 * 格式化文件大小
 * @param {number} size 文件大小(字节)
 * @returns {string} 格式化后的文件大小
 */
export function formatFileSize(size) {
  if (size < 1024) {
    return size + ' B'
  } else if (size < 1024 * 1024) {
    return (size / 1024).toFixed(2) + ' KB'
  } else if (size < 1024 * 1024 * 1024) {
    return (size / (1024 * 1024)).toFixed(2) + ' MB'
  } else {
    return (size / (1024 * 1024 * 1024)).toFixed(2) + ' GB'
  }
}

/**
 * 格式化通知内容(将换行符转换为HTML换行)
 * @param {string} content 通知内容
 * @returns {string} 格式化后的内容
 */
export function formatContent(content) {
  if (!content) return ''
  return content.replace(/\n/g, '<br>')
}

/**
 * 获取未读通知数量
 * @param {Array} notifications 通知列表
 * @returns {number} 未读通知数量
 */
export function getUnreadCount(notifications) {
  if (!notifications || notifications.length === 0) return 0
  return notifications.filter(item => !item.read).length
}

/**
 * 获取指定类型的通知数量
 * @param {Array} notifications 通知列表
 * @param {number} type 通知类型
 * @returns {number} 指定类型的通知数量
 */
export function getTypeCount(notifications, type) {
  if (!notifications || notifications.length === 0) return 0
  return notifications.filter(item => item.type === type).length
}

/**
 * 获取指定渠道的通知数量
 * @param {Array} notifications 通知列表
 * @param {number} channel 通知渠道
 * @returns {number} 指定渠道的通知数量
 */
export function getChannelCount(notifications, channel) {
  if (!notifications || notifications.length === 0) return 0
  return notifications.filter(item => item.channel === channel).length
}

express-ui\src\views\notification\dashboard.vue

<template>
  <div class="app-container">
    <el-row :gutter="20">
      <!-- 统计卡片 -->
      <el-col :xs="24" :sm="12" :md="6" :lg="6" :xl="6" v-for="(item, index) in statisticsCards" :key="index">
        <el-card class="stat-card" :body-style="{ padding: '20px' }" shadow="hover">
          <div class="card-icon">
            <i :class="item.icon" :style="{ color: item.color }"></i>
          </div>
          <div class="card-content">
            <div class="card-title">{{ item.title }}</div>
            <div class="card-value">{{ item.value }}</div>
            <div class="card-footer">
              <span>{{ item.change >= 0 ? '+' : '' }}{{ item.change }}%</span>
              <span>较上周</span>
              <i :class="item.change >= 0 ? 'el-icon-top' : 'el-icon-bottom'" 
                 :style="{ color: item.change >= 0 ? '#67C23A' : '#F56C6C' }"></i>
            </div>
          </div>
        </el-card>
      </el-col>
    </el-row>

    <el-row :gutter="20" style="margin-top: 20px;">
      <!-- 通知发送趋势图 -->
      <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
        <el-card shadow="hover">
          <div slot="header" class="clearfix">
            <span>通知发送趋势</span>
            <el-radio-group v-model="trendTimeRange" size="mini" style="float: right;">
              <el-radio-button label="week">本周</el-radio-button>
              <el-radio-button label="month">本月</el-radio-button>
              <el-radio-button label="year">全年</el-radio-button>
            </el-radio-group>
          </div>
          <div class="chart-container">
            <div ref="trendChart" style="width: 100%; height: 300px;"></div>
          </div>
        </el-card>
      </el-col>

      <!-- 通知渠道分布图 -->
      <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
        <el-card shadow="hover">
          <div slot="header" class="clearfix">
            <span>通知渠道分布</span>
            <el-radio-group v-model="channelTimeRange" size="mini" style="float: right;">
              <el-radio-button label="week">本周</el-radio-button>
              <el-radio-button label="month">本月</el-radio-button>
              <el-radio-button label="year">全年</el-radio-button>
            </el-radio-group>
          </div>
          <div class="chart-container">
            <div ref="channelChart" style="width: 100%; height: 300px;"></div>
          </div>
        </el-card>
      </el-col>
    </el-row>

    <el-row :gutter="20" style="margin-top: 20px;">
      <!-- 最近发送的通知 -->
      <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
        <el-card shadow="hover">
          <div slot="header" class="clearfix">
            <span>最近发送的通知</span>
            <el-button style="float: right; padding: 3px 0" type="text" @click="viewMore('list')">查看更多</el-button>
          </div>
          <el-table :data="recentNotifications" style="width: 100%" :show-header="false">
            <el-table-column width="50">
              <template slot-scope="scope">
                <el-avatar :size="30" :style="{ backgroundColor: getTypeColor(scope.row.type) }">
                  {{ scope.row.title.substring(0, 1) }}
                </el-avatar>
              </template>
            </el-table-column>
            <el-table-column>
              <template slot-scope="scope">
                <div class="notification-item">
                  <div class="notification-title">
                    <span>{{ scope.row.title }}</span>
                    <el-tag size="mini" :type="getTypeTag(scope.row.type)">{{ getTypeName(scope.row.type) }}</el-tag>
                  </div>
                  <div class="notification-content">{{ scope.row.content }}</div>
                  <div class="notification-footer">
                    <span>{{ scope.row.sendTime }}</span>
                    <span>
                      <el-tag size="mini" :type="getChannelTag(scope.row.channel)">
                        {{ getChannelName(scope.row.channel) }}
                      </el-tag>
                    </span>
                  </div>
                </div>
              </template>
            </el-table-column>
            <el-table-column width="80" align="center">
              <template slot-scope="scope">
                <el-button size="mini" type="text" icon="el-icon-view" @click="viewDetail(scope.row)"></el-button>
              </template>
            </el-table-column>
          </el-table>
        </el-card>
      </el-col>

      <!-- 最近更新的模板 -->
      <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
        <el-card shadow="hover">
          <div slot="header" class="clearfix">
            <span>最近更新的模板</span>
            <el-button style="float: right; padding: 3px 0" type="text" @click="viewMore('template')">查看更多</el-button>
          </div>
          <el-table :data="recentTemplates" style="width: 100%" :show-header="false">
            <el-table-column width="50">
              <template slot-scope="scope">
                <el-avatar :size="30" :style="{ backgroundColor: getTypeColor(scope.row.type) }">
                  {{ scope.row.name.substring(0, 1) }}
                </el-avatar>
              </template>
            </el-table-column>
            <el-table-column>
              <template slot-scope="scope">
                <div class="template-item">
                  <div class="template-title">
                    <span>{{ scope.row.name }}</span>
                    <el-tag size="mini" :type="getTypeTag(scope.row.type)">{{ getTypeName(scope.row.type) }}</el-tag>
                  </div>
                  <div class="template-content">{{ scope.row.content }}</div>
                  <div class="template-footer">
                    <span>{{ scope.row.updateTime }}</span>
                    <span>
                      <el-tag v-for="channel in scope.row.channel" :key="channel" size="mini" 
                              :type="getChannelTag(channel)" class="channel-tag">
                        {{ getChannelName(channel) }}
                      </el-tag>
                    </span>
                  </div>
                </div>
              </template>
            </el-table-column>
            <el-table-column width="80" align="center">
              <template slot-scope="scope">
                <el-button size="mini" type="text" icon="el-icon-edit" @click="editTemplate(scope.row)"></el-button>
              </template>
            </el-table-column>
          </el-table>
        </el-card>
      </el-col>
    </el-row>

    <el-row :gutter="20" style="margin-top: 20px;">
      <!-- 快捷操作 -->
      <el-col :span="24">
        <el-card shadow="hover">
          <div slot="header" class="clearfix">
            <span>快捷操作</span>
          </div>
          <div class="quick-actions">
            <el-button type="primary" icon="el-icon-message" @click="$router.push('/notification/send')">发送通知</el-button>
            <el-button type="success" icon="el-icon-document-add" @click="$router.push('/notification/template')">创建模板</el-button>
            <el-button type="warning" icon="el-icon-data-analysis" @click="exportNotificationData">导出数据</el-button>
            <el-button type="info" icon="el-icon-setting" @click="openSettings">通知设置</el-button>
          </div>
        </el-card>
      </el-col>
    </el-row>

    <!-- 设置对话框 -->
    <el-dialog title="通知设置" :visible.sync="settingsVisible" width="500px">
      <el-form :model="settings" label-width="120px">
        <el-form-item label="默认通知渠道">
          <el-checkbox-group v-model="settings.defaultChannels">
            <el-checkbox :label="1">站内信</el-checkbox>
            <el-checkbox :label="2">短信</el-checkbox>
            <el-checkbox :label="3">邮件</el-checkbox>
            <el-checkbox :label="4">推送</el-checkbox>
          </el-checkbox-group>
        </el-form-item>
        <el-form-item label="通知保留时间">
          <el-input-number v-model="settings.retentionDays" :min="1" :max="365" label="天数"></el-input-number>
          <span class="form-help">天</span>
        </el-form-item>
        <el-form-item label="自动清理通知">
          <el-switch v-model="settings.autoCleanup"></el-switch>
        </el-form-item>
        <el-form-item label="通知发送限制">
          <el-input-number v-model="settings.dailyLimit" :min="0" :max="10000" label="每日限制"></el-input-number>
          <span class="form-help">条/天</span>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="settingsVisible = false">取消</el-button>
        <el-button type="primary" @click="saveSettings">保存</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { getNotificationStats } from '@/api/notification'
import * as echarts from 'echarts'

export default {
  name: 'NotificationDashboard',
  data() {
    return {
      // 统计卡片数据
      statisticsCards: [
        {
          title: '今日发送',
          value: 128,
          icon: 'el-icon-s-promotion',
          color: '#409EFF',
          change: 12.5
        },
        {
          title: '阅读率',
          value: '85.2%',
          icon: 'el-icon-view',
          color: '#67C23A',
          change: 5.3
        },
        {
          title: '模板数量',
          value: 24,
          icon: 'el-icon-document',
          color: '#E6A23C',
          change: 0
        },
        {
          title: '未读通知',
          value: 36,
          icon: 'el-icon-message',
          color: '#F56C6C',
          change: -8.2
        }
      ],
      // 趋势图时间范围
      trendTimeRange: 'week',
      // 渠道分布图时间范围
      channelTimeRange: 'week',
      // 趋势图实例
      trendChart: null,
      // 渠道分布图实例
      channelChart: null,
      // 最近发送的通知
      recentNotifications: [
        {
          id: 1,
          title: '系统维护通知',
          content: '系统将于2023年5月1日凌晨2点至4点进行维护,届时系统将暂停服务,请提前做好准备。',
          type: 1,
          channel: 1,
          sendTime: '2023-04-29 10:30:00'
        },
        {
          id: 2,
          title: '快递到达通知',
          content: '您的快递(顺丰 SF1234567890)已到达校园快递站,取件码:8888,请及时前往领取。',
          type: 2,
          channel: 2,
          sendTime: '2023-04-29 09:15:00'
        },
        {
          id: 3,
          title: '五一活动邀请',
          content: '诚邀您参加五一劳动节文艺汇演活动,时间:2023年5月1日下午2点,地点:校园大礼堂,期待您的参与!',
          type: 3,
          channel: 3,
          sendTime: '2023-04-28 16:45:00'
        },
        {
          id: 4,
          title: '教务系统更新通知',
          content: '教务系统已完成版本更新,新增成绩查询、课表导出等功能,欢迎使用。',
          type: 1,
          channel: 4,
          sendTime: '2023-04-28 14:20:00'
        }
      ],
      // 最近更新的模板
      recentTemplates: [
        {
          id: 1,
          name: '系统维护通知模板',
          content: '尊敬的{{userName}},系统将于{{startTime}}至{{endTime}}进行系统维护,届时系统将暂停服务,请提前做好准备。',
          type: 1,
          channel: [1, 3],
          updateTime: '2023-04-27 16:30:00'
        },
        {
          id: 2,
          name: '快递到达通知模板',
          content: '您好,{{userName}},您的快递({{expressCompany}} {{expressCode}})已到达校园快递站,取件码:{{pickupCode}},请及时前往领取。',
          type: 2,
          channel: [1, 2, 4],
          updateTime: '2023-04-26 14:20:00'
        },
        {
          id: 3,
          name: '活动邀请模板',
          content: '亲爱的{{userName}},诚邀您参加{{activityName}}活动,时间:{{activityTime}},地点:{{activityLocation}},期待您的参与!',
          type: 3,
          channel: [1, 3],
          updateTime: '2023-04-25 11:15:00'
        },
        {
          id: 4,
          name: '课程变更通知模板',
          content: '尊敬的{{userName}},您的课程{{courseName}}将于{{changeTime}}变更为{{newTime}},地点:{{location}},请知悉。',
          type: 1,
          channel: [1, 2, 3, 4],
          updateTime: '2023-04-24 09:30:00'
        }
      ],
      // 设置对话框
      settingsVisible: false,
      // 设置
      settings: {
        defaultChannels: [1],
        retentionDays: 30,
        autoCleanup: true,
        dailyLimit: 1000
      }
    }
  },
  mounted() {
    this.initCharts()
    window.addEventListener('resize', this.resizeCharts)
    this.fetchStatistics()
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.resizeCharts)
    if (this.trendChart) {
      this.trendChart.dispose()
    }
    if (this.channelChart) {
      this.channelChart.dispose()
    }
  },
  watch: {
    trendTimeRange() {
      this.updateTrendChart()
    },
    channelTimeRange() {
      this.updateChannelChart()
    }
  },
  methods: {
    // 初始化图表
    initCharts() {
      // 初始化趋势图
      this.trendChart = echarts.init(this.$refs.trendChart)
      this.updateTrendChart()
      
      // 初始化渠道分布图
      this.channelChart = echarts.init(this.$refs.channelChart)
      this.updateChannelChart()
    },
    
    // 更新趋势图
    updateTrendChart() {
      let xAxisData = []
      let seriesData = []
      
      if (this.trendTimeRange === 'week') {
        xAxisData = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
        seriesData = [120, 132, 101, 134, 90, 30, 20]
      } else if (this.trendTimeRange === 'month') {
        xAxisData = Array.from({length: 30}, (_, i) => (i + 1) + '日')
        seriesData = Array.from({length: 30}, () => Math.floor(Math.random() * 150) + 50)
      } else {
        xAxisData = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
        seriesData = [320, 302, 301, 334, 390, 330, 320, 301, 302, 331, 340, 310]
      }
      
      const option = {
        tooltip: {
          trigger: 'axis',
          axisPointer: {
            type: 'shadow'
          }
        },
        grid: {
          left: '3%',
          right: '4%',
          bottom: '3%',
          containLabel: true
        },
        xAxis: {
          type: 'category',
          data: xAxisData,
          axisTick: {
            alignWithLabel: true
          }
        },
        yAxis: {
          type: 'value'
        },
        series: [
          {
            name: '发送数量',
            type: 'bar',
            barWidth: '60%',
            data: seriesData,
            itemStyle: {
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: '#83bff6' },
                { offset: 0.5, color: '#188df0' },
                { offset: 1, color: '#188df0' }
              ])
            },
            emphasis: {
              itemStyle: {
                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                  { offset: 0, color: '#2378f7' },
                  { offset: 0.7, color: '#2378f7' },
                  { offset: 1, color: '#83bff6' }
                ])
              }
            }
          }
        ]
      }
      
      this.trendChart.setOption(option)
    },
    
    // 更新渠道分布图
    updateChannelChart() {
      let data = []
      
      if (this.channelTimeRange === 'week') {
        data = [
          { value: 40, name: '站内信' },
          { value: 30, name: '短信' },
          { value: 20, name: '邮件' },
          { value: 10, name: '推送' }
        ]
      } else if (this.channelTimeRange === 'month') {
        data = [
          { value: 35, name: '站内信' },
          { value: 25, name: '短信' },
          { value: 30, name: '邮件' },
          { value: 10, name: '推送' }
        ]
      } else {
        data = [
          { value: 30, name: '站内信' },
          { value: 25, name: '短信' },
          { value: 25, name: '邮件' },
          { value: 20, name: '推送' }
        ]
      }
      
      const option = {
        tooltip: {
          trigger: 'item',
          formatter: '{a} <br/>{b}: {c} ({d}%)'
        },
        legend: {
          orient: 'vertical',
          right: 10,
          top: 'center',
          data: ['站内信', '短信', '邮件', '推送']
        },
        series: [
          {
            name: '通知渠道',
            type: 'pie',
            radius: ['50%', '70%'],
            avoidLabelOverlap: false,
            label: {
              show: false,
              position: 'center'
            },
            emphasis: {
              label: {
                show: true,
                fontSize: '18',
                fontWeight: 'bold'
              }
            },
            labelLine: {
              show: false
            },
            data: data,
            itemStyle: {
              normal: {
                color: function(params) {
                  const colorList = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C']
                  return colorList[params.dataIndex]
                }
              }
            }
          }
        ]
      }
      
      this.channelChart.setOption(option)
    },
    
    // 调整图表大小
    resizeCharts() {
      if (this.trendChart) {
        this.trendChart.resize()
      }
      if (this.channelChart) {
        this.channelChart.resize()
      }
    },
    
    // 获取统计数据
    fetchStatistics() {
      // 实际项目中的API调用
      // getNotificationStats().then(response => {
      //   const data = response.data
      //   this.statisticsCards[0].value = data.todaySent
      //   this.statisticsCards[1].value = data.readRate + '%'
      //   this.statisticsCards[2].value = data.templateCount
      //   this.statisticsCards[3].value = data.unreadCount
      // })
    },
    
    // 查看通知详情
    viewDetail(row) {
      this.$router.push({ path: `/notification/detail/${row.id}` })
    },
    
    // 编辑模板
    editTemplate(row) {
      this.$router.push({ path: '/notification/template', query: { id: row.id } })
    },
    
    // 查看更多
    viewMore(type) {
      if (type === 'list') {
        this.$router.push({ path: '/notification/list' })
      } else if (type === 'template') {
        this.$router.push({ path: '/notification/template' })
      }
    },
    
    // 导出通知数据
    exportNotificationData() {
      this.$message({
        message: '通知数据导出成功',
        type: 'success'
      })
    },
    
    // 打开设置对话框
    openSettings() {
      this.settingsVisible = true
    },
    
    // 保存设置
    saveSettings() {
      this.$message({
        message: '设置保存成功',
        type: 'success'
      })
      this.settingsVisible = false
    },
    
    // 获取通知类型颜色
    getTypeColor(type) {
      switch (type) {
        case 1: return '#409EFF'
        case 2: return '#67C23A'
        case 3: return '#E6A23C'
        default: return '#909399'
      }
    },
    
    // 获取通知类型标签类型
    getTypeTag(type) {
      switch (type) {
        case 1: return 'primary'
        case 2: return 'success'
        case 3: return 'warning'
        default: return 'info'
      }
    },
    
    // 获取通知类型名称
    getTypeName(type) {
      switch (type) {
        case 1: return '系统通知'
        case 2: return '快递通知'
        case 3: return '活动通知'
        default: return '其他通知'
      }
    },
    
    // 获取通知渠道标签类型
    getChannelTag(channel) {
      switch (channel) {
        case 1: return 'primary'
        case 2: return 'success'
        case 3: return 'warning'
        case 4: return 'danger'
        default: return 'info'
      }
    },
    
    // 获取通知渠道名称
    getChannelName(channel) {
      switch (channel) {
        case 1: return '站内信'
        case 2: return '短信'
        case 3: return '邮件'
        case 4: return '推送'
        default: return '未知'
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.stat-card {
  height: 120px;
  margin-bottom: 20px;
  
  .card-icon {
    float: left;
    font-size: 48px;
    padding: 10px;
  }
  
  .card-content {
    margin-left: 70px;
    
    .card-title {
      font-size: 14px;
      color: #909399;
    }
    
    .card-value {
      font-size: 24px;
      font-weight: bold;
      margin: 10px 0;
    }
    
    .card-footer {
      font-size: 12px;
      color: #909399;
      
      i {
        margin-left: 5px;
      }
    }
  }
}

.chart-container {
  padding: 10px;
}

.notification-item, .template-item {
  padding: 5px 0;
  
  .notification-title, .template-title {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 5px;
    
    span {
      font-weight: bold;
    }
  }
  
  .notification-content, .template-content {
    font-size: 12px;
    color: #606266;
    margin-bottom: 5px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  
  .notification-footer, .template-footer {
    display: flex;
    justify-content: space-between;
    font-size: 12px;
    color: #909399;
  }
}

.quick-actions {
  display: flex;
  justify-content: space-around;
  flex-wrap: wrap;
  
  .el-button {
    margin: 10px;
  }
}

.channel-tag {
  margin-right: 5px;
}

.form-help {
  margin-left: 10px;
  color: #909399;
  font-size: 14px;
}
</style>

express-ui\src\views\notification\detail.vue

<template>
  <div class="app-container">
    <el-card class="box-card">
      <div slot="header" class="clearfix">
        <span>通知详情</span>
        <el-button style="float: right; padding: 3px 0" type="text" @click="goBack">返回</el-button>
      </div>
      
      <el-row :gutter="20" v-loading="loading">
        <el-col :span="24">
          <el-descriptions :column="2" border>
            <el-descriptions-item label="通知ID" :span="1">{{ notification.id }}</el-descriptions-item>
            <el-descriptions-item label="通知类型" :span="1">
              <el-tag :type="notification.type === 1 ? 'primary' : notification.type === 2 ? 'success' : 'info'">
                {{ typeFormat(notification) }}
              </el-tag>
            </el-descriptions-item>
            <el-descriptions-item label="通知标题" :span="2">{{ notification.title }}</el-descriptions-item>
            <el-descriptions-item label="通知渠道" :span="1">
              <el-tag v-if="notification.channel === 1" type="primary">站内信</el-tag>
              <el-tag v-else-if="notification.channel === 2" type="success">短信</el-tag>
              <el-tag v-else-if="notification.channel === 3" type="warning">邮件</el-tag>
              <el-tag v-else-if="notification.channel === 4" type="danger">推送</el-tag>
            </el-descriptions-item>
            <el-descriptions-item label="发送状态" :span="1">
              <el-tag :type="notification.status === 0 ? 'info' : notification.status === 1 ? 'success' : 'danger'">
                {{ statusFormat(notification) }}
              </el-tag>
            </el-descriptions-item>
            <el-descriptions-item label="接收人" :span="1">{{ notification.receiverName }}</el-descriptions-item>
            <el-descriptions-item label="发送时间" :span="1">{{ notification.sendTime }}</el-descriptions-item>
            <el-descriptions-item label="通知内容" :span="2">
              <div class="notification-content">{{ notification.content }}</div>
            </el-descriptions-item>
            <el-descriptions-item label="使用模板" :span="2" v-if="notification.templateId">
              <el-link type="primary" @click="viewTemplate">{{ notification.templateName }}</el-link>
            </el-descriptions-item>
          </el-descriptions>
        </el-col>
      </el-row>
      
      <el-divider content-position="center">发送记录</el-divider>
      
      <el-table :data="sendRecords" style="width: 100%" border>
        <el-table-column prop="id" label="记录ID" width="80" align="center" />
        <el-table-column prop="channelName" label="发送渠道" width="120" align="center">
          <template slot-scope="scope">
            <el-tag :type="getChannelTagType(scope.row.channel)">{{ scope.row.channelName }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="sendTime" label="发送时间" width="180" align="center" />
        <el-table-column prop="status" label="发送状态" width="120" align="center">
          <template slot-scope="scope">
            <el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">
              {{ scope.row.status === 1 ? '发送成功' : '发送失败' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="remark" label="备注" />
      </el-table>
      
      <el-divider content-position="center">阅读记录</el-divider>
      
      <el-table :data="readRecords" style="width: 100%" border>
        <el-table-column prop="id" label="记录ID" width="80" align="center" />
        <el-table-column prop="userName" label="用户" width="120" align="center" />
        <el-table-column prop="readTime" label="阅读时间" width="180" align="center" />
        <el-table-column prop="readDevice" label="阅读设备" width="180" align="center" />
        <el-table-column prop="remark" label="备注" />
      </el-table>
      
      <div class="action-buttons">
        <el-button type="primary" icon="el-icon-edit" @click="handleEdit">编辑通知</el-button>
        <el-button type="success" icon="el-icon-refresh" @click="handleResend">重新发送</el-button>
        <el-button type="danger" icon="el-icon-delete" @click="handleDelete">删除通知</el-button>
      </div>
      
      <!-- 模板详情对话框 -->
      <el-dialog title="模板详情" :visible.sync="templateDialogVisible" width="600px" append-to-body>
        <el-descriptions :column="2" border>
          <el-descriptions-item label="模板名称">{{ template.name }}</el-descriptions-item>
          <el-descriptions-item label="模板类型">{{ typeFormat(template) }}</el-descriptions-item>
          <el-descriptions-item label="适用渠道" :span="2">
            <el-tag v-if="template.channel && template.channel.includes(1)" type="primary" class="channel-tag">站内信</el-tag>
            <el-tag v-if="template.channel && template.channel.includes(2)" type="success" class="channel-tag">短信</el-tag>
            <el-tag v-if="template.channel && template.channel.includes(3)" type="warning" class="channel-tag">邮件</el-tag>
            <el-tag v-if="template.channel && template.channel.includes(4)" type="danger" class="channel-tag">推送</el-tag>
          </el-descriptions-item>
          <el-descriptions-item label="创建时间" :span="2">{{ template.createTime }}</el-descriptions-item>
          <el-descriptions-item label="模板内容" :span="2">
            <div style="white-space: pre-wrap;">{{ template.content }}</div>
          </el-descriptions-item>
        </el-descriptions>
        <div slot="footer" class="dialog-footer">
          <el-button @click="templateDialogVisible = false">关闭</el-button>
        </div>
      </el-dialog>
    </el-card>
  </div>
</template>

<script>
import { getNotification, deleteNotification, resendNotification } from '@/api/notification'
import { getTemplate } from '@/api/notification'

export default {
  name: 'NotificationDetail',
  data() {
    return {
      loading: true,
      notification: {
        id: undefined,
        title: '',
        content: '',
        type: undefined,
        channel: undefined,
        receiverId: undefined,
        receiverName: '',
        sendTime: '',
        status: undefined,
        templateId: undefined,
        templateName: ''
      },
      // 发送记录
      sendRecords: [],
      // 阅读记录
      readRecords: [],
      // 模板详情对话框
      templateDialogVisible: false,
      // 模板信息
      template: {
        id: undefined,
        name: '',
        type: undefined,
        channel: [],
        content: '',
        createTime: ''
      },
      // 通知类型选项
      typeOptions: [
        { value: '1', label: '系统通知' },
        { value: '2', label: '快递通知' },
        { value: '3', label: '活动通知' }
      ],
      // 通知状态选项
      statusOptions: [
        { value: '0', label: '草稿' },
        { value: '1', label: '已发送' },
        { value: '2', label: '发送失败' }
      ]
    }
  },
  created() {
    this.getNotificationDetail()
  },
  methods: {
    // 获取通知详情
    getNotificationDetail() {
      const id = this.$route.params.id
      if (id) {
        this.loading = true
        
        // 模拟数据,实际项目中应该调用API
        setTimeout(() => {
          this.notification = {
            id: id,
            title: '快递到达通知',
            content: '您好,张三,您的快递(顺丰 SF1234567890)已到达校园快递站,取件码:8888,请及时前往领取。',
            type: 2,
            channel: 2,
            receiverId: 2,
            receiverName: '张三',
            sendTime: '2023-04-29 14:30:00',
            status: 1,
            templateId: 2,
            templateName: '快递到达通知模板'
          }
          
          // 模拟发送记录
          this.sendRecords = [
            {
              id: 1,
              channel: 2,
              channelName: '短信',
              sendTime: '2023-04-29 14:30:00',
              status: 1,
              remark: '发送成功'
            },
            {
              id: 2,
              channel: 1,
              channelName: '站内信',
              sendTime: '2023-04-29 14:30:05',
              status: 1,
              remark: '发送成功'
            },
            {
              id: 3,
              channel: 4,
              channelName: '推送',
              sendTime: '2023-04-29 14:30:10',
              status: 1,
              remark: '发送成功'
            }
          ]
          
          // 模拟阅读记录
          this.readRecords = [
            {
              id: 1,
              userName: '张三',
              readTime: '2023-04-29 15:20:30',
              readDevice: 'iPhone 13',
              remark: 'APP内阅读'
            }
          ]
          
          this.loading = false
        }, 500)
        
        // 实际项目中的API调用
        // getNotification(id).then(response => {
        //   this.notification = response.data
        //   this.loading = false
        // })
      }
    },
    
    // 查看模板详情
    viewTemplate() {
      if (this.notification.templateId) {
        // 模拟数据,实际项目中应该调用API
        this.template = {
          id: this.notification.templateId,
          name: '快递到达通知模板',
          type: 2,
          channel: [1, 2, 4],
          content: '您好,{{userName}},您的快递({{expressCompany}} {{expressCode}})已到达校园快递站,取件码:{{pickupCode}},请及时前往领取。',
          createTime: '2023-04-21 14:30:00'
        }
        
        this.templateDialogVisible = true
        
        // 实际项目中的API调用
        // getTemplate(this.notification.templateId).then(response => {
        //   this.template = response.data
        //   this.templateDialogVisible = true
        // })
      }
    },
    
    // 编辑通知
    handleEdit() {
      this.$router.push({ path: '/notification/list', query: { id: this.notification.id, edit: true } })
    },
    
    // 重新发送通知
    handleResend() {
      this.$confirm('确认重新发送该通知?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        // 实际项目中的API调用
        // resendNotification(this.notification.id).then(response => {
        //   this.$message.success('重新发送成功')
        //   this.getNotificationDetail()
        // })
        
        // 模拟发送成功
        this.$message.success('重新发送成功')
        
        // 添加一条新的发送记录
        const newRecord = {
          id: this.sendRecords.length + 1,
          channel: this.notification.channel,
          channelName: this.getChannelName(this.notification.channel),
          sendTime: new Date().toLocaleString(),
          status: 1,
          remark: '重新发送'
        }
        
        this.sendRecords.unshift(newRecord)
      }).catch(() => {})
    },
    
    // 删除通知
    handleDelete() {
      this.$confirm('确认删除该通知?', '警告', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        // 实际项目中的API调用
        // deleteNotification(this.notification.id).then(response => {
        //   this.$message.success('删除成功')
        //   this.goBack()
        // })
        
        // 模拟删除成功
        this.$message.success('删除成功')
        this.goBack()
      }).catch(() => {})
    },
    
    // 返回列表页
    goBack() {
      this.$router.push({ path: '/notification/list' })
    },
    
    // 通知类型字典翻译
    typeFormat(row) {
      return this.selectDictLabel(this.typeOptions, row.type)
    },
    
    // 通知状态字典翻译
    statusFormat(row) {
      return this.selectDictLabel(this.statusOptions, row.status)
    },
    
    // 字典翻译
    selectDictLabel(datas, value) {
      const actions = []
      Object.keys(datas).some(key => {
        if (datas[key].value == value) {
          actions.push(datas[key].label)
          return true
        }
      })
      return actions.join('')
    },
    
    // 获取渠道标签类型
    getChannelTagType(channel) {
      switch (channel) {
        case 1: return 'primary'
        case 2: return 'success'
        case 3: return 'warning'
        case 4: return 'danger'
        default: return 'info'
      }
    },
    
    // 获取渠道名称
    getChannelName(channel) {
      switch (channel) {
        case 1: return '站内信'
        case 2: return '短信'
        case 3: return '邮件'
        case 4: return '推送'
        default: return '未知'
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.notification-content {
  white-space: pre-wrap;
  padding: 10px;
  background-color: #f9f9f9;
  border-radius: 4px;
  min-height: 100px;
}

.action-buttons {
  margin-top: 20px;
  text-align: center;
}

.channel-tag {
  margin-right: 5px;
}

.el-divider {
  margin: 24px 0;
}
</style>

express-ui\src\views\notification\history.vue

<template>
  <div class="app-container">
    <el-card class="box-card">
      <div slot="header" class="clearfix">
        <span>通知历史记录</span>
        <div class="header-operations">
          <el-input
            placeholder="搜索通知内容"
            v-model="searchQuery"
            clearable
            style="width: 200px;"
            @clear="handleClear"
            @keyup.enter.native="handleSearch">
            <el-button slot="append" icon="el-icon-search" @click="handleSearch"></el-button>
          </el-input>
          <el-button type="danger" icon="el-icon-delete" @click="handleBatchDelete" :disabled="selectedNotifications.length === 0">批量删除</el-button>
          <el-button type="primary" icon="el-icon-refresh" @click="refreshList">刷新</el-button>
        </div>
      </div>
      
      <!-- 筛选条件 -->
      <el-form :inline="true" class="filter-container">
        <el-form-item label="通知类型">
          <el-select v-model="filters.type" placeholder="全部类型" clearable @change="handleFilterChange">
            <el-option label="系统通知" :value="1"></el-option>
            <el-option label="快递通知" :value="2"></el-option>
            <el-option label="活动通知" :value="3"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="通知渠道">
          <el-select v-model="filters.channel" placeholder="全部渠道" clearable @change="handleFilterChange">
            <el-option label="站内信" :value="1"></el-option>
            <el-option label="短信" :value="2"></el-option>
            <el-option label="邮件" :value="3"></el-option>
            <el-option label="推送" :value="4"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="阅读状态">
          <el-select v-model="filters.read" placeholder="全部状态" clearable @change="handleFilterChange">
            <el-option label="已读" :value="true"></el-option>
            <el-option label="未读" :value="false"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="时间范围">
          <el-date-picker
            v-model="filters.dateRange"
            type="daterange"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            value-format="yyyy-MM-dd"
            @change="handleFilterChange">
          </el-date-picker>
        </el-form-item>
      </el-form>
      
      <!-- 通知列表 -->
      <el-table
        v-loading="loading"
        :data="notificationList"
        style="width: 100%"
        @selection-change="handleSelectionChange"
        :row-class-name="getRowClassName">
        <el-table-column type="selection" width="55"></el-table-column>
        <el-table-column label="状态" width="60" align="center">
          <template slot-scope="scope">
            <el-badge is-dot :hidden="scope.row.read" class="status-badge">
              <i class="el-icon-message" :class="{ 'read': scope.row.read }"></i>
            </el-badge>
          </template>
        </el-table-column>
        <el-table-column label="类型" width="100">
          <template slot-scope="scope">
            <el-tag :type="getTypeTagType(scope.row.type)" size="small">
              {{ getTypeName(scope.row.type) }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="渠道" width="100">
          <template slot-scope="scope">
            <el-tag :type="getChannelTagType(scope.row.channel)" size="small">
              {{ getChannelName(scope.row.channel) }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="title" label="标题" show-overflow-tooltip></el-table-column>
        <el-table-column prop="sendTime" label="发送时间" width="170" sortable>
          <template slot-scope="scope">
            {{ formatDate(scope.row.sendTime) }}
          </template>
        </el-table-column>
        <el-table-column label="操作" width="200" align="center">
          <template slot-scope="scope">
            <el-button
              v-if="!scope.row.read"
              size="mini"
              type="success"
              @click="markAsRead(scope.row)"
              icon="el-icon-check">
              标为已读
            </el-button>
            <el-button
              size="mini"
              type="primary"
              @click="viewDetail(scope.row)"
              icon="el-icon-view">
              查看
            </el-button>
            <el-button
              size="mini"
              type="danger"
              @click="deleteNotification(scope.row)"
              icon="el-icon-delete">
              删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      
      <!-- 分页 -->
      <div class="pagination-container">
        <el-pagination
          background
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
          :current-page="pagination.currentPage"
          :page-sizes="[10, 20, 30, 50]"
          :page-size="pagination.pageSize"
          layout="total, sizes, prev, pager, next, jumper"
          :total="pagination.total">
        </el-pagination>
      </div>
    </el-card>
    
    <!-- 通知详情对话框 -->
    <el-dialog title="通知详情" :visible.sync="detailDialogVisible" width="600px" append-to-body>
      <div class="notification-detail">
        <div class="detail-header">
          <h3 class="detail-title">{{ currentNotification.title }}</h3>
          <div class="detail-meta">
            <span>
              <i class="el-icon-time"></i>
              {{ formatDate(currentNotification.sendTime) }}
            </span>
            <span>
              <i class="el-icon-user"></i>
              {{ currentNotification.sender }}
            </span>
            <span>
              <el-tag :type="getTypeTagType(currentNotification.type)" size="small">
                {{ getTypeName(currentNotification.type) }}
              </el-tag>
            </span>
            <span>
              <el-tag :type="getChannelTagType(currentNotification.channel)" size="small">
                {{ getChannelName(currentNotification.channel) }}
              </el-tag>
            </span>
          </div>
        </div>
        <div class="detail-content" v-html="formatContent(currentNotification.content)"></div>
        <div v-if="currentNotification.attachments && currentNotification.attachments.length > 0" class="detail-attachments">
          <h4>附件</h4>
          <ul class="attachment-list">
            <li v-for="(attachment, index) in currentNotification.attachments" :key="index" class="attachment-item">
              <i class="el-icon-document"></i>
              <span class="attachment-name">{{ attachment.name }}</span>
              <span class="attachment-size">{{ formatFileSize(attachment.size) }}</span>
              <el-button type="text" @click="downloadAttachment(attachment)">下载</el-button>
            </li>
          </ul>
        </div>
        <div class="detail-actions">
          <el-button v-if="!currentNotification.read" type="success" @click="markAsReadInDialog">标为已读</el-button>
          <el-button type="primary" @click="replyNotification">回复</el-button>
          <el-button type="danger" @click="deleteNotificationInDialog">删除</el-button>
        </div>
      </div>
    </el-dialog>
    
    <!-- 回复对话框 -->
    <el-dialog title="回复通知" :visible.sync="replyDialogVisible" width="500px" append-to-body>
      <el-form :model="replyForm" :rules="replyRules" ref="replyForm" label-width="80px">
        <el-form-item label="标题" prop="title">
          <el-input v-model="replyForm.title" placeholder="请输入回复标题"></el-input>
        </el-form-item>
        <el-form-item label="内容" prop="content">
          <el-input type="textarea" v-model="replyForm.content" :rows="5" placeholder="请输入回复内容"></el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="replyDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="submitReply">发送回复</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { NotificationType, NotificationChannel, getTypeTagType, getChannelTagType, formatContent, formatFileSize } from '@/utils/notification'

export default {
  name: 'NotificationHistory',
  data() {
    return {
      // 加载状态
      loading: false,
      // 搜索关键词
      searchQuery: '',
      // 筛选条件
      filters: {
        type: null,
        channel: null,
        read: null,
        dateRange: null
      },
      // 分页信息
      pagination: {
        currentPage: 1,
        pageSize: 10,
        total: 0
      },
      // 通知列表
      notificationList: [],
      // 选中的通知
      selectedNotifications: [],
      // 当前查看的通知
      currentNotification: {},
      // 对话框显示状态
      detailDialogVisible: false,
      replyDialogVisible: false,
      // 回复表单
      replyForm: {
        title: '',
        content: ''
      },
      // 回复表单校验规则
      replyRules: {
        title: [
          { required: true, message: '请输入回复标题', trigger: 'blur' },
          { min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
        ],
        content: [
          { required: true, message: '请输入回复内容', trigger: 'blur' }
        ]
      }
    }
  },
  created() {
    this.fetchNotifications()
  },
  methods: {
    // 获取通知列表
    fetchNotifications() {
      this.loading = true
      
      // 构建查询参数
      const params = {
        page: this.pagination.currentPage,
        size: this.pagination.pageSize,
        query: this.searchQuery
      }
      
      // 添加筛选条件
      if (this.filters.type !== null) {
        params.type = this.filters.type
      }
      if (this.filters.channel !== null) {
        params.channel = this.filters.channel
      }
      if (this.filters.read !== null) {
        params.read = this.filters.read
      }
      if (this.filters.dateRange) {
        params.startDate = this.filters.dateRange[0]
        params.endDate = this.filters.dateRange[1]
      }
      
      // 实际项目中应该调用API获取通知列表
      // getNotificationHistory(params).then(response => {
      //   this.notificationList = response.data.records
      //   this.pagination.total = response.data.total
      //   this.loading = false
      // })
      
      // 模拟获取数据
      setTimeout(() => {
        // 生成模拟数据
        this.notificationList = this.generateMockData()
        this.pagination.total = 85
        this.loading = false
      }, 500)
    },
    
    // 生成模拟数据
    generateMockData() {
      const result = []
      const types = [1, 2, 3]
      const channels = [1, 2, 3, 4]
      const titles = [
        '系统维护通知',
        '您的快递已到达',
        '校园活动邀请',
        '账号安全提醒',
        '新功能上线通知',
        '快递即将过期提醒',
        '讲座活动预告',
        '系统升级公告'
      ]
      const senders = ['系统管理员', '快递服务', '学生会', '安全中心', '技术部门']
      
      for (let i = 0; i < this.pagination.pageSize; i++) {
        const type = types[Math.floor(Math.random() * types.length)]
        const channel = channels[Math.floor(Math.random() * channels.length)]
        const title = titles[Math.floor(Math.random() * titles.length)]
        const sender = senders[Math.floor(Math.random() * senders.length)]
        const read = Math.random() > 0.3
        
        // 生成随机日期(最近30天内)
        const date = new Date()
        date.setDate(date.getDate() - Math.floor(Math.random() * 30))
        
        // 生成随机内容
        let content = ''
        if (type === 1) {
          content = '尊敬的用户,系统将于2025年4月15日凌晨2:00-4:00进行例行维护,届时系统将暂停服务。给您带来的不便,敬请谅解。'
        } else if (type === 2) {
          content = '您好,您的快递(顺丰速运 - SF1234567890)已到达校园快递中心,请凭取件码 8888 及时领取。取件时间:9:00-18:00。'
        } else {
          content = '诚邀您参加"校园科技创新大赛",时间:2025年4月20日14:00,地点:图书馆报告厅。欢迎踊跃参与!'
        }
        
        // 随机添加附件
        const attachments = []
        if (Math.random() > 0.7) {
          attachments.push({
            name: '附件1.pdf',
            size: Math.floor(Math.random() * 1000000) + 100000,
            url: 'https://example.com/attachment1.pdf'
          })
        }
        
        result.push({
          id: i + 1,
          type,
          channel,
          title,
          content,
          sender,
          read,
          sendTime: date.toISOString(),
          readTime: read ? new Date(date.getTime() + Math.floor(Math.random() * 86400000)).toISOString() : null,
          attachments
        })
      }
      
      // 应用筛选
      return this.applyFilters(result)
    },
    
    // 应用筛选条件
    applyFilters(data) {
      let result = [...data]
      
      // 搜索关键词
      if (this.searchQuery) {
        const query = this.searchQuery.toLowerCase()
        result = result.filter(item => 
          item.title.toLowerCase().includes(query) || 
          item.content.toLowerCase().includes(query)
        )
      }
      
      // 通知类型
      if (this.filters.type !== null) {
        result = result.filter(item => item.type === this.filters.type)
      }
      
      // 通知渠道
      if (this.filters.channel !== null) {
        result = result.filter(item => item.channel === this.filters.channel)
      }
      
      // 阅读状态
      if (this.filters.read !== null) {
        result = result.filter(item => item.read === this.filters.read)
      }
      
      // 日期范围
      if (this.filters.dateRange) {
        const startDate = new Date(this.filters.dateRange[0])
        const endDate = new Date(this.filters.dateRange[1])
        endDate.setHours(23, 59, 59, 999) // 设置为当天结束时间
        
        result = result.filter(item => {
          const sendTime = new Date(item.sendTime)
          return sendTime >= startDate && sendTime <= endDate
        })
      }
      
      return result
    },
    
    // 处理搜索
    handleSearch() {
      this.pagination.currentPage = 1
      this.fetchNotifications()
    },
    
    // 处理清除搜索
    handleClear() {
      this.searchQuery = ''
      this.handleSearch()
    },
    
    // 处理筛选条件变化
    handleFilterChange() {
      this.pagination.currentPage = 1
      this.fetchNotifications()
    },
    
    // 处理页码变化
    handleCurrentChange(page) {
      this.pagination.currentPage = page
      this.fetchNotifications()
    },
    
    // 处理每页显示数量变化
    handleSizeChange(size) {
      this.pagination.pageSize = size
      this.pagination.currentPage = 1
      this.fetchNotifications()
    },
    
    // 处理选择变化
    handleSelectionChange(selection) {
      this.selectedNotifications = selection
    },
    
    // 获取行类名
    getRowClassName({ row }) {
      return row.read ? '' : 'unread-row'
    },
    
    // 刷新列表
    refreshList() {
      this.fetchNotifications()
    },
    
    // 查看通知详情
    viewDetail(notification) {
      this.currentNotification = { ...notification }
      this.detailDialogVisible = true
      
      // 如果是未读通知,自动标记为已读
      if (!notification.read) {
        this.markAsRead(notification, false)
      }
    },
    
    // 标记为已读
    markAsRead(notification, showMessage = true) {
      // 实际项目中应该调用API标记为已读
      // markNotificationAsRead(notification.id).then(response => {
      //   notification.read = true
      //   if (showMessage) {
      //     this.$message.success('已标记为已读')
      //   }
      // })
      
      // 模拟标记为已读
      notification.read = true
      notification.readTime = new Date().toISOString()
      
      if (showMessage) {
        this.$message({
          message: '已标记为已读',
          type: 'success'
        })
      }
    },
    
    // 在对话框中标记为已读
    markAsReadInDialog() {
      this.markAsRead(this.currentNotification)
      this.currentNotification.read = true
      this.currentNotification.readTime = new Date().toISOString()
    },
    
    // 删除通知
    deleteNotification(notification) {
      this.$confirm('确定要删除此通知吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        // 实际项目中应该调用API删除通知
        // deleteNotification(notification.id).then(response => {
        //   this.$message.success('删除成功')
        //   this.fetchNotifications()
        // })
        
        // 模拟删除
        const index = this.notificationList.findIndex(item => item.id === notification.id)
        if (index !== -1) {
          this.notificationList.splice(index, 1)
        }
        
        this.$message({
          message: '删除成功',
          type: 'success'
        })
      }).catch(() => {})
    },
    
    // 在对话框中删除通知
    deleteNotificationInDialog() {
      this.deleteNotification(this.currentNotification)
      this.detailDialogVisible = false
    },
    
    // 批量删除通知
    handleBatchDelete() {
      if (this.selectedNotifications.length === 0) {
        return
      }
      
      this.$confirm(`确定要删除选中的 ${this.selectedNotifications.length} 条通知吗?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        // 实际项目中应该调用API批量删除通知
        // const ids = this.selectedNotifications.map(item => item.id)
        // batchDeleteNotifications(ids).then(response => {
        //   this.$message.success('批量删除成功')
        //   this.fetchNotifications()
        // })
        
        // 模拟批量删除
        const ids = this.selectedNotifications.map(item => item.id)
        this.notificationList = this.notificationList.filter(item => !ids.includes(item.id))
        this.selectedNotifications = []
        
        this.$message({
          message: '批量删除成功',
          type: 'success'
        })
      }).catch(() => {})
    },
    
    // 回复通知
    replyNotification() {
      this.replyForm = {
        title: `回复:${this.currentNotification.title}`,
        content: ''
      }
      this.replyDialogVisible = true
    },
    
    // 提交回复
    submitReply() {
      this.$refs.replyForm.validate(valid => {
        if (valid) {
          // 实际项目中应该调用API发送回复
          // const data = {
          //   originalId: this.currentNotification.id,
          //   title: this.replyForm.title,
          //   content: this.replyForm.content
          // }
          // replyNotification(data).then(response => {
          //   this.$message.success('回复已发送')
          //   this.replyDialogVisible = false
          // })
          
          // 模拟发送回复
          this.$message({
            message: '回复已发送',
            type: 'success'
          })
          this.replyDialogVisible = false
        }
      })
    },
    
    // 下载附件
    downloadAttachment(attachment) {
      // 实际项目中应该调用API下载附件
      // window.open(attachment.url, '_blank')
      
      // 模拟下载
      this.$message({
        message: '开始下载:' + attachment.name,
        type: 'success'
      })
    },
    
    // 格式化日期
    formatDate(dateString) {
      if (!dateString) return ''
      
      const date = new Date(dateString)
      const year = date.getFullYear()
      const month = String(date.getMonth() + 1).padStart(2, '0')
      const day = String(date.getDate()).padStart(2, '0')
      const hours = String(date.getHours()).padStart(2, '0')
      const minutes = String(date.getMinutes()).padStart(2, '0')
      
      return `${year}-${month}-${day} ${hours}:${minutes}`
    },
    
    // 获取通知类型名称
    getTypeName(type) {
      switch (type) {
        case 1: return '系统通知'
        case 2: return '快递通知'
        case 3: return '活动通知'
        default: return '其他通知'
      }
    },
    
    // 获取通知渠道名称
    getChannelName(channel) {
      switch (channel) {
        case 1: return '站内信'
        case 2: return '短信'
        case 3: return '邮件'
        case 4: return '推送'
        default: return '其他'
      }
    },
    
    // 获取通知类型对应的标签类型
    getTypeTagType,
    
    // 获取通知渠道对应的标签类型
    getChannelTagType,
    
    // 格式化内容
    formatContent,
    
    // 格式化文件大小
    formatFileSize
  }
}
</script>

<style lang="scss" scoped>
.header-operations {
  float: right;
  display: flex;
  align-items: center;
  
  .el-button {
    margin-left: 10px;
  }
}

.filter-container {
  margin-bottom: 20px;
  padding: 15px;
  background-color: #f5f7fa;
  border-radius: 4px;
}

.pagination-container {
  margin-top: 20px;
  text-align: center;
}

.status-badge {
  i {
    font-size: 18px;
    color: #409EFF;
    
    &.read {
      color: #909399;
    }
  }
}

.notification-detail {
  .detail-header {
    margin-bottom: 20px;
    
    .detail-title {
      margin: 0 0 10px 0;
      font-size: 18px;
    }
    
    .detail-meta {
      display: flex;
      flex-wrap: wrap;
      color: #909399;
      font-size: 14px;
      
      span {
        margin-right: 15px;
        margin-bottom: 5px;
        display: flex;
        align-items: center;
        
        i {
          margin-right: 5px;
        }
      }
    }
  }
  
  .detail-content {
    padding: 15px;
    background-color: #f5f7fa;
    border-radius: 4px;
    min-height: 100px;
    line-height: 1.6;
    white-space: pre-wrap;
    margin-bottom: 20px;
  }
  
  .detail-attachments {
    margin-bottom: 20px;
    
    h4 {
      margin: 0 0 10px 0;
      font-size: 16px;
    }
    
    .attachment-list {
      list-style: none;
      padding: 0;
      margin: 0;
      
      .attachment-item {
        display: flex;
        align-items: center;
        padding: 8px 0;
        border-bottom: 1px solid #ebeef5;
        
        &:last-child {
          border-bottom: none;
        }
        
        i {
          margin-right: 10px;
          color: #909399;
        }
        
        .attachment-name {
          flex: 1;
        }
        
        .attachment-size {
          color: #909399;
          margin: 0 10px;
        }
      }
    }
  }
  
  .detail-actions {
    text-align: right;
  }
}
</style>

<style>
.unread-row {
  font-weight: bold;
  background-color: #f0f9eb;
}
</style>

express-ui\src\views\notification\list.vue

<template>
  <div class="app-container">
    <el-card class="box-card">
      <div slot="header" class="clearfix">
        <span>通知列表</span>
        <el-button style="float: right; padding: 3px 0" type="text" @click="handleAdd">新增通知</el-button>
      </div>
      
      <!-- 搜索区域 -->
      <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
        <el-form-item label="标题" prop="title">
          <el-input v-model="queryParams.title" placeholder="请输入通知标题" clearable size="small" @keyup.enter.native="handleQuery" />
        </el-form-item>
        <el-form-item label="类型" prop="type">
          <el-select v-model="queryParams.type" placeholder="通知类型" clearable size="small">
            <el-option v-for="dict in typeOptions" :key="dict.value" :label="dict.label" :value="dict.value" />
          </el-select>
        </el-form-item>
        <el-form-item label="状态" prop="status">
          <el-select v-model="queryParams.status" placeholder="通知状态" clearable size="small">
            <el-option v-for="dict in statusOptions" :key="dict.value" :label="dict.label" :value="dict.value" />
          </el-select>
        </el-form-item>
        <el-form-item label="发送时间">
          <el-date-picker
            v-model="dateRange"
            size="small"
            type="daterange"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            value-format="yyyy-MM-dd"
          />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
          <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
        </el-form-item>
      </el-form>

      <!-- 表格工具栏 -->
      <el-row :gutter="10" class="mb8">
        <el-col :span="1.5">
          <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新增</el-button>
        </el-col>
        <el-col :span="1.5">
          <el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate">修改</el-button>
        </el-col>
        <el-col :span="1.5">
          <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete">删除</el-button>
        </el-col>
        <el-col :span="1.5">
          <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport">导出</el-button>
        </el-col>
        <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>

      <!-- 数据表格 -->
      <el-table v-loading="loading" :data="notificationList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="ID" align="center" prop="id" width="80" />
        <el-table-column label="标题" align="center" prop="title" :show-overflow-tooltip="true" />
        <el-table-column label="通知类型" align="center" prop="type">
          <template slot-scope="scope">
            <el-tag :type="scope.row.type === 1 ? 'primary' : scope.row.type === 2 ? 'success' : 'info'">
              {{ typeFormat(scope.row) }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="通知渠道" align="center" prop="channel">
          <template slot-scope="scope">
            <el-tag v-if="scope.row.channel === 1" type="primary">站内信</el-tag>
            <el-tag v-else-if="scope.row.channel === 2" type="success">短信</el-tag>
            <el-tag v-else-if="scope.row.channel === 3" type="warning">邮件</el-tag>
            <el-tag v-else-if="scope.row.channel === 4" type="danger">推送</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="接收人" align="center" prop="receiverName" :show-overflow-tooltip="true" />
        <el-table-column label="发送时间" align="center" prop="sendTime" width="160">
          <template slot-scope="scope">
            <span>{{ scope.row.sendTime }}</span>
          </template>
        </el-table-column>
        <el-table-column label="状态" align="center" prop="status">
          <template slot-scope="scope">
            <el-tag :type="scope.row.status === 0 ? 'info' : scope.row.status === 1 ? 'success' : 'danger'">
              {{ statusFormat(scope.row) }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
          <template slot-scope="scope">
            <el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)">查看</el-button>
            <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)">修改</el-button>
            <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      
      <!-- 分页 -->
      <pagination
        v-show="total > 0"
        :total="total"
        :page.sync="queryParams.pageNum"
        :limit.sync="queryParams.pageSize"
        @pagination="getList"
      />

      <!-- 添加或修改通知对话框 -->
      <el-dialog :title="title" :visible.sync="open" width="780px" append-to-body>
        <el-form ref="form" :model="form" :rules="rules" label-width="100px">
          <el-row>
            <el-col :span="12">
              <el-form-item label="通知标题" prop="title">
                <el-input v-model="form.title" placeholder="请输入通知标题" />
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="通知类型" prop="type">
                <el-select v-model="form.type" placeholder="请选择通知类型">
                  <el-option v-for="dict in typeOptions" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)" />
                </el-select>
              </el-form-item>
            </el-col>
          </el-row>
          <el-row>
            <el-col :span="12">
              <el-form-item label="通知渠道" prop="channel">
                <el-select v-model="form.channel" placeholder="请选择通知渠道">
                  <el-option label="站内信" :value="1" />
                  <el-option label="短信" :value="2" />
                  <el-option label="邮件" :value="3" />
                  <el-option label="推送" :value="4" />
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="接收人" prop="receiverId">
                <el-select v-model="form.receiverId" placeholder="请选择接收人" filterable>
                  <el-option v-for="item in userOptions" :key="item.userId" :label="item.userName" :value="item.userId" />
                </el-select>
              </el-form-item>
            </el-col>
          </el-row>
          <el-row>
            <el-col :span="24">
              <el-form-item label="通知内容" prop="content">
                <el-input v-model="form.content" type="textarea" placeholder="请输入通知内容" :rows="8" />
              </el-form-item>
            </el-col>
          </el-row>
          <el-row>
            <el-col :span="12">
              <el-form-item label="发送时间" prop="sendTime">
                <el-date-picker v-model="form.sendTime" type="datetime" placeholder="选择发送时间" value-format="yyyy-MM-dd HH:mm:ss" />
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="状态" prop="status">
                <el-radio-group v-model="form.status">
                  <el-radio :label="0">草稿</el-radio>
                  <el-radio :label="1">已发送</el-radio>
                  <el-radio :label="2">发送失败</el-radio>
                </el-radio-group>
              </el-form-item>
            </el-col>
          </el-row>
        </el-form>
        <div slot="footer" class="dialog-footer">
          <el-button type="primary" @click="submitForm">确 定</el-button>
          <el-button @click="cancel">取 消</el-button>
        </div>
      </el-dialog>

      <!-- 通知详情对话框 -->
      <el-dialog title="通知详情" :visible.sync="openView" width="700px" append-to-body>
        <el-descriptions :column="2" border>
          <el-descriptions-item label="通知标题">{{ form.title }}</el-descriptions-item>
          <el-descriptions-item label="通知类型">{{ typeFormat(form) }}</el-descriptions-item>
          <el-descriptions-item label="通知渠道">
            <el-tag v-if="form.channel === 1" type="primary">站内信</el-tag>
            <el-tag v-else-if="form.channel === 2" type="success">短信</el-tag>
            <el-tag v-else-if="form.channel === 3" type="warning">邮件</el-tag>
            <el-tag v-else-if="form.channel === 4" type="danger">推送</el-tag>
          </el-descriptions-item>
          <el-descriptions-item label="接收人">{{ form.receiverName }}</el-descriptions-item>
          <el-descriptions-item label="发送时间" :span="1">{{ form.sendTime }}</el-descriptions-item>
          <el-descriptions-item label="状态" :span="1">
            <el-tag :type="form.status === 0 ? 'info' : form.status === 1 ? 'success' : 'danger'">
              {{ statusFormat(form) }}
            </el-tag>
          </el-descriptions-item>
          <el-descriptions-item label="通知内容" :span="2">
            <div style="white-space: pre-wrap;">{{ form.content }}</div>
          </el-descriptions-item>
        </el-descriptions>
        <div slot="footer" class="dialog-footer">
          <el-button @click="openView = false">关 闭</el-button>
        </div>
      </el-dialog>
    </el-card>
  </div>
</template>

<script>
import { listNotification, getNotification, delNotification, addNotification, updateNotification } from '@/api/notification'

export default {
  name: 'NotificationList',
  data() {
    return {
      // 遮罩层
      loading: true,
      // 选中数组
      ids: [],
      // 非单个禁用
      single: true,
      // 非多个禁用
      multiple: true,
      // 显示搜索条件
      showSearch: true,
      // 总条数
      total: 0,
      // 通知表格数据
      notificationList: [],
      // 弹出层标题
      title: '',
      // 是否显示弹出层
      open: false,
      // 是否显示详情弹出层
      openView: false,
      // 日期范围
      dateRange: [],
      // 查询参数
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        title: undefined,
        type: undefined,
        status: undefined
      },
      // 表单参数
      form: {},
      // 表单校验
      rules: {
        title: [
          { required: true, message: '通知标题不能为空', trigger: 'blur' }
        ],
        content: [
          { required: true, message: '通知内容不能为空', trigger: 'blur' }
        ],
        type: [
          { required: true, message: '通知类型不能为空', trigger: 'change' }
        ],
        channel: [
          { required: true, message: '通知渠道不能为空', trigger: 'change' }
        ],
        receiverId: [
          { required: true, message: '接收人不能为空', trigger: 'change' }
        ]
      },
      // 通知类型选项
      typeOptions: [
        { value: '1', label: '系统通知' },
        { value: '2', label: '快递通知' },
        { value: '3', label: '活动通知' }
      ],
      // 通知状态选项
      statusOptions: [
        { value: '0', label: '草稿' },
        { value: '1', label: '已发送' },
        { value: '2', label: '发送失败' }
      ],
      // 用户选项
      userOptions: [
        { userId: 1, userName: '管理员' },
        { userId: 2, userName: '张三' },
        { userId: 3, userName: '李四' },
        { userId: 4, userName: '王五' }
      ]
    }
  },
  created() {
    this.getList()
  },
  methods: {
    /** 查询通知列表 */
    getList() {
      this.loading = true
      // 模拟数据,实际项目中应该调用API
      this.notificationList = [
        {
          id: 1,
          title: '系统维护通知',
          type: 1,
          channel: 1,
          receiverId: 1,
          receiverName: '管理员',
          content: '尊敬的用户,系统将于2023年5月1日22:00-24:00进行系统维护,届时系统将暂停服务,请提前做好准备。',
          sendTime: '2023-04-28 10:00:00',
          status: 1
        },
        {
          id: 2,
          title: '快递到达通知',
          type: 2,
          channel: 2,
          receiverId: 2,
          receiverName: '张三',
          content: '您的快递已到达校园快递站,请及时前往领取。',
          sendTime: '2023-04-29 14:30:00',
          status: 1
        },
        {
          id: 3,
          title: '活动邀请',
          type: 3,
          channel: 3,
          receiverId: 3,
          receiverName: '李四',
          content: '诚邀您参加校园快递服务满意度调查活动,完成问卷可获得积分奖励。',
          sendTime: '2023-04-30 09:15:00',
          status: 0
        }
      ]
      this.total = this.notificationList.length
      this.loading = false
      
      // 实际项目中的API调用
      // listNotification(this.queryParams).then(response => {
      //   this.notificationList = response.data.rows
      //   this.total = response.data.total
      //   this.loading = false
      // })
    },
    // 通知类型字典翻译
    typeFormat(row) {
      return this.selectDictLabel(this.typeOptions, row.type)
    },
    // 通知状态字典翻译
    statusFormat(row) {
      return this.selectDictLabel(this.statusOptions, row.status)
    },
    // 字典翻译
    selectDictLabel(datas, value) {
      const actions = []
      Object.keys(datas).some(key => {
        if (datas[key].value == value) {
          actions.push(datas[key].label)
          return true
        }
      })
      return actions.join('')
    },
    /** 搜索按钮操作 */
    handleQuery() {
      this.queryParams.pageNum = 1
      this.getList()
    },
    /** 重置按钮操作 */
    resetQuery() {
      this.dateRange = []
      this.resetForm('queryForm')
      this.handleQuery()
    },
    /** 新增按钮操作 */
    handleAdd() {
      this.reset()
      this.open = true
      this.title = '添加通知'
    },
    /** 修改按钮操作 */
    handleUpdate(row) {
      this.reset()
      const id = row.id || this.ids[0]
      // 实际项目中应该调用API获取详情
      // getNotification(id).then(response => {
      //   this.form = response.data
      //   this.open = true
      //   this.title = '修改通知'
      // })
      
      // 模拟数据
      this.form = JSON.parse(JSON.stringify(row))
      this.open = true
      this.title = '修改通知'
    },
    /** 查看详情按钮操作 */
    handleView(row) {
      this.reset()
      const id = row.id
      // 实际项目中应该调用API获取详情
      // getNotification(id).then(response => {
      //   this.form = response.data
      //   this.openView = true
      // })
      
      // 模拟数据
      this.form = JSON.parse(JSON.stringify(row))
      this.openView = true
    },
    /** 提交按钮 */
    submitForm() {
      this.$refs['form'].validate(valid => {
        if (valid) {
          if (this.form.id) {
            // updateNotification(this.form).then(response => {
            //   this.$modal.msgSuccess('修改成功')
            //   this.open = false
            //   this.getList()
            // })
            this.$message.success('修改成功')
            this.open = false
            this.getList()
          } else {
            // addNotification(this.form).then(response => {
            //   this.$modal.msgSuccess('新增成功')
            //   this.open = false
            //   this.getList()
            // })
            this.$message.success('新增成功')
            this.open = false
            this.getList()
          }
        }
      })
    },
    /** 删除按钮操作 */
    handleDelete(row) {
      const ids = row.id || this.ids
      this.$confirm('是否确认删除通知编号为"' + ids + '"的数据项?', '警告', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        // delNotification(ids).then(() => {
        //   this.getList()
        //   this.$modal.msgSuccess('删除成功')
        // })
        this.$message.success('删除成功')
        this.getList()
      }).catch(() => {})
    },
    /** 导出按钮操作 */
    handleExport() {
      this.$confirm('是否确认导出所有通知数据项?', '警告', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        this.$message.success('导出成功')
      }).catch(() => {})
    },
    // 多选框选中数据
    handleSelectionChange(selection) {
      this.ids = selection.map(item => item.id)
      this.single = selection.length !== 1
      this.multiple = !selection.length
    },
    /** 重置表单数据 */
    reset() {
      this.form = {
        id: undefined,
        title: undefined,
        content: undefined,
        type: 1,
        channel: 1,
        receiverId: undefined,
        receiverName: undefined,
        sendTime: undefined,
        status: 0
      }
      this.resetForm('form')
    },
    /** 取消按钮 */
    cancel() {
      this.open = false
      this.reset()
    }
  }
}
</script>

express-ui\src\views\notification\my-notifications.vue

<template>
  <div class="app-container">
    <el-card class="box-card">
      <div slot="header" class="clearfix">
        <span>我的通知</span>
        <div class="header-operations">
          <el-button type="text" @click="markAllAsRead" v-if="unreadCount > 0">全部标为已读</el-button>
          <el-button type="text" @click="refreshNotifications">刷新</el-button>
        </div>
      </div>
      
      <el-tabs v-model="activeTab" @tab-click="handleTabClick">
        <el-tab-pane label="全部通知" name="all">
          <div class="tab-content">
            <el-empty v-if="notifications.length === 0" description="暂无通知"></el-empty>
            <notification-list v-else :notifications="notifications" @view="viewNotification" @mark-read="markAsRead" />
          </div>
        </el-tab-pane>
        <el-tab-pane :label="'未读通知 (' + unreadCount + ')'" name="unread">
          <div class="tab-content">
            <el-empty v-if="unreadNotifications.length === 0" description="暂无未读通知"></el-empty>
            <notification-list v-else :notifications="unreadNotifications" @view="viewNotification" @mark-read="markAsRead" />
          </div>
        </el-tab-pane>
        <el-tab-pane label="系统通知" name="system">
          <div class="tab-content">
            <el-empty v-if="systemNotifications.length === 0" description="暂无系统通知"></el-empty>
            <notification-list v-else :notifications="systemNotifications" @view="viewNotification" @mark-read="markAsRead" />
          </div>
        </el-tab-pane>
        <el-tab-pane label="快递通知" name="express">
          <div class="tab-content">
            <el-empty v-if="expressNotifications.length === 0" description="暂无快递通知"></el-empty>
            <notification-list v-else :notifications="expressNotifications" @view="viewNotification" @mark-read="markAsRead" />
          </div>
        </el-tab-pane>
        <el-tab-pane label="活动通知" name="activity">
          <div class="tab-content">
            <el-empty v-if="activityNotifications.length === 0" description="暂无活动通知"></el-empty>
            <notification-list v-else :notifications="activityNotifications" @view="viewNotification" @mark-read="markAsRead" />
          </div>
        </el-tab-pane>
      </el-tabs>
      
      <div class="pagination-container">
        <el-pagination
          background
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
          :current-page="queryParams.pageNum"
          :page-sizes="[10, 20, 30, 50]"
          :page-size="queryParams.pageSize"
          layout="total, sizes, prev, pager, next, jumper"
          :total="total">
        </el-pagination>
      </div>
    </el-card>
    
    <!-- 通知详情对话框 -->
    <el-dialog :title="currentNotification.title" :visible.sync="detailDialogVisible" width="600px" append-to-body>
      <el-card class="notification-detail-card" v-loading="detailLoading">
        <div class="notification-meta">
          <div class="meta-item">
            <i class="el-icon-message"></i>
            <span>{{ getTypeName(currentNotification.type) }}</span>
          </div>
          <div class="meta-item">
            <i class="el-icon-time"></i>
            <span>{{ currentNotification.sendTime }}</span>
          </div>
          <div class="meta-item">
            <i class="el-icon-user"></i>
            <span>{{ currentNotification.senderName }}</span>
          </div>
          <div class="meta-item">
            <i class="el-icon-position"></i>
            <span>{{ getChannelName(currentNotification.channel) }}</span>
          </div>
        </div>
        
        <div class="notification-content">
          <div v-html="formatContent(currentNotification.content)"></div>
        </div>
        
        <div class="notification-actions" v-if="currentNotification.actions && currentNotification.actions.length > 0">
          <div class="action-title">可执行操作:</div>
          <div class="action-buttons">
            <el-button 
              v-for="action in currentNotification.actions" 
              :key="action.id"
              :type="action.type || 'primary'"
              size="small"
              @click="handleAction(action)">
              {{ action.name }}
            </el-button>
          </div>
        </div>
        
        <div class="notification-attachments" v-if="currentNotification.attachments && currentNotification.attachments.length > 0">
          <div class="attachment-title">附件:</div>
          <div class="attachment-list">
            <div 
              v-for="attachment in currentNotification.attachments" 
              :key="attachment.id"
              class="attachment-item"
              @click="downloadAttachment(attachment)">
              <i class="el-icon-document"></i>
              <span>{{ attachment.name }}</span>
              <span class="attachment-size">({{ formatFileSize(attachment.size) }})</span>
            </div>
          </div>
        </div>
      </el-card>
      <div slot="footer" class="dialog-footer">
        <el-button @click="detailDialogVisible = false">关闭</el-button>
        <el-button type="primary" @click="handleReply" v-if="currentNotification.canReply">回复</el-button>
      </div>
    </el-dialog>
    
    <!-- 回复对话框 -->
    <el-dialog title="回复通知" :visible.sync="replyDialogVisible" width="500px" append-to-body>
      <el-form :model="replyForm" :rules="replyRules" ref="replyForm" label-width="80px">
        <el-form-item label="回复内容" prop="content">
          <el-input 
            type="textarea" 
            v-model="replyForm.content" 
            :rows="4"
            placeholder="请输入回复内容">
          </el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="replyDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="submitReply">发送</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { listUnreadNotifications, markAsRead, markAllAsRead } from '@/api/notification'

// 通知列表组件
const NotificationList = {
  props: {
    notifications: {
      type: Array,
      required: true
    }
  },
  methods: {
    viewNotification(notification) {
      this.$emit('view', notification)
    },
    markAsRead(notification) {
      this.$emit('mark-read', notification)
    }
  },
  render(h) {
    return h('div', { class: 'notification-list' }, this.notifications.map(notification => {
      return h('div', {
        class: ['notification-item', { 'unread': !notification.read }],
        key: notification.id
      }, [
        // 左侧图标
        h('div', { class: 'notification-icon' }, [
          h('el-avatar', {
            props: {
              size: 40,
              icon: 'el-icon-message'
            },
            style: {
              backgroundColor: this.getTypeColor(notification.type)
            }
          })
        ]),
        // 中间内容
        h('div', { class: 'notification-content' }, [
          h('div', { class: 'notification-header' }, [
            h('span', { class: 'notification-title' }, notification.title),
            h('el-tag', {
              props: {
                size: 'mini',
                type: this.getTypeTag(notification.type)
              }
            }, this.getTypeName(notification.type))
          ]),
          h('div', { class: 'notification-body' }, notification.content),
          h('div', { class: 'notification-footer' }, [
            h('span', { class: 'notification-time' }, notification.sendTime),
            h('span', { class: 'notification-channel' }, [
              h('el-tag', {
                props: {
                  size: 'mini',
                  type: this.getChannelTag(notification.channel)
                }
              }, this.getChannelName(notification.channel))
            ])
          ])
        ]),
        // 右侧操作
        h('div', { class: 'notification-actions' }, [
          h('el-button', {
            props: {
              type: 'text',
              icon: 'el-icon-view',
              size: 'mini'
            },
            on: {
              click: (e) => {
                e.stopPropagation()
                this.viewNotification(notification)
              }
            }
          }, '查看'),
          !notification.read ? h('el-button', {
            props: {
              type: 'text',
              icon: 'el-icon-check',
              size: 'mini'
            },
            on: {
              click: (e) => {
                e.stopPropagation()
                this.markAsRead(notification)
              }
            }
          }, '已读') : null
        ])
      ])
    }))
  },
  methods: {
    getTypeColor(type) {
      switch (type) {
        case 1: return '#409EFF'
        case 2: return '#67C23A'
        case 3: return '#E6A23C'
        default: return '#909399'
      }
    },
    getTypeTag(type) {
      switch (type) {
        case 1: return 'primary'
        case 2: return 'success'
        case 3: return 'warning'
        default: return 'info'
      }
    },
    getTypeName(type) {
      switch (type) {
        case 1: return '系统通知'
        case 2: return '快递通知'
        case 3: return '活动通知'
        default: return '其他通知'
      }
    },
    getChannelTag(channel) {
      switch (channel) {
        case 1: return 'primary'
        case 2: return 'success'
        case 3: return 'warning'
        case 4: return 'danger'
        default: return 'info'
      }
    },
    getChannelName(channel) {
      switch (channel) {
        case 1: return '站内信'
        case 2: return '短信'
        case 3: return '邮件'
        case 4: return '推送'
        default: return '未知'
      }
    }
  }
}

export default {
  name: 'MyNotifications',
  components: {
    NotificationList
  },
  data() {
    return {
      // 激活的标签页
      activeTab: 'all',
      // 查询参数
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        type: null,
        read: null
      },
      // 总记录数
      total: 0,
      // 通知列表
      notifications: [],
      // 未读通知数量
      unreadCount: 0,
      // 详情对话框
      detailDialogVisible: false,
      // 详情加载状态
      detailLoading: false,
      // 当前查看的通知
      currentNotification: {
        id: undefined,
        title: '',
        content: '',
        type: undefined,
        channel: undefined,
        senderName: '',
        sendTime: '',
        read: false,
        canReply: false,
        actions: [],
        attachments: []
      },
      // 回复对话框
      replyDialogVisible: false,
      // 回复表单
      replyForm: {
        notificationId: undefined,
        content: ''
      },
      // 回复表单校验规则
      replyRules: {
        content: [
          { required: true, message: '请输入回复内容', trigger: 'blur' }
        ]
      }
    }
  },
  computed: {
    // 未读通知
    unreadNotifications() {
      return this.notifications.filter(item => !item.read)
    },
    // 系统通知
    systemNotifications() {
      return this.notifications.filter(item => item.type === 1)
    },
    // 快递通知
    expressNotifications() {
      return this.notifications.filter(item => item.type === 2)
    },
    // 活动通知
    activityNotifications() {
      return this.notifications.filter(item => item.type === 3)
    }
  },
  created() {
    this.getNotificationList()
  },
  methods: {
    // 获取通知列表
    getNotificationList() {
      // 实际项目中的API调用
      // listUnreadNotifications(this.queryParams).then(response => {
      //   this.notifications = response.rows
      //   this.total = response.total
      //   this.unreadCount = response.unreadCount
      // })
      
      // 模拟数据
      this.notifications = [
        {
          id: 1,
          title: '系统维护通知',
          content: '系统将于2023年5月1日凌晨2点至4点进行维护,届时系统将暂停服务,请提前做好准备。',
          type: 1,
          channel: 1,
          senderName: '系统管理员',
          sendTime: '2023-04-29 10:30:00',
          read: false,
          canReply: false
        },
        {
          id: 2,
          title: '快递到达通知',
          content: '您的快递(顺丰 SF1234567890)已到达校园快递站,取件码:8888,请及时前往领取。',
          type: 2,
          channel: 2,
          senderName: '快递管理员',
          sendTime: '2023-04-29 09:15:00',
          read: false,
          canReply: true,
          actions: [
            { id: 1, name: '查看快递详情', type: 'primary', url: '/express/detail/123' }
          ]
        },
        {
          id: 3,
          title: '五一活动邀请',
          content: '诚邀您参加五一劳动节文艺汇演活动,时间:2023年5月1日下午2点,地点:校园大礼堂,期待您的参与!',
          type: 3,
          channel: 3,
          senderName: '学生会',
          sendTime: '2023-04-28 16:45:00',
          read: true,
          canReply: true,
          actions: [
            { id: 1, name: '参加', type: 'primary', action: 'join' },
            { id: 2, name: '不参加', type: 'info', action: 'decline' }
          ]
        },
        {
          id: 4,
          title: '教务系统更新通知',
          content: '教务系统已完成版本更新,新增成绩查询、课表导出等功能,欢迎使用。',
          type: 1,
          channel: 4,
          senderName: '教务处',
          sendTime: '2023-04-28 14:20:00',
          read: true,
          canReply: false,
          attachments: [
            { id: 1, name: '教务系统使用手册.pdf', size: 2048000 }
          ]
        },
        {
          id: 5,
          title: '图书馆借阅到期提醒',
          content: '您借阅的《数据结构与算法》将于2023年5月5日到期,请及时归还或续借。',
          type: 1,
          channel: 1,
          senderName: '图书馆',
          sendTime: '2023-04-28 10:00:00',
          read: false,
          canReply: false,
          actions: [
            { id: 1, name: '续借', type: 'primary', action: 'renew' }
          ]
        }
      ]
      
      this.total = this.notifications.length
      this.unreadCount = this.notifications.filter(item => !item.read).length
    },
    
    // 查看通知详情
    viewNotification(notification) {
      this.detailLoading = true
      this.currentNotification = { ...notification }
      this.detailDialogVisible = true
      
      // 如果通知未读,标记为已读
      if (!notification.read) {
        this.markAsRead(notification)
      }
      
      setTimeout(() => {
        this.detailLoading = false
      }, 500)
    },
    
    // 标记通知为已读
    markAsRead(notification) {
      // 实际项目中的API调用
      // markAsRead(notification.id).then(response => {
      //   this.$message.success('已标记为已读')
      //   notification.read = true
      //   this.unreadCount = this.unreadCount - 1
      // })
      
      // 模拟标记为已读
      notification.read = true
      this.unreadCount = this.notifications.filter(item => !item.read).length
    },
    
    // 标记所有通知为已读
    markAllAsRead() {
      this.$confirm('确认将所有未读通知标记为已读吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        // 实际项目中的API调用
        // markAllAsRead().then(response => {
        //   this.$message.success('已全部标记为已读')
        //   this.notifications.forEach(item => {
        //     item.read = true
        //   })
        //   this.unreadCount = 0
        // })
        
        // 模拟全部标记为已读
        this.notifications.forEach(item => {
          item.read = true
        })
        this.unreadCount = 0
        this.$message.success('已全部标记为已读')
      }).catch(() => {})
    },
    
    // 刷新通知列表
    refreshNotifications() {
      this.getNotificationList()
    },
    
    // 处理标签页点击
    handleTabClick(tab) {
      switch (tab.name) {
        case 'all':
          this.queryParams.type = null
          this.queryParams.read = null
          break
        case 'unread':
          this.queryParams.type = null
          this.queryParams.read = false
          break
        case 'system':
          this.queryParams.type = 1
          this.queryParams.read = null
          break
        case 'express':
          this.queryParams.type = 2
          this.queryParams.read = null
          break
        case 'activity':
          this.queryParams.type = 3
          this.queryParams.read = null
          break
      }
      this.queryParams.pageNum = 1
      this.getNotificationList()
    },
    
    // 处理通知操作
    handleAction(action) {
      if (action.url) {
        this.$router.push(action.url)
        this.detailDialogVisible = false
      } else if (action.action) {
        switch (action.action) {
          case 'join':
            this.$message.success('您已成功报名参加活动')
            break
          case 'decline':
            this.$message.info('您已婉拒参加活动')
            break
          case 'renew':
            this.$message.success('续借成功,新的到期日为2023年6月5日')
            break
          default:
            break
        }
      }
    },
    
    // 下载附件
    downloadAttachment(attachment) {
      this.$message.success(`正在下载:${attachment.name}`)
    },
    
    // 回复通知
    handleReply() {
      this.replyForm.notificationId = this.currentNotification.id
      this.replyForm.content = ''
      this.replyDialogVisible = true
    },
    
    // 提交回复
    submitReply() {
      this.$refs.replyForm.validate(valid => {
        if (valid) {
          // 实际项目中的API调用
          // const data = {
          //   notificationId: this.replyForm.notificationId,
          //   content: this.replyForm.content
          // }
          // replyNotification(data).then(response => {
          //   this.$message.success('回复成功')
          //   this.replyDialogVisible = false
          // })
          
          // 模拟回复成功
          this.$message.success('回复成功')
          this.replyDialogVisible = false
        }
      })
    },
    
    // 处理分页大小变化
    handleSizeChange(size) {
      this.queryParams.pageSize = size
      this.getNotificationList()
    },
    
    // 处理页码变化
    handleCurrentChange(page) {
      this.queryParams.pageNum = page
      this.getNotificationList()
    },
    
    // 格式化通知内容
    formatContent(content) {
      if (!content) return ''
      // 将换行符转换为HTML换行
      return content.replace(/\n/g, '<br>')
    },
    
    // 格式化文件大小
    formatFileSize(size) {
      if (size < 1024) {
        return size + ' B'
      } else if (size < 1024 * 1024) {
        return (size / 1024).toFixed(2) + ' KB'
      } else if (size < 1024 * 1024 * 1024) {
        return (size / (1024 * 1024)).toFixed(2) + ' MB'
      } else {
        return (size / (1024 * 1024 * 1024)).toFixed(2) + ' GB'
      }
    },
    
    // 获取通知类型名称
    getTypeName(type) {
      switch (type) {
        case 1: return '系统通知'
        case 2: return '快递通知'
        case 3: return '活动通知'
        default: return '其他通知'
      }
    },
    
    // 获取通知渠道名称
    getChannelName(channel) {
      switch (channel) {
        case 1: return '站内信'
        case 2: return '短信'
        case 3: return '邮件'
        case 4: return '推送'
        default: return '未知'
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.header-operations {
  float: right;
}

.tab-content {
  min-height: 400px;
  padding: 10px 0;
}

.notification-list {
  .notification-item {
    display: flex;
    padding: 15px;
    border-bottom: 1px solid #ebeef5;
    cursor: pointer;
    transition: background-color 0.3s;
    
    &:hover {
      background-color: #f5f7fa;
    }
    
    &.unread {
      background-color: #f0f9eb;
      
      .notification-title {
        font-weight: bold;
      }
    }
    
    .notification-icon {
      flex: 0 0 40px;
      margin-right: 15px;
    }
    
    .notification-content {
      flex: 1;
      overflow: hidden;
      
      .notification-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 5px;
        
        .notification-title {
          font-size: 14px;
          color: #303133;
        }
      }
      
      .notification-body {
        font-size: 13px;
        color: #606266;
        margin-bottom: 5px;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }
      
      .notification-footer {
        display: flex;
        justify-content: space-between;
        font-size: 12px;
        color: #909399;
      }
    }
    
    .notification-actions {
      flex: 0 0 100px;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: flex-end;
    }
  }
}

.pagination-container {
  margin-top: 20px;
  text-align: center;
}

.notification-detail-card {
  .notification-meta {
    display: flex;
    flex-wrap: wrap;
    margin-bottom: 15px;
    padding-bottom: 15px;
    border-bottom: 1px solid #ebeef5;
    
    .meta-item {
      flex: 0 0 50%;
      margin-bottom: 10px;
      
      i {
        margin-right: 5px;
        color: #909399;
      }
    }
  }
  
  .notification-content {
    padding: 15px;
    background-color: #f9f9f9;
    border-radius: 4px;
    min-height: 100px;
    margin-bottom: 15px;
    line-height: 1.6;
  }
  
  .notification-actions {
    margin-bottom: 15px;
    
    .action-title {
      font-weight: bold;
      margin-bottom: 10px;
    }
    
    .action-buttons {
      .el-button {
        margin-right: 10px;
        margin-bottom: 10px;
      }
    }
  }
  
  .notification-attachments {
    .attachment-title {
      font-weight: bold;
      margin-bottom: 10px;
    }
    
    .attachment-list {
      .attachment-item {
        display: inline-flex;
        align-items: center;
        padding: 5px 15px;
        background-color: #f5f7fa;
        border-radius: 4px;
        margin-right: 10px;
        margin-bottom: 10px;
        cursor: pointer;
        
        &:hover {
          background-color: #ecf5ff;
        }
        
        i {
          margin-right: 5px;
          color: #409EFF;
        }
        
        .attachment-size {
          margin-left: 5px;
          color: #909399;
          font-size: 12px;
        }
      }
    }
  }
}
</style>

express-ui\src\views\notification\send.vue

<template>
  <div class="app-container">
    <el-card class="box-card">
      <div slot="header" class="clearfix">
        <span>发送通知</span>
      </div>
      
      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
        <el-row>
          <el-col :span="24">
            <el-form-item label="通知类型" prop="type">
              <el-select v-model="form.type" placeholder="请选择通知类型" @change="handleTypeChange">
                <el-option v-for="dict in typeOptions" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row>
          <el-col :span="24">
            <el-form-item label="通知模板" prop="templateId">
              <el-select v-model="form.templateId" placeholder="请选择通知模板" @change="handleTemplateChange" filterable>
                <el-option 
                  v-for="item in templateOptions" 
                  :key="item.id" 
                  :label="item.name" 
                  :value="item.id"
                  :disabled="form.type && item.type !== form.type"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row>
          <el-col :span="24">
            <el-form-item label="通知标题" prop="title">
              <el-input v-model="form.title" placeholder="请输入通知标题" />
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row>
          <el-col :span="24">
            <el-form-item label="通知渠道" prop="channel">
              <el-checkbox-group v-model="form.channel">
                <el-checkbox :label="1" :disabled="selectedTemplate && !selectedTemplate.channel.includes(1)">站内信</el-checkbox>
                <el-checkbox :label="2" :disabled="selectedTemplate && !selectedTemplate.channel.includes(2)">短信</el-checkbox>
                <el-checkbox :label="3" :disabled="selectedTemplate && !selectedTemplate.channel.includes(3)">邮件</el-checkbox>
                <el-checkbox :label="4" :disabled="selectedTemplate && !selectedTemplate.channel.includes(4)">推送</el-checkbox>
              </el-checkbox-group>
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row>
          <el-col :span="24">
            <el-form-item label="接收对象" prop="receiverType">
              <el-radio-group v-model="form.receiverType" @change="handleReceiverTypeChange">
                <el-radio :label="1">指定用户</el-radio>
                <el-radio :label="2">用户组</el-radio>
                <el-radio :label="3">全部用户</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row v-if="form.receiverType === 1">
          <el-col :span="24">
            <el-form-item label="选择用户" prop="receiverIds">
              <el-select v-model="form.receiverIds" multiple filterable placeholder="请选择用户" style="width: 100%">
                <el-option v-for="item in userOptions" :key="item.userId" :label="item.userName" :value="item.userId" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row v-if="form.receiverType === 2">
          <el-col :span="24">
            <el-form-item label="选择用户组" prop="groupIds">
              <el-select v-model="form.groupIds" multiple filterable placeholder="请选择用户组" style="width: 100%">
                <el-option v-for="item in groupOptions" :key="item.groupId" :label="item.groupName" :value="item.groupId" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row>
          <el-col :span="24">
            <el-form-item label="通知内容" prop="content">
              <el-input v-model="form.content" type="textarea" placeholder="请输入通知内容" :rows="10" />
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row>
          <el-col :span="24">
            <el-form-item label="变量数据" v-if="templateVariables.length > 0">
              <el-card class="variable-card">
                <div v-for="(variable, index) in templateVariables" :key="index" class="variable-item">
                  <el-form-item :label="variable.name" :prop="'variableValues.' + index + '.value'">
                    <el-input v-model="variable.value" :placeholder="'请输入' + variable.name" />
                  </el-form-item>
                </div>
              </el-card>
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row>
          <el-col :span="12">
            <el-form-item label="发送时间" prop="sendTime">
              <el-radio-group v-model="form.sendTimeType" @change="handleSendTimeTypeChange">
                <el-radio :label="1">立即发送</el-radio>
                <el-radio :label="2">定时发送</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
          <el-col :span="12" v-if="form.sendTimeType === 2">
            <el-form-item label="定时时间" prop="scheduledTime">
              <el-date-picker
                v-model="form.scheduledTime"
                type="datetime"
                placeholder="选择日期时间"
                value-format="yyyy-MM-dd HH:mm:ss"
              />
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row>
          <el-col :span="24">
            <el-form-item>
              <el-button type="primary" @click="submitForm">发送通知</el-button>
              <el-button @click="resetForm">重置</el-button>
              <el-button type="success" @click="previewNotification">预览通知</el-button>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      
      <!-- 预览通知对话框 -->
      <el-dialog title="通知预览" :visible.sync="previewVisible" width="600px" append-to-body>
        <el-card class="preview-card">
          <div slot="header" class="clearfix">
            <span>{{ form.title }}</span>
          </div>
          <div class="preview-content">
            <div v-html="previewContent"></div>
          </div>
          <div class="preview-info">
            <p><strong>发送渠道:</strong>
              <el-tag v-if="form.channel.includes(1)" type="primary" class="channel-tag">站内信</el-tag>
              <el-tag v-if="form.channel.includes(2)" type="success" class="channel-tag">短信</el-tag>
              <el-tag v-if="form.channel.includes(3)" type="warning" class="channel-tag">邮件</el-tag>
              <el-tag v-if="form.channel.includes(4)" type="danger" class="channel-tag">推送</el-tag>
            </p>
            <p><strong>接收对象:</strong>
              <span v-if="form.receiverType === 1">指定用户 ({{ getReceiverNames() }})</span>
              <span v-else-if="form.receiverType === 2">用户组 ({{ getGroupNames() }})</span>
              <span v-else-if="form.receiverType === 3">全部用户</span>
            </p>
            <p><strong>发送时间:</strong>
              <span v-if="form.sendTimeType === 1">立即发送</span>
              <span v-else-if="form.sendTimeType === 2">{{ form.scheduledTime }}</span>
            </p>
          </div>
        </el-card>
        <div slot="footer" class="dialog-footer">
          <el-button @click="previewVisible = false">关闭</el-button>
          <el-button type="primary" @click="confirmSend">确认发送</el-button>
        </div>
      </el-dialog>
    </el-card>
  </div>
</template>

<script>
import { sendNotification, getTemplate } from '@/api/notification'

export default {
  name: 'NotificationSend',
  data() {
    return {
      // 表单参数
      form: {
        type: undefined,
        templateId: undefined,
        title: '',
        content: '',
        channel: [1],
        receiverType: 1,
        receiverIds: [],
        groupIds: [],
        sendTimeType: 1,
        scheduledTime: undefined
      },
      // 表单校验
      rules: {
        type: [
          { required: true, message: '请选择通知类型', trigger: 'change' }
        ],
        title: [
          { required: true, message: '请输入通知标题', trigger: 'blur' }
        ],
        content: [
          { required: true, message: '请输入通知内容', trigger: 'blur' }
        ],
        channel: [
          { required: true, message: '请选择通知渠道', trigger: 'change', type: 'array' }
        ],
        receiverIds: [
          { required: true, message: '请选择接收用户', trigger: 'change', type: 'array' }
        ],
        groupIds: [
          { required: true, message: '请选择用户组', trigger: 'change', type: 'array' }
        ],
        scheduledTime: [
          { required: true, message: '请选择定时发送时间', trigger: 'change' }
        ]
      },
      // 通知类型选项
      typeOptions: [
        { value: '1', label: '系统通知' },
        { value: '2', label: '快递通知' },
        { value: '3', label: '活动通知' }
      ],
      // 模板选项
      templateOptions: [
        {
          id: 1,
          name: '系统维护通知模板',
          type: 1,
          channel: [1, 3],
          content: '尊敬的{{userName}},系统将于{{startTime}}至{{endTime}}进行系统维护,届时系统将暂停服务,请提前做好准备。'
        },
        {
          id: 2,
          name: '快递到达通知模板',
          type: 2,
          channel: [1, 2, 4],
          content: '您好,{{userName}},您的快递({{expressCompany}} {{expressCode}})已到达校园快递站,取件码:{{pickupCode}},请及时前往领取。'
        },
        {
          id: 3,
          name: '活动邀请模板',
          type: 3,
          channel: [1, 3],
          content: '亲爱的{{userName}},诚邀您参加{{activityName}}活动,时间:{{activityTime}},地点:{{activityLocation}},期待您的参与!'
        }
      ],
      // 用户选项
      userOptions: [
        { userId: 1, userName: '管理员' },
        { userId: 2, userName: '张三' },
        { userId: 3, userName: '李四' },
        { userId: 4, userName: '王五' }
      ],
      // 用户组选项
      groupOptions: [
        { groupId: 1, groupName: '管理员组' },
        { groupId: 2, groupName: '学生组' },
        { groupId: 3, groupName: '教师组' },
        { groupId: 4, groupName: '后勤组' }
      ],
      // 选中的模板
      selectedTemplate: null,
      // 模板变量
      templateVariables: [],
      // 预览对话框
      previewVisible: false,
      // 预览内容
      previewContent: ''
    }
  },
  watch: {
    'form.receiverType': function(val) {
      if (val === 1) {
        this.rules.receiverIds = [{ required: true, message: '请选择接收用户', trigger: 'change', type: 'array' }]
        this.rules.groupIds = []
      } else if (val === 2) {
        this.rules.groupIds = [{ required: true, message: '请选择用户组', trigger: 'change', type: 'array' }]
        this.rules.receiverIds = []
      } else {
        this.rules.receiverIds = []
        this.rules.groupIds = []
      }
    },
    'form.sendTimeType': function(val) {
      if (val === 2) {
        this.rules.scheduledTime = [{ required: true, message: '请选择定时发送时间', trigger: 'change' }]
      } else {
        this.rules.scheduledTime = []
      }
    }
  },
  methods: {
    // 处理通知类型变化
    handleTypeChange(val) {
      this.form.templateId = undefined
      this.selectedTemplate = null
      this.form.content = ''
      this.templateVariables = []
    },
    
    // 处理模板变化
    handleTemplateChange(val) {
      const template = this.templateOptions.find(item => item.id === val)
      if (template) {
        this.selectedTemplate = template
        this.form.content = template.content
        this.form.channel = template.channel.slice(0, 1) // 默认选择第一个渠道
        
        // 提取模板变量
        this.extractTemplateVariables(template.content)
      }
    },
    
    // 提取模板变量
    extractTemplateVariables(content) {
      const regex = /\{\{([^}]+)\}\}/g
      let match
      const variables = []
      
      while ((match = regex.exec(content)) !== null) {
        variables.push({
          name: match[1].trim(),
          placeholder: match[0],
          value: ''
        })
      }
      
      // 去重
      this.templateVariables = variables.filter((v, i, a) => a.findIndex(t => t.name === v.name) === i)
    },
    
    // 处理接收对象类型变化
    handleReceiverTypeChange(val) {
      if (val === 1) {
        this.form.groupIds = []
      } else if (val === 2) {
        this.form.receiverIds = []
      } else {
        this.form.receiverIds = []
        this.form.groupIds = []
      }
    },
    
    // 处理发送时间类型变化
    handleSendTimeTypeChange(val) {
      if (val === 1) {
        this.form.scheduledTime = undefined
      }
    },
    
    // 预览通知
    previewNotification() {
      this.$refs['form'].validate(valid => {
        if (valid) {
          let content = this.form.content
          
          // 替换模板变量
          this.templateVariables.forEach(variable => {
            if (variable.value) {
              const regex = new RegExp(variable.placeholder, 'g')
              content = content.replace(regex, variable.value)
            }
          })
          
          // 将换行符转换为HTML换行
          this.previewContent = content.replace(/\n/g, '<br>')
          this.previewVisible = true
        }
      })
    },
    
    // 获取接收者名称
    getReceiverNames() {
      if (!this.form.receiverIds || this.form.receiverIds.length === 0) {
        return '无'
      }
      
      return this.form.receiverIds.map(id => {
        const user = this.userOptions.find(item => item.userId === id)
        return user ? user.userName : id
      }).join(', ')
    },
    
    // 获取用户组名称
    getGroupNames() {
      if (!this.form.groupIds || this.form.groupIds.length === 0) {
        return '无'
      }
      
      return this.form.groupIds.map(id => {
        const group = this.groupOptions.find(item => item.groupId === id)
        return group ? group.groupName : id
      }).join(', ')
    },
    
    // 确认发送
    confirmSend() {
      this.submitForm()
    },
    
    // 提交表单
    submitForm() {
      this.$refs['form'].validate(valid => {
        if (valid) {
          // 构建发送参数
          const sendData = {
            ...this.form,
            // 替换模板变量
            content: this.form.content
          }
          
          // 替换模板变量
          this.templateVariables.forEach(variable => {
            if (variable.value) {
              const regex = new RegExp(variable.placeholder, 'g')
              sendData.content = sendData.content.replace(regex, variable.value)
            }
          })
          
          // 实际项目中的API调用
          // sendNotification(sendData).then(response => {
          //   this.$modal.msgSuccess('发送成功')
          //   this.resetForm()
          //   this.previewVisible = false
          // })
          
          // 模拟发送成功
          this.$message.success('通知发送成功')
          this.resetForm()
          this.previewVisible = false
        }
      })
    },
    
    // 重置表单
    resetForm() {
      this.$refs['form'].resetFields()
      this.form = {
        type: undefined,
        templateId: undefined,
        title: '',
        content: '',
        channel: [1],
        receiverType: 1,
        receiverIds: [],
        groupIds: [],
        sendTimeType: 1,
        scheduledTime: undefined
      }
      this.selectedTemplate = null
      this.templateVariables = []
    }
  }
}
</script>

<style lang="scss" scoped>
.channel-tag {
  margin-right: 5px;
}

.variable-card {
  margin-bottom: 20px;
  
  .variable-item {
    margin-bottom: 10px;
    
    &:last-child {
      margin-bottom: 0;
    }
  }
}

.preview-card {
  .preview-content {
    padding: 15px;
    min-height: 150px;
    border: 1px solid #ebeef5;
    border-radius: 4px;
    background-color: #f9f9f9;
    margin-bottom: 15px;
  }
  
  .preview-info {
    padding: 10px;
    border-top: 1px solid #ebeef5;
    
    p {
      margin: 5px 0;
    }
  }
}
</style>

express-ui\src\views\notification\settings.vue

<template>
  <div class="app-container">
    <el-card class="box-card">
      <div slot="header" class="clearfix">
        <span>通知设置</span>
      </div>
      
      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
        <el-tabs v-model="activeTab">
          <el-tab-pane label="通知偏好" name="preferences">
            <el-divider content-position="left">通知接收设置</el-divider>
            
            <el-form-item label="接收通知" prop="receiveNotifications">
              <el-switch v-model="form.receiveNotifications"></el-switch>
              <span class="form-help">关闭后将不会接收任何通知</span>
            </el-form-item>
            
            <el-form-item label="通知渠道">
              <el-checkbox-group v-model="form.enabledChannels">
                <el-checkbox :label="1" :disabled="!form.receiveNotifications">站内信</el-checkbox>
                <el-checkbox :label="2" :disabled="!form.receiveNotifications">短信</el-checkbox>
                <el-checkbox :label="3" :disabled="!form.receiveNotifications">邮件</el-checkbox>
                <el-checkbox :label="4" :disabled="!form.receiveNotifications">推送</el-checkbox>
              </el-checkbox-group>
            </el-form-item>
            
            <el-form-item label="通知类型">
              <el-checkbox-group v-model="form.enabledTypes">
                <el-checkbox :label="1" :disabled="!form.receiveNotifications">系统通知</el-checkbox>
                <el-checkbox :label="2" :disabled="!form.receiveNotifications">快递通知</el-checkbox>
                <el-checkbox :label="3" :disabled="!form.receiveNotifications">活动通知</el-checkbox>
              </el-checkbox-group>
            </el-form-item>
            
            <el-divider content-position="left">通知显示设置</el-divider>
            
            <el-form-item label="桌面通知" prop="desktopNotifications">
              <el-switch v-model="form.desktopNotifications" :disabled="!form.receiveNotifications"></el-switch>
              <span class="form-help">在浏览器中显示桌面通知</span>
            </el-form-item>
            
            <el-form-item label="声音提醒" prop="soundNotifications">
              <el-switch v-model="form.soundNotifications" :disabled="!form.receiveNotifications"></el-switch>
              <span class="form-help">收到通知时播放提示音</span>
            </el-form-item>
            
            <el-form-item label="通知显示数量" prop="maxDisplayCount">
              <el-input-number v-model="form.maxDisplayCount" :min="1" :max="50" :disabled="!form.receiveNotifications"></el-input-number>
              <span class="form-help">导航栏通知中心显示的最大通知数量</span>
            </el-form-item>
          </el-tab-pane>
          
          <el-tab-pane label="时间设置" name="time">
            <el-divider content-position="left">免打扰时间</el-divider>
            
            <el-form-item label="启用免打扰" prop="enableDoNotDisturb">
              <el-switch v-model="form.enableDoNotDisturb" :disabled="!form.receiveNotifications"></el-switch>
              <span class="form-help">在指定时间段内不接收通知</span>
            </el-form-item>
            
            <el-form-item label="免打扰时间段" v-if="form.enableDoNotDisturb">
              <el-time-picker
                v-model="form.doNotDisturbStart"
                format="HH:mm"
                placeholder="开始时间"
                :disabled="!form.receiveNotifications || !form.enableDoNotDisturb">
              </el-time-picker>
              <span class="time-separator">至</span>
              <el-time-picker
                v-model="form.doNotDisturbEnd"
                format="HH:mm"
                placeholder="结束时间"
                :disabled="!form.receiveNotifications || !form.enableDoNotDisturb">
              </el-time-picker>
            </el-form-item>
            
            <el-form-item label="免打扰日期" v-if="form.enableDoNotDisturb">
              <el-checkbox-group v-model="form.doNotDisturbDays" :disabled="!form.receiveNotifications || !form.enableDoNotDisturb">
                <el-checkbox :label="1">周一</el-checkbox>
                <el-checkbox :label="2">周二</el-checkbox>
                <el-checkbox :label="3">周三</el-checkbox>
                <el-checkbox :label="4">周四</el-checkbox>
                <el-checkbox :label="5">周五</el-checkbox>
                <el-checkbox :label="6">周六</el-checkbox>
                <el-checkbox :label="0">周日</el-checkbox>
              </el-checkbox-group>
            </el-form-item>
            
            <el-divider content-position="left">定时清理</el-divider>
            
            <el-form-item label="自动清理通知" prop="autoCleanNotifications">
              <el-switch v-model="form.autoCleanNotifications"></el-switch>
              <span class="form-help">定期自动清理已读通知</span>
            </el-form-item>
            
            <el-form-item label="保留时间" prop="retentionDays" v-if="form.autoCleanNotifications">
              <el-input-number v-model="form.retentionDays" :min="1" :max="365"></el-input-number>
              <span class="form-help">天 (超过此天数的已读通知将被自动清理)</span>
            </el-form-item>
          </el-tab-pane>
          
          <el-tab-pane label="订阅管理" name="subscriptions">
            <el-divider content-position="left">通知订阅</el-divider>
            
            <el-table :data="subscriptions" style="width: 100%" border>
              <el-table-column prop="name" label="订阅类型" width="180">
                <template slot-scope="scope">
                  <el-tag :type="getSubscriptionTagType(scope.row.type)">{{ scope.row.name }}</el-tag>
                </template>
              </el-table-column>
              <el-table-column prop="description" label="描述"></el-table-column>
              <el-table-column label="订阅状态" width="100" align="center">
                <template slot-scope="scope">
                  <el-switch
                    v-model="scope.row.subscribed"
                    :disabled="!form.receiveNotifications"
                    @change="handleSubscriptionChange(scope.row)">
                  </el-switch>
                </template>
              </el-table-column>
              <el-table-column label="接收渠道" width="280">
                <template slot-scope="scope">
                  <el-checkbox-group 
                    v-model="scope.row.channels" 
                    size="mini"
                    :disabled="!scope.row.subscribed || !form.receiveNotifications">
                    <el-checkbox :label="1">站内信</el-checkbox>
                    <el-checkbox :label="2">短信</el-checkbox>
                    <el-checkbox :label="3">邮件</el-checkbox>
                    <el-checkbox :label="4">推送</el-checkbox>
                  </el-checkbox-group>
                </template>
              </el-table-column>
            </el-table>
          </el-tab-pane>
          
          <el-tab-pane label="联系方式" name="contacts">
            <el-divider content-position="left">通知接收联系方式</el-divider>
            
            <el-alert
              title="请确保您的联系方式正确,以便接收通知"
              type="info"
              :closable="false"
              style="margin-bottom: 20px;">
            </el-alert>
            
            <el-form-item label="手机号码" prop="phoneNumber">
              <el-input v-model="form.phoneNumber" placeholder="请输入手机号码">
                <template slot="prepend">+86</template>
              </el-input>
            </el-form-item>
            
            <el-form-item label="电子邮箱" prop="email">
              <el-input v-model="form.email" placeholder="请输入电子邮箱"></el-input>
            </el-form-item>
            
            <el-form-item label="微信绑定" prop="wechatBound">
              <div v-if="form.wechatBound" class="bound-account">
                <i class="el-icon-success"></i>
                <span>已绑定微信账号:{{ form.wechatNickname }}</span>
                <el-button type="text" @click="unbindWechat">解除绑定</el-button>
              </div>
              <div v-else class="unbound-account">
                <i class="el-icon-warning"></i>
                <span>未绑定微信账号</span>
                <el-button type="primary" size="small" @click="bindWechat">绑定微信</el-button>
              </div>
            </el-form-item>
            
            <el-form-item label="APP推送" prop="appBound">
              <div v-if="form.appBound" class="bound-account">
                <i class="el-icon-success"></i>
                <span>已安装校园快递APP,设备名称:{{ form.appDeviceName }}</span>
                <el-button type="text" @click="unbindApp">解除绑定</el-button>
              </div>
              <div v-else class="unbound-account">
                <i class="el-icon-warning"></i>
                <span>未安装校园快递APP</span>
                <el-button type="primary" size="small" @click="downloadApp">下载APP</el-button>
              </div>
            </el-form-item>
          </el-tab-pane>
        </el-tabs>
        
        <el-form-item>
          <el-button type="primary" @click="submitForm">保存设置</el-button>
          <el-button @click="resetForm">重置</el-button>
          <el-button type="success" @click="testNotification">发送测试通知</el-button>
        </el-form-item>
      </el-form>
      
      <!-- 微信绑定对话框 -->
      <el-dialog title="绑定微信账号" :visible.sync="wechatDialogVisible" width="400px" append-to-body>
        <div class="qrcode-container">
          <div class="qrcode">
            <img src="https://via.placeholder.com/200" alt="微信二维码" />
          </div>
          <div class="qrcode-tip">请使用微信扫描二维码完成绑定</div>
        </div>
        <div slot="footer" class="dialog-footer">
          <el-button @click="wechatDialogVisible = false">取消</el-button>
          <el-button type="primary" @click="confirmBindWechat">已完成扫码</el-button>
        </div>
      </el-dialog>
    </el-card>
  </div>
</template>

<script>
import { NotificationType, NotificationChannel } from '@/utils/notification'

export default {
  name: 'NotificationSettings',
  data() {
    return {
      // 当前激活的标签页
      activeTab: 'preferences',
      // 表单数据
      form: {
        // 通知偏好
        receiveNotifications: true,
        enabledChannels: [1, 3], // 默认启用站内信和邮件
        enabledTypes: [1, 2, 3], // 默认启用所有类型
        desktopNotifications: true,
        soundNotifications: true,
        maxDisplayCount: 10,
        
        // 时间设置
        enableDoNotDisturb: false,
        doNotDisturbStart: null,
        doNotDisturbEnd: null,
        doNotDisturbDays: [],
        autoCleanNotifications: true,
        retentionDays: 30,
        
        // 联系方式
        phoneNumber: '',
        email: '',
        wechatBound: false,
        wechatNickname: '',
        appBound: false,
        appDeviceName: ''
      },
      // 表单校验规则
      rules: {
        phoneNumber: [
          { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
        ],
        email: [
          { type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
        ],
        retentionDays: [
          { type: 'number', min: 1, max: 365, message: '保留时间必须在1-365天之间', trigger: 'blur' }
        ],
        maxDisplayCount: [
          { type: 'number', min: 1, max: 50, message: '显示数量必须在1-50之间', trigger: 'blur' }
        ]
      },
      // 订阅列表
      subscriptions: [
        {
          id: 1,
          type: NotificationType.SYSTEM,
          name: '系统通知',
          description: '系统维护、更新、安全提醒等系统相关通知',
          subscribed: true,
          channels: [NotificationChannel.IN_APP, NotificationChannel.EMAIL]
        },
        {
          id: 2,
          type: NotificationType.EXPRESS,
          name: '快递通知',
          description: '快递到达、取件提醒、催领通知等快递相关通知',
          subscribed: true,
          channels: [NotificationChannel.IN_APP, NotificationChannel.SMS, NotificationChannel.PUSH]
        },
        {
          id: 3,
          type: NotificationType.ACTIVITY,
          name: '活动通知',
          description: '校园活动、讲座、比赛等活动相关通知',
          subscribed: true,
          channels: [NotificationChannel.IN_APP, NotificationChannel.EMAIL]
        },
        {
          id: 4,
          type: 4,
          name: '课程通知',
          description: '课程变更、考试安排等课程相关通知',
          subscribed: true,
          channels: [NotificationChannel.IN_APP, NotificationChannel.EMAIL, NotificationChannel.SMS]
        },
        {
          id: 5,
          type: 5,
          name: '公告通知',
          description: '学校公告、政策变更等公告通知',
          subscribed: true,
          channels: [NotificationChannel.IN_APP]
        }
      ],
      // 微信绑定对话框
      wechatDialogVisible: false
    }
  },
  created() {
    this.getSettings()
  },
  methods: {
    // 获取用户通知设置
    getSettings() {
      // 实际项目中应该从API获取用户设置
      // 这里使用模拟数据
      setTimeout(() => {
        this.form.phoneNumber = '13800138000'
        this.form.email = 'user@example.com'
      }, 500)
    },
    
    // 提交表单
    submitForm() {
      this.$refs.form.validate(valid => {
        if (valid) {
          // 实际项目中应该调用API保存设置
          // saveNotificationSettings(this.form).then(response => {
          //   this.$message.success('设置保存成功')
          // })
          
          // 模拟保存成功
          this.$message({
            message: '设置保存成功',
            type: 'success'
          })
        }
      })
    },
    
    // 重置表单
    resetForm() {
      this.$confirm('确定要重置所有设置吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        this.$refs.form.resetFields()
        this.getSettings()
      }).catch(() => {})
    },
    
    // 发送测试通知
    testNotification() {
      // 实际项目中应该调用API发送测试通知
      // sendTestNotification().then(response => {
      //   this.$message.success('测试通知已发送')
      // })
      
      // 模拟发送成功
      this.$message({
        message: '测试通知已发送,请检查您的通知渠道',
        type: 'success'
      })
    },
    
    // 处理订阅状态变更
    handleSubscriptionChange(subscription) {
      if (!subscription.subscribed) {
        subscription.channels = []
      } else if (subscription.channels.length === 0) {
        // 默认选择站内信
        subscription.channels = [NotificationChannel.IN_APP]
      }
    },
    
    // 获取订阅类型对应的标签类型
    getSubscriptionTagType(type) {
      switch (type) {
        case NotificationType.SYSTEM:
          return 'primary'
        case NotificationType.EXPRESS:
          return 'success'
        case NotificationType.ACTIVITY:
          return 'warning'
        default:
          return 'info'
      }
    },
    
    // 绑定微信
    bindWechat() {
      this.wechatDialogVisible = true
    },
    
    // 确认绑定微信
    confirmBindWechat() {
      // 实际项目中应该调用API确认绑定状态
      // confirmWechatBind().then(response => {
      //   this.form.wechatBound = true
      //   this.form.wechatNickname = response.data.nickname
      //   this.wechatDialogVisible = false
      //   this.$message.success('微信绑定成功')
      // })
      
      // 模拟绑定成功
      this.form.wechatBound = true
      this.form.wechatNickname = '校园快递用户'
      this.wechatDialogVisible = false
      this.$message({
        message: '微信绑定成功',
        type: 'success'
      })
    },
    
    // 解除微信绑定
    unbindWechat() {
      this.$confirm('确定要解除微信绑定吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        // 实际项目中应该调用API解除绑定
        // unbindWechat().then(response => {
        //   this.form.wechatBound = false
        //   this.form.wechatNickname = ''
        //   this.$message.success('微信解绑成功')
        // })
        
        // 模拟解绑成功
        this.form.wechatBound = false
        this.form.wechatNickname = ''
        this.$message({
          message: '微信解绑成功',
          type: 'success'
        })
      }).catch(() => {})
    },
    
    // 下载APP
    downloadApp() {
      // 打开下载页面
      window.open('https://example.com/download', '_blank')
    },
    
    // 解除APP绑定
    unbindApp() {
      this.$confirm('确定要解除APP设备绑定吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        // 实际项目中应该调用API解除绑定
        // unbindApp().then(response => {
        //   this.form.appBound = false
        //   this.form.appDeviceName = ''
        //   this.$message.success('APP设备解绑成功')
        // })
        
        // 模拟解绑成功
        this.form.appBound = false
        this.form.appDeviceName = ''
        this.$message({
          message: 'APP设备解绑成功',
          type: 'success'
        })
      }).catch(() => {})
    }
  }
}
</script>

<style lang="scss" scoped>
.form-help {
  margin-left: 10px;
  color: #909399;
  font-size: 14px;
}

.time-separator {
  margin: 0 10px;
}

.bound-account, .unbound-account {
  display: flex;
  align-items: center;
  
  i {
    margin-right: 10px;
    font-size: 18px;
  }
  
  .el-icon-success {
    color: #67C23A;
  }
  
  .el-icon-warning {
    color: #E6A23C;
  }
  
  span {
    flex: 1;
  }
}

.qrcode-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px;
  
  .qrcode {
    margin-bottom: 20px;
    
    img {
      width: 200px;
      height: 200px;
    }
  }
  
  .qrcode-tip {
    color: #909399;
    font-size: 14px;
  }
}
</style>

express-ui\src\views\notification\statistics.vue

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天天进步2015

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值