index.vue 10.8 KB
<template>
  <div class="NCC-common-layout">
    <div class="NCC-common-layout-center">
      <!-- 统计卡片 -->
      <el-row :gutter="16" class="stat-cards">
        <el-col :span="6" v-for="card in statCards" :key="card.key">
          <div
            class="stat-card"
            :class="{ active: activeStatus === card.value }"
            @click="handleCardClick(card.value)"
          >
            <div class="stat-card__icon" :style="{ backgroundColor: card.bgColor }">
              <i :class="card.icon" :style="{ color: card.color }"></i>
            </div>
            <div class="stat-card__info">
              <div class="stat-card__count">{{ card.count }}</div>
              <div class="stat-card__label">{{ card.label }}</div>
            </div>
          </div>
        </el-col>
      </el-row>

      <!-- 筛选区 -->
      <el-row class="NCC-common-search-box" :gutter="16">
        <el-form @submit.native.prevent>
          <el-col :span="6">
            <el-form-item label="文件名">
              <el-input
                v-model="query.keyword"
                placeholder="请输入文件名关键字"
                clearable
                @keyup.enter.native="search"
              />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="上传时间">
              <el-date-picker
                v-model="dateRange"
                type="daterange"
                value-format="yyyy-MM-dd"
                format="yyyy-MM-dd"
                start-placeholder="开始日期"
                end-placeholder="结束日期"
                style="width: 100%"
              />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item>
              <el-button type="primary" icon="el-icon-search" @click="search">查询</el-button>
              <el-button icon="el-icon-refresh-right" @click="reset">重置</el-button>
            </el-form-item>
          </el-col>
        </el-form>
      </el-row>

      <!-- 表格区 -->
      <div class="NCC-common-layout-main NCC-flex-main">
        <div class="NCC-common-head">
          <div></div>
          <div class="NCC-common-head-right">
            <el-tooltip effect="dark" content="刷新" placement="top">
              <el-link
                icon="icon-ym icon-ym-Refresh NCC-common-head-icon"
                :underline="false"
                @click="reset"
              />
            </el-tooltip>
          </div>
        </div>

        <el-table v-loading="listLoading" :data="list" border style="width: 100%">
          <el-table-column label="缩略图" width="80" align="center">
            <template slot-scope="scope">
              <el-image
                v-if="isImage(scope.row.extension)"
                :src="listThumbSrc(scope.row)"
                :preview-src-list="[scope.row.imageUrl]"
                style="width: 50px; height: 50px"
                fit="cover"
              />
              <i v-else class="el-icon-document" style="font-size: 28px; color: #909399"></i>
            </template>
          </el-table-column>
          <el-table-column prop="originalFileName" label="原始文件名" min-width="160" show-overflow-tooltip />
          <el-table-column prop="auditStatusText" label="审核状态" width="100" align="center">
            <template slot-scope="scope">
              <el-tag :type="auditTagType(scope.row.auditStatus)" size="small">
                {{ scope.row.auditStatusText }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column prop="auditReason" label="审核原因" min-width="150" show-overflow-tooltip>
            <template slot-scope="scope">
              {{ scope.row.auditReason || '无' }}
            </template>
          </el-table-column>
          <el-table-column prop="userName" label="上传人" width="100" show-overflow-tooltip />
          <el-table-column prop="fileType" label="来源模块" width="120" align="center">
            <template slot-scope="scope">
              <el-tag size="small" :type="getFileTypeTagType(scope.row.fileType)">
                {{ getFileTypeText(scope.row.fileType) }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column prop="createTime" label="上传时间" width="160">
            <template slot-scope="scope">
              {{ formatDateTime(scope.row.createTime) }}
            </template>
          </el-table-column>
          <el-table-column prop="auditTime" label="审核时间" width="160">
            <template slot-scope="scope">
              {{ scope.row.auditTime ? formatDateTime(scope.row.auditTime) : '-' }}
            </template>
          </el-table-column>
          <el-table-column label="操作" width="120" fixed="right">
            <template slot-scope="scope">
              <el-button type="text" icon="el-icon-view" @click="viewImage(scope.row)">查看图片</el-button>
            </template>
          </el-table-column>
        </el-table>

        <pagination
          :total="total"
          :page.sync="listQuery.currentPage"
          :limit.sync="listQuery.pageSize"
          @pagination="initData"
        />
      </div>
    </div>
  </div>
</template>

<script>
import { getUploadRecords, getAuditStats } from '@/api/extend/ossUpload'
import { appendOssImageThumb } from '@/utils/oss-image-thumb'

export default {
  name: 'LqUploadRecord',
  data() {
    return {
      activeStatus: null,
      stats: { pending: 0, pass: 0, reject: 0, failed: 0, total: 0 },
      dateRange: null,
      query: {
        keyword: undefined,
        auditStatus: undefined
      },
      list: [],
      total: 0,
      listLoading: false,
      listQuery: {
        currentPage: 1,
        pageSize: 20
      }
    }
  },
  computed: {
    statCards() {
      return [
        {
          key: 'total',
          label: '全部',
          value: null,
          count: this.stats.total,
          icon: 'el-icon-document',
          color: '#409EFF',
          bgColor: 'rgba(64,158,255,0.1)'
        },
        {
          key: 'pending',
          label: '待审核',
          value: 0,
          count: this.stats.pending,
          icon: 'el-icon-time',
          color: '#E6A23C',
          bgColor: 'rgba(230,162,60,0.1)'
        },
        {
          key: 'pass',
          label: '已通过',
          value: 1,
          count: this.stats.pass,
          icon: 'el-icon-circle-check',
          color: '#67C23A',
          bgColor: 'rgba(103,194,58,0.1)'
        },
        {
          key: 'reject',
          label: '违规',
          value: 2,
          count: this.stats.reject,
          icon: 'el-icon-warning',
          color: '#F56C6C',
          bgColor: 'rgba(245,108,108,0.1)'
        }
      ]
    }
  },
  created() {
    this.loadStats()
    this.initData()
  },
  methods: {
    loadStats() {
      getAuditStats().then(res => {
        if (res.data) {
          this.stats = res.data
        }
      })
    },
    initData() {
      this.listLoading = true
      const params = {
        currentPage: this.listQuery.currentPage,
        pageSize: this.listQuery.pageSize
      }
      if (this.query.keyword) {
        params.keyword = this.query.keyword
      }
      if (this.query.auditStatus !== undefined && this.query.auditStatus !== null) {
        params.auditStatus = this.query.auditStatus
      }
      if (this.dateRange && this.dateRange.length === 2) {
        params.startTime = this.dateRange[0]
        params.endTime = this.dateRange[1]
      }
      getUploadRecords(params).then(res => {
        this.list = res.data.list
        this.total = res.data.pagination.total
        this.listLoading = false
      }).catch(() => {
        this.listLoading = false
      })
    },
    handleCardClick(status) {
      this.activeStatus = status
      this.query.auditStatus = status
      this.listQuery.currentPage = 1
      this.initData()
    },
    search() {
      this.listQuery.currentPage = 1
      this.initData()
    },
    reset() {
      this.activeStatus = null
      this.query = { keyword: undefined, auditStatus: undefined }
      this.dateRange = null
      this.listQuery = { currentPage: 1, pageSize: 20 }
      this.initData()
      this.loadStats()
    },
    auditTagType(status) {
      const map = { 0: 'warning', 1: 'success', 2: 'danger', 3: 'info' }
      return map[status] || 'info'
    },
    isImage(ext) {
      if (!ext) return false
      return ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].includes(ext.toLowerCase())
    },
    listThumbSrc(row) {
      if (!row || !row.imageUrl) return ''
      if (row.thumbnailUrl) return row.thumbnailUrl
      return appendOssImageThumb(row.imageUrl)
    },
    viewImage(row) {
      if (row.imageUrl) {
        window.open(row.imageUrl, '_blank')
      }
    },
    formatDateTime(dateTime) {
      if (!dateTime) return '-'
      const date = new Date(dateTime)
      if (isNaN(date.getTime())) return dateTime
      const y = date.getFullYear()
      const m = String(date.getMonth() + 1).padStart(2, '0')
      const d = String(date.getDate()).padStart(2, '0')
      const h = String(date.getHours()).padStart(2, '0')
      const min = String(date.getMinutes()).padStart(2, '0')
      const s = String(date.getSeconds()).padStart(2, '0')
      return `${y}-${m}-${d} ${h}:${min}:${s}`
    },
    getFileTypeText(fileType) {
      const map = {
        'annexpic': '附件图片',
        'attendancepic': '考勤打卡',
        'workFlow': '工作流',
        'aiVoice': 'AI语音',
        'member': '会员资料',
        'store': '门店资料'
      }
      return map[fileType] || fileType || '未知'
    },
    getFileTypeTagType(fileType) {
      const map = {
        'annexpic': '',
        'attendancepic': 'success',
        'workFlow': 'warning',
        'aiVoice': 'info',
        'member': 'danger',
        'store': ''
      }
      return map[fileType] || 'info'
    }
  }
}
</script>

<style lang="scss" scoped>
.stat-cards {
  margin-bottom: 16px;
}

.stat-card {
  display: flex;
  align-items: center;
  height: 100px;
  padding: 12px;
  background: #fff;
  border-radius: 12px;
  border: 1px solid #ebeef5;
  cursor: pointer;
  transition: all 0.3s;

  &:hover {
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
  }

  &.active {
    border-color: #409eff;
    box-shadow: 0 2px 12px rgba(64, 158, 255, 0.2);
  }

  &__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    border-radius: 12px;
    flex-shrink: 0;

    i {
      font-size: 28px;
    }
  }

  &__info {
    margin-left: 16px;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

  &__count {
    font-size: 28px;
    font-weight: 600;
    color: #303133;
    line-height: 1.2;
  }

  &__label {
    font-size: 14px;
    color: #909399;
    margin-top: 4px;
  }
}
</style>