后台系统项目常用功能点-笔记

0 阅读2分钟

背景

记录一下后台系统中通用的功能点,以便在后续工作中直接copy使用。

当前项目技术栈: vue2 + element-ui

el-table 第一行高亮

image.png

<el-table :key="keys" :data="tableData" :border="true" :row-class-name="tableRowClassName">
      <el-table-column v-for="i in tableColumns" :key="i.prop" :label="i.label" :prop="i.prop" :width="i.width" />
</el-table>


methods: {
    tableRowClassName({ row, rowIndex }) {
      console.log(rowIndex)
      if (rowIndex === 0) {
        return 'first-row'
      }
      return ''
    },
}   


<style lang="scss" scoped>
/deep/.first-row {
  td {
    font-weight: bold;
    color: red;
    background-color: #f0f9eb;
  }
}
</style>

多选表格数据,不符合条件的数据禁止选择

 <el-table :data="tableData" :border="true" @selection-change="handleSelectionChange">
     <!----折叠行数据-->
      <el-table-column type="expand">
        <template #default="{ row }"></template>
      </el-table-column>
      
      <!--多选行代码-->
      <el-table-column type="selection" width="55" :selectable="handleSelectable" />
      
      <!--表格数据-->
      <el-table-column v-for="i in tableColumns" :key="i.prop" :label="i.label" :prop="i.prop" :width="i.width">
        <template #default="{ row }"> 
            {{ row[i.prop] }}  
        </template>
      </el-table-column>
    </el-table>


// 符合条件的数据允许选择
handleSelectable(row) {
  return row.audit_status === 0
}

导入excel文件,并以表格的形式展示数据

<template>
  <div style="display: inline-block;">
    <el-upload
      ref="upload"
      action="#"
      :limit="1"
      :show-file-list="false"
      accept=".xls,.xlsx"
      :on-change="fileChange"
      :auto-upload="false"
      style="display: inline-block;"
      class="ml16"
    >
      <buttons is-import>批量导入模板</buttons>
    </el-upload>

    <el-dialog title="" width="1000px" :visible.sync="dialogVisible" :close-on-click-modal="false" top="5vh">
      <el-form ref="form" inline :model="form" label-width="130px" :rules="formRules">
        <el-form-item label="上传文件所属项目">
          <el-input v-model="form.sourceProject" disabled />
        </el-form-item>
        <el-form-item label="上传文件渠道包">
          <el-input v-model="form.sourcePackage" disabled />
        </el-form-item>
        <el-form-item label="当前所属项目">
          <el-input v-model="form.curProject" disabled />
        </el-form-item>
        <el-form-item label="当前渠道包" prop="curPackage">
          <el-select v-model="form.curPackage" placeholder="请选择渠道包" filterable style="width:240px">
            <el-option v-for="({ id, name }) in packageList" :key="id" :value="id" :label="name" />
          </el-select>
        </el-form-item>
      </el-form>
      <!-- 提示信息 -->
      <div class="total">
        <span v-if="form.sourceProject !== form.curProject" class="tips-red fl">
          <i class="el-icon-info" />
          上传文件所属项目和当前项目不完全一致,请仔细确认导入数据
        </span>
        <span><b class="blue ml16 mr16">{{ tableData.length }}</b>条数据</span>
      </div>
      <el-table :data="tableData" :border="true" height="calc(100vh - 400px)">
        <el-table-column
          v-for="{ label, prop } in columns1"
          :key="prop"
          :label="label"
          :prop="prop"
          show-overflow-tooltip
        />
      </el-table>
      <span slot="footer">
        <el-button @click="handleCancel">取消</el-button>
        <el-button type="primary" :loading="importLoading" :disabled="importLoading" @click="handleSubmit">确定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
import * as XLSX from 'xlsx'
import { batchImportSubscribeTemplate } from '@/api/product/messageSubcribe'
export default {
  name: '',
  props: {
    packageList: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      columns: [
        { label: '项目ID', prop: 'curPid' },
        { label: '项目名称', prop: 'appName' },
        { label: '渠道包ID', prop: 'package_id' },
        { label: '模版名称', prop: 'name' },
        { label: '订阅模版ID', prop: 'template_id' },
        { label: '订阅模板默认内容', prop: 'data' },
        { label: '模版卡片跳转页面', prop: 'page' },
        { label: '默认延时计算方式', prop: 'delay_type' },
        { label: '延迟发送时间', prop: 'delay_time' },
        { label: '状态', prop: 'status' }
      ],
      formRules: {
        curPackage: [{ required: true, message: '请选择渠道包', trigger: 'blur' }]
      },
      form: {
        sourceProject: '', // 上传文件所属项目
        sourcePackage: '', // 上传文件所属集群
        curProject: '', // 当前项目
        curPackage: '' // 当前渠道包
      },
      dialogVisible: false,
      tableData: [],
      importLoading: false
    }
  },
  computed: {
    columns1() {
      const arr = ['curPid', 'appName', 'package_id']
      return this.columns.filter(item => !arr.includes(item.prop))
    }
  },
  methods: {
    // 使用箭头函数,增加空数据校验
    fileChange(file) {
      if (!file || !file.raw) {
        this.$message.error('文件无效')
        return
      }
      const reader = new FileReader()
      reader.onload = (e) => {
        try {
          const data = new Uint8Array(e.target.result)
          const workbook = XLSX.read(data, {
            type: 'array',
            cellDates: true, // 将日期单元格解析为 Date 对象
            cellNF: true, // 保留数字格式
            cellText: true // 保留单元格文本
          })
          const firstSheet = workbook.Sheets[workbook.SheetNames[0]]
          const jsonData = XLSX.utils.sheet_to_json(firstSheet, {
            header: 1,
            raw: true,
            defval: '',
            dateNF: 'yyyy-mm-dd hh:mm:ss' // 指定日期输出格式
          })
          this.dealData(jsonData)
        } catch (error) {
          this.$message.error('文件解析失败,请检查文件格式')
        }
      }
      reader.readAsArrayBuffer(file.raw)
    },

    dealData(data) {
      if (!data || data.length <= 1) {
        this.$message.warning('导入文件无有效数据')
        return false
      }
      // 处理数据,将列名映射到 prop
      this.tableData = data.slice(1).map(item => {
        const obj = {}
        for (const key in item) {
          if (this.columns[key]) {
            obj[this.columns[key].prop] = item[key]
          }
        }
        return obj
      })
      // 数据中含有不同渠道包则返回报错信息
      const packageIds = new Set(this.tableData.map(item => item.package_id))
      if (packageIds.size > 1) {
        this.$message.error('导入文件中包含不同渠道包,无法导入,请检查数据')
        return
      }
      // 安全访问第一个元素
      if (this.tableData.length > 0) {
        this.form.sourceProject = `[${this.tableData[0].curPid}]${this.tableData[0].appName}`
        const packageItem = this.packageList.find(item => item.id === this.tableData[0].package_id)
        this.form.sourcePackage = packageItem?.name

        const { curPid, appName } = this.$store.getters
        this.form.curProject = `[${curPid}]${appName}`
        this.form.curPackage = this.packageList[0]?.id || ''
        this.dialogVisible = true
      }
    },

    async handleSubmit() {
      try {
        this.importLoading = true
        const project = this.form.curProject
        const packages = this.packageList.find(item => item.id === this.form.curPackage)?.name || ''
        await this.$refs.form.validate()
        await this.$confirm('当前导入项目为【' + project + '】,导入渠道包为【' + packages + '】,导入数据会对有数据进行覆盖,是否继续', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        })
        const list = JSON.parse(JSON.stringify(this.tableData))
        list.forEach((item, index) => {
          item.package_id = this.form.curPackage
          delete item.curPid
          delete item.appName
          try {
            item.data = item.data ? JSON.parse(item.data) : {}
          } catch (e) {
            throw new Error(`第 ${index + 1} 行订阅模板默认内容格式错误`)
          }
        })
        await batchImportSubscribeTemplate({ list })
        this.$message.success('导入成功')
        this.dialogVisible = false
        this.$emit('update')
        this.$refs.upload?.clearFiles()
      } catch (error) {
        console.log(error.message || '导入失败,请稍后重试')
      } finally {
        this.importLoading = false
      }
    },
    // 新增取消处理方法
    handleCancel() {
      this.dialogVisible = false
      this.$refs.upload?.clearFiles()
    }
  }
}
</script>

<style lang="scss" scoped>
.total {
  margin-bottom: 16px;
  margin-left: 20px;
  text-align: right;

  .tips-red {
    font-weight: bold;
  }
}
</style>