Newer
Older
robot_dog_patrol_front / src / components / NormalTable / index.vue
<script lang="ts" setup name="NormalTable">
import type { Ref } from 'vue'
import { ElMessage, ElTable } from 'element-plus'
import { defineExpose, ref } from 'vue'
import type { TableColumn } from './table_interface'
import TableColumnCom from './tableColumn.vue'
// ------------------定义props、 emit-------------------
const props = defineProps({
  // 是否显示多选表格
  isShowmultiSelect: {
    type: Boolean,
    default: false,
  },
  // 是否允许多选
  isMulti: {
    type: Boolean,
    default: true,
  },
  // 是否展示全选
  disabledAll: {
    type: Boolean,
    default: false,
  },
  border: {
    type: Boolean,
    default: true,
  },
  // 查询条件,此处主要需要分页的条件
  query: {
    type: Object,
    default() {
      return {
        offset: 1,
        limit: 20,
      }
    },
  },
  // loading状态
  listLoading: {
    type: Boolean,
    default: false,
  },
  // 数据
  data: {
    type: Array,
    default() {
      return []
    },
  },
  // 表格高度
  height: {
    type: Number,
    default() {
      return null
    },
  },
  // 数据总数
  total: {
    type: Number,
    default: 0,
  },
  // 数据列配置
  columns: {
    type: Array<TableColumn>,
    default() {
      return []
    },
  },
  // 是否需要分页控件
  pagination: {
    type: Boolean,
    default: true,
  },
  // 配置项
  options: {
    type: Object,
    default() {
      return {
        needIndex: true, // 是否需要序号列
        border: true, // 是否需要上方边框
      }
    },
  },
  // 可选单页显示条数
  pageSizes: {
    type: Array,
    default() {
      return [5, 10, 20, 30, 50, 100, 150, 200]
    },
  },
  // 表格大小
  size: {
    type: String,
    default: 'default',
  }, // 表格大小,默认,small,mini等,与el-table条件相同
  // 是否要右击菜单
  needContextmenu: {
    type: Boolean,
    default: false,
  },
  // 单选默认选中
  defaultSingleChecked: {
    type: String,
    default: '',
  },
})
const emit = defineEmits(['change', 'selectionChange', 'rowClick', 'rowDbClick', 'multiSelect', 'filterChange', 'rowDisabled', 'handleClickFollowLink'])
// -------定义数据--------------
interface columnsCheckInfo {
  text: string
  show: boolean
}
const columnsChecked: Ref<columnsCheckInfo[]> = ref([])
const table = ref<InstanceType<typeof ElTable>>()
const singleChecked = ref(' ') // 单选选中id
// 初始化列显示状态
// function initColumnsState() {
//   columnsChecked.value = []
//   for (const column of props.columns) {
//     columnsChecked.value.push({ text: column.text, show: !!column.show })
//   }
// }

// 最终展示列
// const columnsFiltered: Ref<TableColumn[]> = ref([])
// 切换列
// function changeColumns() {
//   columnsFiltered.value = []
//   for (const i in props.columns) {
//     if (columnsChecked.value[i].show === true) {
//       columnsFiltered.value.push(props.columns[i])
//     }
//   }
// }
// 计算索引值方法
function indexMethod(index: number) {
  return props.query.limit * (props.query.offset - 1) + index + 1
}
// 刷新
function refresh() {
  emit('change')
}
// 改变页容量
function handleSizeChange(val: number) {
  emit('change', { size: val })
}
// 改变当前页
function handleCurrentChange(val: number) {
  emit('change', { page: val })
}
// 多选选中结果
function selectionChange(selection: []) {
  emit('selectionChange', selection)
}
// 点击行
function rowClick(row: object, column?: any, event?: any) {
  emit('rowClick', row)
}
// 双击行
function rowDbClick(row: object, column?: any, event?: any) {
  emit('rowDbClick', row)
}
// 监听columns
// watch(props.columns, (val) => {
//   initColumnsState()
//   changeColumns()
// })
// 多选
const multiSelectData = ref([])
const handleSelectionChange = (val: any) => {
  // console.log(val)
  multiSelectData.value = val
  emit('multiSelect', val)
}
// 单选选中
const radioChange = () => {
  const checkValue = props.data.find((item: any) => item.id == singleChecked.value)
  emit('multiSelect', [checkValue])
}
// 清除多选选中
const clearMulti = () => {
  console.log('清理选中')
  table.value!.clearSelection()
  singleChecked.value = ''
}
// elementUI的合并行方法
function mergeTableRow({ row, column, rowIndex, columnIndex }: any) {
  // console.log(row, column, rowIndex, columnIndex);
  const span = `${column.property}-span`
  if (row[span]) {
    return row[span]
  }
}
const setScrollLeft = (distance: number) => {
  table.value!.setScrollLeft(distance)
}
defineExpose({
  clearMulti,
  // initColumnsState,
  table,
  setScrollLeft,
})
// onBeforeMount(() => {
//   initColumnsState()
//   changeColumns()
// })
// 多选框单选功能
const selectClick = (selection: any, row: any) => {
  if (!props.isMulti) {
    console.log('selectClick')
    if (selection.length > 1) {
      const del_row = selection.shift()
      table.value?.toggleRowSelection(del_row, false) // 用于多选表格,切换某一行的选中状态,如果使用了第二个参数,则是设置这一行选中与否(selected 为 true 则选中)
    }
  }
}
// 筛选条件发生变化
const filterChange = (val: { [key: string]: string }) => {
  emit('filterChange', val)
}
const selectable = (row: any, index: number) => {
  let result = true
  // status 接收参数  true 禁用  false 可选
  emit('rowDisabled', row, index, (status: string) => {
    if (status === 'true') {
      result = false
    }
    else if (status === 'false') {
      result = true
    }
    else {
      result = true
    }
  })
  return result
}
const handleClickFollowLink = (row: any, title: string) => {
  emit('handleClickFollowLink', row, title)
}

// 右击当前行操作
const clickIndex = ref(-1)
const contextmenu = (row: any, column: any, event: Event, index: number) => {
  if (!props.needContextmenu) { return }
  // 阻止默认的右键菜单
  event.preventDefault()
  clickIndex.value = props.data.findIndex(item => item === row)
  console.log('右击', clickIndex.value)
  // 获取自定义菜单元素
  var menu = document.getElementById('custom-menu') as HTMLElement
  // 设置自定义菜单的位置并显示
  let positionX = event.pageX
  let positionY = event.pageY
  if (document.documentElement.clientHeight - event.pageY < 204) {
    positionY = document.documentElement.clientHeight - 204 + 102
  }
  if (document.documentElement.clientWidth - event.pageX < 146) {
    positionX = document.documentElement.clientWidth - 146
  }
  menu.style.top = `${positionY - 102}px`
  menu.style.left = `${positionX}px`
  menu.style.display = 'block'
}
// 点击其他位置隐藏自定义菜单
document.addEventListener('click', () => {
  if (!props.needContextmenu) { return }
  if (document.getElementById('custom-menu')) {
    (document.getElementById('custom-menu') as HTMLElement).style.display = 'none'
  }
})
// 添加行
const costomAddRow = (type: string) => {
  if (type === 'current-pre') {
    // 当前行前方插入
    props.data.splice(clickIndex.value, 0, JSON.parse(JSON.stringify(props.data[clickIndex.value])))
  }
  else if (type === 'current-next') {
    // 当前行后方方插入
    props.data.splice(clickIndex.value + 1, 0, JSON.parse(JSON.stringify(props.data[clickIndex.value])))
  }
  else if (type === 'list-head') {
    // 列表头行插入
    props.data.splice(0, 0, JSON.parse(JSON.stringify(props.data[clickIndex.value])))
  }
  else if (type === 'list-tail') {
    // 列表尾行插入
    props.data.splice(props.data.length, 0, JSON.parse(JSON.stringify(props.data[clickIndex.value])))
  }
  else if (type === 'select-pre') {
    // 选中行前方插入
    if (!multiSelectData.value.length) {
      ElMessage.warning('未选择数据')
      return
    }
    multiSelectData.value.forEach((item, index) => {
      const dataIndex = props.data.findIndex(citem => item === citem)
      props.data.splice(dataIndex, 0, JSON.parse(JSON.stringify(item)))
    })
    table.value!.clearSelection()
  }
  else if (type === 'select-next') {
    // 选中行后方插入
    if (!multiSelectData.value.length) {
      ElMessage.warning('未选择数据')
      return
    }
    multiSelectData.value.forEach((item, index) => {
      const dataIndex = props.data.findIndex(citem => item === citem)
      props.data.splice(dataIndex + 1, 0, JSON.parse(JSON.stringify(item)))
    })
    table.value!.clearSelection()
  }
  else if (type === 'del-current') {
    props.data.splice(clickIndex.value, 1)
  }
  else if (type === 'del-select') {
    if (!multiSelectData.value.length) {
      ElMessage.warning('未选择数据')
      return
    }
    multiSelectData.value.forEach((item, index) => {
      const dataIndex = props.data.findIndex(citem => item === citem)
      props.data.splice(dataIndex, 1)
    })
    table.value!.clearSelection()
  }
  clickIndex.value = -1
}

watch(() => props.defaultSingleChecked, (newValue) => {
  console.log('监听到单选选中id:', newValue)
  if (newValue) {
    singleChecked.value = newValue
  }
}, { immediate: true })
</script>

<template>
  <div id="normal-table-com">
    <el-table
      id="print"
      ref="table" v-loading="listLoading" :class="`${disabledAll ? 'selectAllbtnDis' : ''}`" :data="data" :height="height" :border="border" stripe :size="size"
      style="width: 100%;" :row-key="(row) => { return row.id || row.index }" :span-method="mergeTableRow"
      @selection-change="handleSelectionChange" @select="selectClick" @row-click="rowClick" @row-dblclick="rowDbClick"
      @filter-change="filterChange" @row-contextmenu="contextmenu"
    >
      <slot name="preColumns" />

      <el-table-column v-if="!isMulti" label="" width="60" align="center" fixed>
        <template #default="scope">
          <el-radio v-model="singleChecked" :label="scope.row.id" class="radio" @change="radioChange" />
        </template>
      </el-table-column>
      <el-table-column
        v-if="isShowmultiSelect && isMulti" :reserve-selection="true" type="selection" width="38"
        :selectable="selectable" fixed="left" header-cell-class-name="all-select" class="all-select"
      />
      <template v-for="column of columns" :key="column.value">
        <el-table-column
          v-if="!column.isFilters" :label="column.text" :prop="column.value" :width="column.width"
          :align="column.align" :show-overflow-tooltip="column.showOverflow ? column.showOverflow : true"
          :fixed="column.fixed"
        >
          <template #default="scope">
            <span
              v-if="!column.filter && !column.isLink && !column.isCustom"
              :style="column.styleFilter ? column.styleFilter(scope.row) : ''"
            >{{ scope.row[column.value] }}</span>
            <span
              v-if="column.filter && !column.isLink"
              :style="column.styleFilter ? column.styleFilter(scope.row) : ''"
            >{{ column.filter(scope.row) }}</span>
            <div v-if="column.isCustom">
              <slot name="isCustom" :scope="scope" :column="column" />
            </div>
          </template>
        </el-table-column>
        <el-table-column
          v-if="column.isFilters" :label="column.text" :prop="column.value" :width="column.width"
          :align="column.align" :show-overflow-tooltip="column.showOverflow ? column.showOverflow : true"
          :fixed="column.fixed" :column-key="column.value" :filters="column.filters" :filter-multiple="false"
        >
          <template #default="scope">
            <span v-if="!column.filter" :style="column.styleFilter ? column.styleFilter(scope.row) : ''">{{
              scope.row[column.value] }}</span>
            <span v-else :style="column.styleFilter ? column.styleFilter(scope.row) : ''">{{ column.filter(scope.row)
            }}</span>
          </template>
        </el-table-column>
      </template>

      <slot name="columns" />
    </el-table>
    <div v-if="props.pagination" style="width: 100%;margin-top: 10px;">
      <el-pagination
        :current-page="props.query.offset" :page-sizes="props.pageSizes as number[]"
        :page-size="props.query.limit" :total="props.total" layout="total, sizes, prev, pager, next"
        @size-change="handleSizeChange" @current-change="handleCurrentChange"
      />
    </div>
    <!-- 自定义菜单 -->
    <div id="custom-menu">
      <p class="menu-item" @click="costomAddRow('current-pre')">
        当前行前方插入
      </p>
      <p class="menu-item" @click="costomAddRow('current-next')">
        当前行后方插入
      </p>
      <p class="menu-item" @click="costomAddRow('list-head')">
        列表头行插入
      </p>
      <p class="menu-item" @click="costomAddRow('list-tail')">
        列表尾行插入
      </p>
      <p v-if="isShowmultiSelect && isMulti" class="menu-item" @click="costomAddRow('select-pre')">
        选中行前方插入
      </p>
      <!--  -->
      <p v-if="isShowmultiSelect && isMulti" class="menu-item" @click="costomAddRow('select-next')">
        选中行后方插入
      </p>
      <p class="menu-item" @click="costomAddRow('del-current')">
        删除当前行
      </p>
      <p v-if="isShowmultiSelect && isMulti" class="menu-item" @click="costomAddRow('del-select')">
        删除选中行
      </p>
    </div>
  </div>
</template>

<style lang="scss" scoped>
::v-deep .selectAllbtnDis .el-table__header-wrapper .cell .el-checkbox__inner {
  display: none;
}
</style>

<style lang="scss" scoped>
#custom-menu {
  display: none;
  position: fixed;
  background-color: #fff;
  border-radius: 5px;
  padding: 5px 0;
  z-index: 1000;
  border: 1px solid #c8c9cc;
  box-shadow: 0 0 12px rgb(0 0 0 / 12%);

  .menu-item {
    display: flex;
    align-items: center;
    white-space: nowrap;
    list-style: none;
    line-height: 22px;
    padding: 5px 16px;
    margin: 0;
    // font-size: var(--el-font-size-base);
    color: #606266;
    cursor: pointer;
    outline: none;

    &:hover {
      background-color: #ecf5ff;
      color: #409eff;
    }
  }
}

#normal-table-com {
  width: 100%;
}

.single-table {
  width: 100%;

  :deep(.el-table th.el-table__cell:nth-child(1) .cell) {
    visibility: hidden;
  }
}

.follow-link {
  color: #0d76d4;
  cursor: pointer;

  &:hover {
    color: #04395e;
  }
}
</style>

<style lang="scss">
#normal-table-com {
  .el-radio__label {
    display: none !important;
  }
  // 单元格样式
  .el-table__cell {
    position: static !important; // 解决el-image 和 el-table冲突层级冲突问题
    z-index: 99999999;
  }
}
</style>