el-table实现滑窗列

左、右列冻结,中间任务列可横向拖动的汇总表格

实现代码:
html 复制代码
<template>
  <el-row class="testStatistics">
    <el-col :span="24">
      <H_Panel>
        <div class="panel_box">
          <div class="ctl-bar">
            <el-space>
              <label>
                导入日期:
                <el-date-picker
                    v-model="selectedDate"
                    type="month"
                    placeholder="选择年月"
                    format="YYYY年MM月"
                    value-format="YYYY-MM"
                    style="width: 150px; margin-right: 10px"
                    @change="handleDateChange"
                />
              </label>
              <el-button style="margin: 0 20px" @click="resetTime">
                重置
              </el-button>
            </el-space>

          </div>
          <el-table
              :data="tabsData[0].data"
              border
              :pagination="false"
              :style="{ marginTop: '8px', height: '726px' }"
              :scroll="{ x: 'min-content' }"
              show-summary
              :summary-method="getSummaries"
          >

            <el-table-column prop="dept" label="所属单位" width="180" fixed="left" align="center"/>
            <el-table-column prop="address" label="部署地点" width="180" fixed="left" align="center"/>
            <el-table-column prop="devName" label="设备列表" width="150" fixed="left" align="center"/>


            <el-table-column
                v-for="task in tabsData[0].columns[3].children"
                :key="task.title"
                :label="task.title"
                :prop="task.title"
                align="center"
                :min-width="100"
            >
              <template #default="{ row }">
                <template v-if="row.devTaskjson && row.devTaskjson[0]">
                  <img
                      v-if="row.devTaskjson[0][task.title] === '1' && !editableData[row.id]"
                      :src="success"
                      style="width: 25px; height: 25px"
                  >
                  <div v-else-if="editableData[row.id]">
                    是否参试:
                    <el-select
                        v-model="editableData[row.id].devTaskjson[0][task.title]"
                        style="width: 120px"
                    >
                      <el-option
                          v-for="opt in options2"
                          :key="opt.value"
                          :label="opt.label"
                          :value="opt.value"
                      />
                    </el-select>
                  </div>
                </template>
              </template>
            </el-table-column>


            <el-table-column prop="totalSum" label="统计" :width="headerFlag?'80':''" fixed="right" align="center">
              <template #default="{ row }">
                <span style="font-weight:bold;color:#0082D9">{{ sum(row.devTaskjson) }}</span>
              </template>
            </el-table-column>

            <!--          <el-table-column-->
            <!--              v-if="user.role === '管理员'"-->
            <!--              label="操作"-->
            <!--              width="120"-->
            <!--              fixed="right"-->
            <!--          >-->
            <!--            <template #default="{ row }">-->
            <!--              <div class="editable-row-operations">-->
            <!--                  <span v-if="editableData[row.id]">-->
            <!--                    <el-link @click="save(row.id)">保存</el-link>-->
            <!--                    <el-popconfirm title="您要放弃保存吗?" @confirm="cancel(row.id)">-->
            <!--                      <template #reference>-->
            <!--                        <el-link type="danger" style="margin-left: 8px">取消</el-link>-->
            <!--                      </template>-->
            <!--                    </el-popconfirm>-->
            <!--                  </span>-->
            <!--                <span v-else>-->
            <!--                    <el-space>-->
            <!--                      <el-link @click="edit(row.id)">编辑</el-link>-->
            <!--                      <el-popconfirm title="确定删除该条数据?" @confirm="deleteRow(row.id)">-->
            <!--                        <template #reference>-->
            <!--                          <el-link type="danger" class="del-link">删除</el-link>-->
            <!--                        </template>-->
            <!--                      </el-popconfirm>-->
            <!--                    </el-space>-->
            <!--                  </span>-->
            <!--              </div>-->
            <!--            </template>-->
            <!--          </el-table-column>-->
          </el-table>
        </div>
      </H_Panel>
    </el-col>
  </el-row>
</template>

<script setup>
import {onMounted, ref, reactive, nextTick} from 'vue'
import H_Panel from './components/H_Panel.vue'
import success from '@/assets/cops/statistics/success@2x.png'
import {cloneDeep} from 'lodash-es'
import useGlobalStore from '@/store/modules/global'
import {ElMessage} from 'element-plus'
import {TestStatisticsService} from '@/api/statistical/index.js'   // <-- 真实接口

const {user} = useGlobalStore()

const headerFlag = ref(true)

/* ----------------------------------------------------------
 *  日期选择(年月)
 * ---------------------------------------------------------- */
const selectedDate = ref('')
const setCurrentDate = () => {
  const now = new Date()
  const year = now.getFullYear()
  const month = (now.getMonth() + 1).toString().padStart(2, '0')
  selectedDate.value = `${year}-${month}`
}
const handleDateChange = () => selectedDate.value && getData(selectedDate.value)
const resetTime = () => {
  setCurrentDate()
  getData(selectedDate.value)
}

/* ----------------------------------------------------------
 *  表格骨架
 * ---------------------------------------------------------- */
const tabsData = ref([
  {
    key: 'basic',
    title: '基本信息',
    columns: [
      {dataIndex: 'dept', key: 'dept', title: '所属单位', width: 180, fixed: 'left'},
      {dataIndex: 'address', key: 'address', title: '部署地点', width: 180, fixed: 'left'},
      {dataIndex: 'devName', key: 'devName', title: '设备列表', width: 150, fixed: 'left'},
      {title: '任务编号', children: []},
      {title: '统计', dataIndex: 'gender', key: 'gender', width: 80, fixed: 'right'},
    ],
    data: [],
  },
])

/* ----------------------------------------------------------
 *  编辑相关
 * ---------------------------------------------------------- */
const editableData = reactive({})
const editTitle = ref([])
const options2 = ref([{value: '0', label: '否'}, {value: '1', label: '是'}])


function mapRemoteToLocal({header, taskList}) {

  /* 动态列 */
  tabsData.value[0].columns[3].children = header.map(h => ({
    title: h.name,
    key: h.name,
    dataIndex: h.name,
    width: 100,
    sum: h.value,
  }))
  /* 行数据 */
  tabsData.value[0].data = taskList.map(item => ({
    ...item,
    devTaskjson: [JSON.parse(item.devTaskjson)],
  }))
}

async function getData(date) {
  try {
    const params = {queryMonth: date}
    const res = await TestStatisticsService.getTestStatistics(params)

    if (res?.code === 200) {
      headerFlag.value = res?.data.header.length > 0
      mapRemoteToLocal(res.data)
    } else {
      // initMockData()
    }
  } catch (e) {
    console.error('获取数据失败,使用模拟数据:', e)
    // initMockData()
  }
}

function generateMockData() {
  const departments = ['作战一部', '作战二部', '技术支持部', '后勤保障部', '信息中心']
  const locations = ['指挥中心A区', '指挥中心B区', '野外阵地1号', '野外阵地2号', '机动部署点']
  const devices = ['雷达系统', '通信设备', '监控终端', '数据处理服务器', '导航设备']
  const tasks = ['任务001', '任务002', '任务003', '任务004', '任务005', '任务006', '任务007', '任务008', '任务009', '任务010', '任务011', '任务012', '任务013', '任务014', '任务015', '任务016']

  const taskColumns = tasks.map(t => ({title: t, key: t, dataIndex: t, width: 100, sum: 0}))
  const mockData = Array.from({length: 15}).map((_, i) => {
    const taskJson = {}
    tasks.forEach(t => (taskJson[t] = Math.random() > 0.3 ? '1' : '0'))
    return {
      id: String(i + 1),
      dept: departments[i % departments.length],
      address: locations[i % locations.length],
      devName: `${devices[i % devices.length]}-${i + 1}`,
      devTaskjson: [taskJson],
    }
  })
  taskColumns.forEach(col => {
    col.sum = mockData.filter(r => r.devTaskjson[0][col.title] === '1').length
  })
  return {mockData, taskColumns}
}

function initMockData() {
  const {mockData, taskColumns} = generateMockData()
  tabsData.value[0].data = mockData
  tabsData.value[0].columns[3].children = taskColumns
}

/* ----------------------------------------------------------
 *  汇总行
 * ---------------------------------------------------------- */
function getSummaries({ columns, data }) {
  const sums = []
  columns.forEach((col, idx) => {
    if (idx === 0) { sums[idx] = '统计'; return }
    if (idx < 3) { sums[idx] = ''; return }

    if (col.property === 'totalSum') {
      sums[idx] = data.reduce((acc, row) => acc + sum(row.devTaskjson), 0)
      return
    }

    if (user?.role === '管理员' && idx === columns.length - 2) { sums[idx] = ''; return }

    const taskName = col.property
    sums[idx] = data.reduce((acc, row) =>
        acc + (row.devTaskjson?.[0]?.[taskName] === '1' ? 1 : 0), 0)
  })
  return sums
}

/* ----------------------------------------------------------
 *  行内统计
 * ---------------------------------------------------------- */
function sum(devTaskjson) {
  if (!devTaskjson?.[0]) return 0
  return Object.values(devTaskjson[0]).filter(v => v === '1').length
}

/* ----------------------------------------------------------
 *  编辑 / 保存 / 删除
 * ---------------------------------------------------------- */
function edit(id) {
  if (Object.keys(editableData).length) return ElMessage.warning('请先处理上一条数据')
  const row = tabsData.value[0].data.find(item => item.id === id)
  if (row) {
    editableData[id] = cloneDeep(row)
    editTitle.value = tabsData.value[0].columns[3].children.map(t => t.title)
  }
}

async function save(id) {
  try {
    const params = {
      ...editableData[id],
      devTaskjson: JSON.stringify(editableData[id].devTaskjson[0]),
    }
    const res = await updateItem(params)
    if (res?.code === 0) {
      ElMessage.success(res.msg)
      delete editableData[id]
      editTitle.value = []
      await getData(selectedDate.value)
    }
  } catch (e) {
    ElMessage.error('保存失败')
  }
}

function cancel(id) {
  delete editableData[id]
  editTitle.value = []
}

async function deleteRow(id) {
  try {
    const res = await deleteDev({devId: id})
    if (res?.code === 0) {
      ElMessage.success(res.msg)
      await getData(selectedDate.value)
    }
  } catch (e) {
    ElMessage.error('删除失败')
  }
}

/* ----------------------------------------------------------
 *  初始化
 * ---------------------------------------------------------- */
onMounted(async () => {
  setCurrentDate()
  await nextTick()
  getData(selectedDate.value)
})
</script>


<style lang="scss" scoped>
.testStatistics {
  height: 100%;
}

.panel_box {
  height: 100%;

  :deep(.el-table) {
    height: 726px;
    background: transparent !important;

    .el-table__header th {
      color: #fff;
      height: 60px !important;
      font-size: 16px;
      font-weight: normal;
    }

    .el-table__header {
      background: #001351 !important;

      th {
        background: #001351 !important;
      }

      //tr{
      //  background: url("/src/assets/cops/statistics/right_table_header.png") no-repeat !important;
      //  background-size: 100% 100% !important;
      //  color: #fff;
      //}

    }

    .el-table__body {
      tr {
        //background: transparent !important;
      }
    }

    // 修改汇总行样式
    .el-table__footer-wrapper {
      tr {
        background-color: #001351 !important;

        td {
          background-color: #001351 !important;
          font-weight: bold;
          color: #0082D9 !important;
          font-size: 14px;
          text-align: center;


          &:first-child {
            color: #0082D9 !important;
            font-weight: 700;
          }


          &:not(:first-child):not(:empty) {
            color: #0082D9 !important;
            font-weight: 700;
            font-size: 15px;
          }
        }
      }
    }
  }
}

.ctl-bar {
  height: 35px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
}

.page-bar {
  margin-top: 16px;
  display: flex;
  justify-content: flex-end;
}

.del-link {
  color: #f56c6c;
}

.editable-row-operations a {
  margin-right: 8px;
}

.task-item {
  margin-bottom: 16px;
}

.task-buttons {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 20px;
}

.form-buttons {
  display: flex;
  justify-content: center;
  margin-top: 50px;
  width: 100%;
}

:deep(.el-form-item) {
  width: 100%;
  margin-bottom: 22px;
}

:deep(.el-input) {
  width: 100%;
}

:deep(.el-table .el-table__body) {
  tr:nth-child(even) {
    background-color: #002872 !important;
  }

  tr:nth-child(odd) {
    background-color: #00206A !important;
  }
}

:deep(.el-table__body-wrapper) {
  max-height: 686px;
  overflow-y: auto;
}

:deep(.el-table .table-striped) {
  background-color: #fafafa;
}

:deep(.el-upload-list) {
  display: none;
}
</style>
相关推荐
阿蓝灬3 小时前
Chrome Lighthouse优化
前端·chrome
武昌库里写JAVA3 小时前
Java设计模式-(创建型)抽象工厂模式
java·vue.js·spring boot·后端·sql
程序员爱钓鱼5 小时前
Node.js 编程实战:图像与文件上传下载
前端·后端·node.js
kong79069286 小时前
环境搭建-运行前端工程(vue)
前端·前端环境
谷歌开发者7 小时前
Web 开发指向标|开发者工具 AI 辅助功能的 5 大实践应用
前端·人工智能
快乐肚皮12 小时前
一文了解XSS攻击:分类、原理与全方位防御方案
java·前端·xss
保护我方头发丶12 小时前
ESP-wifi-蓝牙
前端·javascript·数据库
想学后端的前端工程师13 小时前
【Flutter跨平台开发实战指南:从零到上线-web技术栈】
前端·flutter
老王Bingo13 小时前
Qwen Code + Chrome DevTools MCP,让爬虫、数据采集、自动化测试效率提升 100 倍
前端·爬虫·chrome devtools