vue3 elementui plus 可编辑表格 完整例子

完整代码:

1.组件

cpp 复制代码
<template>
  <div class="mult_table_box">
    <el-form ref="formRef" :model="formData" :rules="formRules">
      <el-table
        ref="tableRef"
        :data="formData.tableDataValue"
        :header-cell-style="{ 'text-align': 'center' }"
        @row-click="handleEdit"
        @selection-change="handleSelectionChange"
        class="mult_table_"
        max-height="540"
      >
        <el-table-column
          type="selection"
          width="55"
          align="center"
          v-if="showCheckbox"
          :selectable="checkSelectable"
        />
        <el-table-column type="index" label="序号" width="80" align="center" />

        <el-table-column
          v-for="(column, index) in columns"
          :key="index"
          :prop="column.prop"
          :label="column.label"
          :width="column.width"
          :show-overflow-tooltip="
            typeof column.showTooltip === 'boolean' ? column.showTooltip : showTooltip
          "
          align="center"
        >
         <template #header>
              <div class="flex_ flex_cetner"
                ><span class="red_" v-if="column.required">*</span
                ><span> {{ column.label }}</span></div
              >
              </template>
          <!-- 多级表头 -->
          <el-table-column
            v-for="(column2, cIndex) in column.children"
            :key="cIndex"
            :prop="column2.prop"
            :label="column2.label"
            :width="column2.width"
            :show-overflow-tooltip="
              typeof column.showTooltip === 'boolean' ? column.showTooltip : showTooltip
            "
          >
            <template #header>
              <div class="flex_ flex_cetner"
                ><span class="red_" v-if="column2.required">*</span
                ><span> {{ column2.label }}</span></div
              >
            </template>
            <template #default="scope">
              <template v-if="scope.row.isEdit && !column2.disabled">
                <el-form-item
                  :prop="'tableDataValue[' + scope.$index + '].' + column.prop"
                  :rules="[
                    { required: column2.required, message: '', trigger: 'blur' }
                  ]"
                >
             
                    <!-- 输入框 -->
                    <el-input
                      v-if="column2.type == 'input'"
                      v-model="scope.row[column2.prop]"
                      :placeholder="`请输入${column2.label}`"
                    />
                    <!-- 下拉 -->
                    <el-select
                      v-else-if="column2.type == 'select'"
                      v-model="scope.row[column2.prop]"
                    >
                      <el-option
                        v-for="option in getColumnOptions(column2)"
                        :key="option.value"
                        :label="option.label"
                        :value="option.value"
                      />
                    </el-select>
                    <!-- 自定义 -->
                    <div v-else-if="column2.type === 'slot'" class="w100">
                      <slot :name="column2.prop + 'Edit'" :row="scope.row" :column="column2"></slot>
                    </div>
                 
                </el-form-item>
              </template>
              <!-- 不编辑展示状态-多层表头 -->
              <template v-else>
                <el-form-item prop="operation">
                  <div v-if="column2.type == 'switch'">
                    <el-switch
                      v-model="scope.row[column2.prop]"
                      active-text=""
                      inactive-text=""
                      disabled
                    />
                  </div>
                  <div
                    v-else-if="column2.type == 'dictSelect' || column2.type == 'dictSelect_string'"
                  >
                    <div v-if="column2.propLable">{{ scope.row[column2.propLable] }}</div>
                    <div v-else>
                      {{ getDictLabel(scope.row[column2.prop], column2.dictName) }}
                    </div>
                  </div>
                  <div v-else-if="column2.type == 'datetime'">
                    {{ formatDate(scope.row[column2.prop]) }}
                  </div>
                  <div v-else-if="column2.type === 'slot'">
                    <slot :name="column2.prop" :row="scope.row" :column="column2"></slot>
                  </div>

                  <div v-else>{{ scope.row[column2.prop] }}</div>
                </el-form-item>
              </template>
            </template>
          </el-table-column>

          <template #default="scope" v-if="!column.children?.length">
            <template v-if="scope.row.isEdit && !column.disabled">
              <el-form-item
                :prop="'tableDataValue[' + scope.$index + '].' + column.prop"
                :rules="[{ required: column.required, message: '', trigger: 'blur' }]"
              >
                <!-- 输入框 -->
                <el-input
                  v-if="column.type == 'input'"
                  v-model="scope.row[column.prop]"
                  :placeholder="`请输入${column.label}`"
                />
                <!-- numner输入框(小数) -->
                <div @click.stop>
                  <el-input-number
                    v-if="column.type == 'input-number'"
                    :placeholder="`请输入${column.label}`"
                    v-model="scope.row[column.prop]"
                    :precision="1"
                    :step="0.1"
                  />
                </div>
                <!-- number输入框(整数) -->
                <div @click.stop>
                  <el-input-number
                    v-if="column.type == 'number'"
                    :placeholder="`请输入${column.label}`"
                    v-model="scope.row[column.prop]"
                    :precision="0"
                    :step="1"
                  />
                </div>
                <!-- 文本域 -->
                <el-input
                  v-if="column.type == 'textarea'"
                  type="textarea"
                  v-model="scope.row[column.prop]"
                  :placeholder="`请输入${column.label}`"
                  resize="vertical"
                  :autosize="{ minRows: 2, maxRows: 4 }"
                  clearable
                />
                <!-- 下拉 -->
                <el-select
                  v-else-if="column.type == 'select'"
                  v-model="scope.row[column.prop]"
                  clearable
                >
                  <el-option
                    v-for="option in getColumnOptions(column)"
                    :key="option.value"
                    :label="option.label"
                    :value="option.value"
                  />
                </el-select>
                <!-- 字典下拉-value为string -->
                <el-select
                  v-else-if="column.type == 'dictSelect_string'"
                  v-model="scope.row[column.prop]"
                  clearable
                >
                  <el-option
                    v-for="dict in getDictOptions(DICT_TYPE[column.dictName])"
                    :key="dict.value"
                    :label="dict.label"
                    :value="dict.value"
                  />
                </el-select>
                <!-- 字典下拉-value为number -->
                <el-select
                  v-else-if="column.type == 'dictSelect'"
                  v-model="scope.row[column.prop]"
                  clearable
                >
                  <el-option
                    v-for="dict in getIntDictOptions(DICT_TYPE[column.dictName])"
                    :key="dict.value"
                    :label="dict.label"
                    :value="dict.value"
                  />
                </el-select>
                <el-date-picker
                  v-else-if="column.type === 'date'"
                  v-model="scope.row[column.prop]"
                  type="date"
                  :placeholder="`请选择${column.label}`"
                  value-format="YYYY-MM-DD"
                  format="YYYY-MM-DD"
                  style="width: 100%"
                />
                <el-date-picker
                  v-else-if="column.type === 'datetime'"
                  v-model="scope.row[column.prop]"
                  type="datetime"
                  :placeholder="`请选择${column.label}`"
                  value-format="YYYY-MM-DD HH:mm:ss"
                  format="YYYY-MM-DD HH:mm:ss"
                  style="width: 100%"
                />
                <el-date-picker
                  v-else-if="column.type === 'timestamp'"
                  v-model="scope.row[column.prop]"
                  value-format="x"
                  type="datetime"
                  :placeholder="`请选择${column.label}`"
                  style="width: 100%"
                />
                <el-switch
                  v-else-if="column.type === 'switch'"
                  v-model="scope.row[column.prop]"
                  active-text=""
                  inactive-text=""
                />
                <div v-else-if="column.type == 'uploadFile'" @click.stop>
                  <UploadFile
                    :is-show-tip="false"
                    v-model="scope.row[column.prop]"
                    :modelValueIsString="true"
                  />
                </div>
                <div v-else-if="column.type == 'uploadImgs'" @click.stop>
                  <UploadImgs
                    v-model="scope.row[column.prop]"
                    :modelValueIsString="true"
                    :limit="9"
                    height="50px"
                    width="50px"
                  />
                </div>
                <div v-else-if="column.type === 'slot'" @click.stop class="w100">
                  <slot :name="column.prop + 'Edit'" :row="scope.row" :column="column"></slot>
                </div>
              </el-form-item>
            </template>
            <!-- 不编辑展示状态 -->
            <template v-else>
              <el-form-item prop="operation">
                <div v-if="column.type == 'switch'">
                  <el-switch
                    v-model="scope.row[column.prop]"
                    active-text=""
                    inactive-text=""
                    disabled
                  />
                </div>
                <div v-else-if="column.type == 'datetime'">
                  {{ formatDate(scope.row[column.prop]) }}
                </div>
                <div v-else-if="column.type == 'timestamp'">
                  {{ formatDate(scope.row[column.prop]) }}
                </div>
                <div v-else-if="column.type == 'uploadFile'">
                  <UploadFile
                    :is-show-tip="false"
                    v-model="scope.row[column.prop]"
                    :disabled="true"
                  />
                </div>
                <div v-else-if="column.type == 'uploadImgs'">
                  <UploadImgs
                    v-model="scope.row[column.prop]"
                    :limit="9"
                    :modelValueIsString="true"
                    height="50px"
                    width="50px"
                    :disabled="true"
                  />
                </div>
                <div v-else-if="column.type == 'dictSelect' || column.type == 'dictSelect_string'">
                  <div v-if="column.propLable">{{ scope.row[column.propLable] }}</div>
                  <div v-else>
                    {{ getDictLabel(scope.row[column.prop], column.dictName) }}
                  </div>
                </div>

                <div v-else-if="column.type === 'slot'">
                  <div v-if="column.propLable">
                    {{ scope.row[column.propLable] }}
                  </div>
                  <slot v-else :name="column.prop" :row="scope.row" :column="column"></slot>
                </div>
                <div v-else>{{ scope.row[column.prop] }}</div>
              </el-form-item>
            </template>
          </template>
        </el-table-column>
        <el-table-column
          fixed="right"
          label="操作"
          prop="city"
          min-width="160"
          v-if="showOperationColumn"
          :width="operationWidth"
        >
          <!-- :width="setOperationBoxWidth()" -->
          <template #default="scope">
            <el-form-item prop="operation">
              <div class="table_btn_box" @click.stop>
                <!-- 表格编辑默认的按钮 -->
                <span id="operationBox" v-if="allowEdit">
                  <el-button
                    link
                    type="primary"
                    size="small"
                    :icon="EditPen"
                    v-if="!scope.row.isEdit"
                    @click.stop="handleEdit(scope.row)"
                    >编辑</el-button
                  >
                  <el-button
                    link
                    type="success"
                    size="small"
                    :icon="Check"
                    v-if="scope.row.isEdit"
                    @click.stop="handleSave(scope.row)"
                    >保存</el-button
                  >
                  <el-button
                    link
                    type="warning"
                    size="small"
                    class="mr12px"
                    :icon="Close"
                    v-if="scope.row.isEdit"
                    @click.stop="handleCancel(scope)"
                    >取消</el-button
                  >
                  <el-button
                    v-if=" !scope.row.isEdit && scope.row?.id"
                    link
                    type="danger"
                    class="mr12px"
                    :icon="Delete"
                    size="small"
                    @click.stop="handleDelete(scope.row)"
                    >删除</el-button
                  >
                </span>
                <!-- 需要把编辑emit出去的按钮 使用弹窗类 -->
                <!-- 其他需要自定义的按钮 -->
                <slot name="operation" :row="scope.row"></slot>
              </div>
            </el-form-item>
          </template>
        </el-table-column>
      </el-table>
    </el-form>

  </div>
</template>
<script lang="ts" setup>
import { Plus, View, Close, EditPen, Delete, Check } from '@element-plus/icons-vue'
import { columnsType } from '@/api/common/types'
import { ElMessageBox, ElMessage, FormInstance } from 'element-plus'
import { formatDate } from '@/utils/formatTime'
import { getDictOptions, getIntDictOptions, getDictObj, DICT_TYPE } from '@/utils/dict'
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import { cloneDeep } from 'lodash-es'
import ProcessUserSelect from '@/components/approvalComponent/ProcessUserSelect.vue'
import approvalHistoryDialog from '@/views/equipmentManagement/ledger/equipmentLedger/abnormalMovementManagement/approvalHistoryDialog.vue'
import { useRouter } from 'vue-router'
const message = useMessage() // 消息弹窗
const router = useRouter()
const emit = defineEmits([
  'handleEdit',
  'handleEditEmit',
  'handleDelete',
  'handleSave',
  'handleProcessSubmit',
  'handleRefresh'
])
const props = defineProps({
  columns: {
    type: Array as PropType<columnsType[]>,
    required: true,
    default: () => []
  },
  tableData: {
    type: Array,
    required: true,
    default: () => []
  },
  allOptionList: {
    type: Object,
    required: false,
    default: () => {}
  },
  allowEdit: {
    //表格编辑
    type: Boolean,
    default: true
  },
  showOperationColumn: {
    type: Boolean,
    default: true
  },
  operationWidth: {
    type: String,
    default: '160'
  },
  showTooltip: {
    type: Boolean,
    default: true
  },
  showCheckbox: {
    type: Boolean,
    default: true
  },
})

const formRules = reactive({
  rule: [{ required: true, message: '邮箱不能为空', trigger: 'blur' }]
})
let formRef = ref()
const formData = reactive({
  rule: '',
  tableDataValue: []
})
// const tableDataValue = ref(props.tableData)
let dataLength = ref(0)
// 监听data变化
watch(
  () => props.tableData,
  (newVal) => {
    // 初次进入给dataLength赋值即可
    if (!dataLength.value) {
      dataLength.value = props.tableData.length
      formData.tableDataValue = newVal
      return
    }
    // 新增、判断是否有正在编辑的行 是-取消新增
    if (dataLength.value == newVal?.length - 1) {
      let dataLenIndex = formData.tableDataValue.length - 1
      if (EditRow.value?.isEdit) {
        formData.tableDataValue.splice(dataLenIndex, 1)
        message.warning('有正在编辑的行,请取消编辑再尝试')
        return
      }

      let newRow = newVal[dataLenIndex]
      EditRow.value = newRow
    }
    // 新增后滚动到底部
    if (dataLength.value && dataLength.value == newVal?.length - 1) {
      scrollToBottom()
    }
    dataLength.value = formData.tableDataValue.length
    formData.tableDataValue = newVal
  },
  { deep: true, immediate: true }
)

onMounted(() => {
  initData()
})

//获取数据
const initData = async () => {}
// 获取列选项
const getColumnOptions = (column: columnsType) => {
  if (column.optionApi) {
    return props.allOptionList[column.optionApi] || []
  }
  // return column.options || []
}

// 表格操作
const multipleSelection = ref([])
const handleSelectionChange = (rows) => {
  multipleSelection.value = rows.map((item) => {
    return item.id
  })
}

//编辑
let originalRow = reactive({}) //原始行,作用:取消编辑时,数据恢复
let EditRow = ref()
const handleEdit = (row: any) => {
  if (EditRow.value?.isEdit && EditRow.value != row) {
    message.warning('有正在编辑的行,请取消编辑再尝试')
    return
  }
    if (props.allowEdit&& props.showOperationColumn) {
      row.isEdit = true
      let nRow = cloneDeep(toRaw(row))
      originalRow = { ...nRow }
      EditRow.value = row
    
  }
}

const handleSave = async (row) => {
  const valid = await formRef.value.validate()
  if (!valid) return
  row.isEdit = false
  EditRow.value = row
  emit('handleSave', row)
}
const handleDelete = (row: any) => {
  ElMessageBox.confirm('确认删除该行数据吗?', '提示', {
    confirmButtonText: '确 认',
    cancelButtonText: '取 消'
  })
    .then(() => {
      //确认
      emit('handleDelete', row)
    })
    .catch(() => console.info('操作取消'))
}

//取消编辑
const handleCancel = (scope) => {
  let index = scope.$index
  if (!scope.row.id) {
    scope.row.isEdit = false
    formData.tableDataValue.splice(index, 1)
  } else {
    originalRow.isEdit = false
    EditRow.value.isEdit = false
    formData.tableDataValue.splice(index, 1, originalRow)
  }
}

const handleEditEmit = (row) => {
  emit('handleEditEmit', row)
}
//#endregion


// 是否可以勾选-ge
const checkSelectable = (row) => {
  if (row.id) {
return true
  } else {
    return false
  }
}
//获取字典lable
const getDictLabel = (value, dictName) => {
  let label = ''
  if (value != null) {
    let option = getDictObj(DICT_TYPE[dictName], value)
    label = option?.label || ''
  }
  return label
}

// 表格滚动到最底部
const tableRef = ref()
const scrollToBottom = async () => {
  nextTick(() => {
    tableRef.value?.setScrollTop(tableRef.value?.$refs.tableBody.scrollHeight)
  })
}

defineExpose({
  multipleSelection
})
</script>

<style lang="scss" scoped>
.mult_table_box {
  margin-top: 10px;
  margin-bottom: 20px;

  .mr10 {
    margin-right: 10px;
  }

  #operationBox {
    // white-space: nowrap;
    // width: auto;
    // overflow: hidden;
  }
  .mr12px {
    // margin-right: 12px;
  }
  .ml15 {
    margin-left: 12px;
  }
  .table_btn_box {
    // display: flex;
    .el-button {
      margin-left: 12px !important;
      margin-right: 0px !important;
    }
  }
  .flex_ {
    display: flex;
    align-items: center;
  }
  .flex_cetner{
  justify-content: center;
  }
  .red_ {
    color: red;
    font-size: 14px;
    margin-right: 5px;
  }
}
</style>

<style lang="scss">
.mult_table_box {
  .el-select {
    .el-select__wrapper {
      height: 30px !important;
    }
  }
  .el-form-item {
    margin-bottom: 1px !important;
    .el-form-item__content {
      font-size: 12px !important;
    }
  }

  .el-date-editor,
  .el-input-number {
    width: 100%;
  }
  .el-scrollbar__bar {
    display: block !important;
  }
  .el-select,
  .w100 {
    width: 100%;
  }
}
</style>

2.使用的表头格式为:

cpp 复制代码
export interface columnsType {
  prop: string
  propLable?: string
  label: string
  width?: string
  type:
    | 'input'
    | 'textarea'//文本域
    | 'select'//下拉
    | 'date'//yyyy-mmm-hh格式
    | 'datetime'//yyyy-mmm-hh dd:hh:ss格式
    | 'timestamp'//时间戳格式
    | 'switch'//开关
    | 'number'//整数
    | 'input-number'//一位小数点
    | 'getValue'
    | 'parentKksCode'
    | 'uploadFile'//上传文件
    | 'uploadImgs'//上传图片
    | 'dictSelect'//字典下拉-value为numner
    | 'dictSelect_string'//字典下拉-value为string
    | 'slot' // 自定义
  optionApi?: string//废弃?
  dictName?: string//字典名称
  disabled?: boolean
  children?: columnsType[]
  showTooltip?: boolean
  required?: boolean
}

例:

cpp 复制代码
const kksFuntionColumns: columnsType[] = [
  {
    prop: 'name',
    label: 'KKS编码字典',
    type: 'input',
    width: '',
    children: [
      {
        prop: 'codePrefix',
        label: '编码前缀',
        type: 'input',
        width: '80',
        disabled: true
      },
      {
        prop: 'code1',
        label: '全厂码(F0)',
        type: 'input',
        width: '280'
      },
    ]
  },
  {
    prop: 'isClosed',
    label: '是否关闭',
    type: 'slot',//如果为slot 可以在使用组件的页面 自定义编辑或者展示时的形式 solt的name为prop的值,编辑时solt的name为'prop的值+Edit' 下面有例子
    width: '240',
    disabled: true
  },
 ]

3.使用

cpp 复制代码
  <!-- 表格 -->
        <multi-table
          ref="muliTableRef"
          :columns="personnelPassColumns"
          :tableData="tableData"
          :showTooltip="true"
          v-loading="loading"
          @handle-save="handleSave"
          @handle-delete="handleDelete"
          @handle-refresh="getTableData"
          operationWidth="200"
        >
          <!-- 是否关闭 -->
          <template #isClosed="scope">
            {{ scope.row.isClosed ? '是' : '否' }}
          </template>
          <template #isClosedEdit="scope">
           自定义编辑的时候如何展示
          </template>
          <!-- 操作 -->
          <template #operation="scope">
            <el-button
              link
              type="info"
              size="small"
              @click.stop="handleClose(scope.row)"
            >
              测试按钮
            </el-button>
          </template>
        </multi-table>
相关推荐
IT_陈寒2 小时前
SpringBoot自动配置把我坑惨了,原来它偷偷干了这么多事
前端·人工智能·后端
nodcloud2 小时前
Chrome 142 更新导致点可云报表助手打印异常:启动服务仍提示启动的解决方案
前端·数据库·chrome
ZC跨境爬虫2 小时前
3D地球卫星轨道可视化平台开发Day2(轨道错位Bug修复+模块化结构优化)
前端·3d·html·json·bug
ZC跨境爬虫2 小时前
3D 地球卫星轨道可视化平台开发 Day1(3D 场景、卫星渲染与筛选交互实现)
前端·3d·html·json·交互
研究点啥好呢2 小时前
Github热榜项目推荐 | React生态系统的成熟演进
前端·react.js·github
daols882 小时前
vxe-table 自定义数字行主键,解决默认字符串主键与后端类型不匹配问题
前端·javascript·vue.js·vxe-table
啥都不懂的小小白2 小时前
Vue 小白入门|Pinia 核心用法全解
javascript·vue.js·ecmascript
skywalk81632 小时前
g4f提供的模型调用:python JavaScript和curl
前端·javascript·vue.js·g4f
R-sz2 小时前
前端直接将页面 HTML 报表导出为 Word 文档,html转word
前端·html·word