<template> 
 | 
  <el-table 
 | 
    ref="multipleTable" 
 | 
    v-loading="tableLoading" 
 | 
    :border="border" 
 | 
    :data="tableData" 
 | 
    :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" 
 | 
    :height="height" 
 | 
    :highlight-current-row="highlightCurrentRow" 
 | 
    :row-class-name="rowClassName" 
 | 
    :row-style="rowStyle" 
 | 
    :row-key="rowKey" 
 | 
    style="width: 100%" 
 | 
    tooltip-effect="dark" 
 | 
    :expand-row-keys="expandRowKeys" 
 | 
    :show-summary="isShowSummary" 
 | 
    :summary-method="summaryMethod" 
 | 
    stripe 
 | 
    @row-click="rowClick" 
 | 
    @current-change="currentChange" 
 | 
    @selection-change="handleSelectionChange" 
 | 
    @expand-change="expandChange" 
 | 
    class="lims-table" 
 | 
  > 
 | 
    <el-table-column 
 | 
      align="center" 
 | 
      type="selection" 
 | 
      width="55" 
 | 
      v-if="isSelection" 
 | 
    /> 
 | 
    <el-table-column align="center" label="序号" type="index" width="60" /> 
 | 
  
 | 
    <el-table-column 
 | 
      v-for="(item, index) in column" 
 | 
      :key="index" 
 | 
      :column-key="item.columnKey" 
 | 
      :filter-method="item.filterHandler" 
 | 
      :filter-multiple="item.filterMultiple" 
 | 
      :filtered-value="item.filteredValue" 
 | 
      :filters="item.filters" 
 | 
      :fixed="item.fixed" 
 | 
      :label="item.label" 
 | 
      :prop="item.prop" 
 | 
      show-overflow-tooltip 
 | 
      :align="item.align" 
 | 
      :sortable="!!item.sortable" 
 | 
      :type="item.type" 
 | 
      :width="item.width" 
 | 
    > 
 | 
      <template 
 | 
        v-if="item.hasOwnProperty('colunmTemplate')" 
 | 
        #[item.colunmTemplate]="scope" 
 | 
      > 
 | 
        <slot 
 | 
          v-if="item.theadSlot" 
 | 
          :name="item.theadSlot" 
 | 
          :index="scope.$index" 
 | 
          :row="scope.row" 
 | 
        /> 
 | 
      </template> 
 | 
  
 | 
      <template #default="scope"> 
 | 
        <!-- 插槽 --> 
 | 
        <div v-if="item.dataType == 'slot'"> 
 | 
          <slot 
 | 
            v-if="item.slot" 
 | 
            :index="scope.$index" 
 | 
            :name="item.slot" 
 | 
            :row="scope.row" 
 | 
          /> 
 | 
        </div> 
 | 
        <!-- 进度条 --> 
 | 
        <div v-else-if="item.dataType == 'progress'"> 
 | 
          <el-progress :percentage="Number(scope.row[item.prop])" /> 
 | 
        </div> 
 | 
        <!-- 图片 --> 
 | 
        <div v-else-if="item.dataType == 'image'"> 
 | 
          <img 
 | 
            :src="javaApi + '/img/' + scope.row[item.prop]" 
 | 
            alt="" 
 | 
            style="width: 40px; height: 40px; margin-top: 10px" 
 | 
          /> 
 | 
        </div> 
 | 
  
 | 
        <!-- tag --> 
 | 
        <div v-else-if="item.dataType == 'tag'"> 
 | 
          <el-tag 
 | 
            v-if=" 
 | 
              typeof dataTypeFn(scope.row[item.prop], item.formatData) === 
 | 
              'string' 
 | 
            " 
 | 
            :title="formatters(scope.row[item.prop], item.formatData)" 
 | 
            :type="formatType(scope.row[item.prop], item.formatType)" 
 | 
          > 
 | 
            {{ formatters(scope.row[item.prop], item.formatData) }} 
 | 
          </el-tag> 
 | 
  
 | 
          <el-tag 
 | 
            v-for="(tag, index) in dataTypeFn( 
 | 
              scope.row[item.prop], 
 | 
              item.formatData 
 | 
            )" 
 | 
            v-else-if=" 
 | 
              typeof dataTypeFn(scope.row[item.prop], item.formatData) === 
 | 
              'object' 
 | 
            " 
 | 
            :key="index" 
 | 
            :title="formatters(scope.row[item.prop], item.formatData)" 
 | 
            :type="formatType(tag, item.formatType)" 
 | 
          > 
 | 
            {{ item.tagGroup ? tag[item.tagGroup.label] ?? tag : tag }} 
 | 
          </el-tag> 
 | 
  
 | 
          <el-tag 
 | 
            v-else 
 | 
            :title="formatters(scope.row[item.prop], item.formatData)" 
 | 
            :type="formatType(scope.row[item.prop], item.formatType)" 
 | 
          > 
 | 
            {{ formatters(scope.row[item.prop], item.formatData) }} 
 | 
          </el-tag> 
 | 
        </div> 
 | 
  
 | 
        <!-- 按钮 --> 
 | 
        <div v-else-if="item.dataType == 'action'"> 
 | 
          <template v-for="(o, key) in item.operation" :key="key"> 
 | 
            <el-button 
 | 
              v-show="o.type != 'upload'" 
 | 
              size="small" 
 | 
              v-if="o.showHide ? o.showHide(scope.row) : true" 
 | 
              :disabled="o.disabled ? o.disabled(scope.row) : false" 
 | 
              :plain="o.plain" 
 | 
              type="primary" 
 | 
              :style="{ 
 | 
                color: 
 | 
                  o.name === '删除' || o.name === 'delete' 
 | 
                    ? '#f56c6c' 
 | 
                    : o.color, 
 | 
              }" 
 | 
              link 
 | 
              @click="o.clickFun(scope.row)" 
 | 
              :key="key" 
 | 
            > 
 | 
              {{ o.name }} 
 | 
            </el-button> 
 | 
            <el-upload 
 | 
              :action=" 
 | 
                javaApi + 
 | 
                o.url + 
 | 
                '?id=' + 
 | 
                (o.uploadIdFun ? o.uploadIdFun(scope.row) : scope.row.id) 
 | 
              " 
 | 
              ref="uploadRef" 
 | 
              size="small" 
 | 
              :multiple="o.multiple ? o.multiple : false" 
 | 
              :limit="1" 
 | 
              :disabled="o.disabled ? o.disabled(scope.row) : false" 
 | 
              :accept=" 
 | 
                o.accept 
 | 
                  ? o.accept 
 | 
                  : '.jpg,.jpeg,.png,.gif,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.zip,.rar' 
 | 
              " 
 | 
              v-if="o.type == 'upload'" 
 | 
              style="display: inline-block; width: 50px" 
 | 
              v-show="o.showHide ? o.showHide(scope.row) : true" 
 | 
              :headers="uploadHeader" 
 | 
              :before-upload="(file) => beforeUpload(file, scope.$index)" 
 | 
              :on-change=" 
 | 
                (file, fileList) => handleChange(file, fileList, scope.$index) 
 | 
              " 
 | 
              :on-error=" 
 | 
                (error, file, fileList) => 
 | 
                  onError(error, file, fileList, scope.$index) 
 | 
              " 
 | 
              :on-success=" 
 | 
                (response, file, fileList) => 
 | 
                  handleSuccessUp(response, file, fileList, scope.$index) 
 | 
              " 
 | 
              :on-exceed="onExceed" 
 | 
              :show-file-list="false" 
 | 
            > 
 | 
              <el-button 
 | 
                :size="o.size ? o.size : 'small'" 
 | 
                link 
 | 
                type="primary" 
 | 
                :disabled="o.disabled ? o.disabled(scope.row) : false" 
 | 
                >{{ o.name }}</el-button 
 | 
              > 
 | 
            </el-upload> 
 | 
          </template> 
 | 
        </div> 
 | 
        <!-- 可点击的文字 --> 
 | 
        <div 
 | 
          v-else-if="item.dataType == 'link'" 
 | 
          class="cell link" 
 | 
          style="width: 100%" 
 | 
          @click="goLink(scope.row, item.linkMethod)" 
 | 
        > 
 | 
          <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span> 
 | 
        </div> 
 | 
        <!-- 默认纯展示数据 --> 
 | 
        <div v-else class="cell" style="width: 100%"> 
 | 
          <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span> 
 | 
          <span v-else>{{ 
 | 
            formatters(scope.row[item.prop], item.formatData) 
 | 
          }}</span> 
 | 
        </div> 
 | 
      </template> 
 | 
    </el-table-column> 
 | 
  </el-table> 
 | 
  <pagination 
 | 
    v-if="page.total > 0" 
 | 
    :total="page.total" 
 | 
    :layout="page.layout" 
 | 
    :page="page.current" 
 | 
    :limit="page.size" 
 | 
    @pagination="paginationSearch" 
 | 
  /> 
 | 
</template> 
 | 
  
 | 
<script setup> 
 | 
import pagination from "./Pagination.vue"; 
 | 
import { ref, inject, getCurrentInstance } from "vue"; 
 | 
import { ElMessage } from "element-plus"; 
 | 
  
 | 
// 获取全局的 uploadHeader 
 | 
const { proxy } = getCurrentInstance(); 
 | 
const uploadHeader = proxy.uploadHeader; 
 | 
const javaApi = proxy.javaApi; 
 | 
  
 | 
const emit = defineEmits(["pagination", "expand-change", "selection-change"]); 
 | 
  
 | 
// Filters 
 | 
const typeFn = (val, row) => { 
 | 
  return typeof val === "function" ? val(row) : val; 
 | 
}; 
 | 
  
 | 
const formatters = (val, format) => { 
 | 
  return typeof format === "function" ? format(val) : val; 
 | 
}; 
 | 
  
 | 
// Props(使用 defineProps 的非 TS 形式) 
 | 
const props = defineProps({ 
 | 
  tableLoading: { 
 | 
    type: Boolean, 
 | 
    default: false, 
 | 
  }, 
 | 
  height: { 
 | 
    type: [Number, String], 
 | 
    default: "calc(100vh - 22em)", 
 | 
  }, 
 | 
  expandRowKeys: { 
 | 
    type: Array, 
 | 
    default: () => [], 
 | 
  }, 
 | 
  summaryMethod: { 
 | 
    type: Function, 
 | 
    default: () => {}, 
 | 
  }, 
 | 
  rowClick: { 
 | 
    type: Function, 
 | 
    default: () => {}, 
 | 
  }, 
 | 
  currentChange: { 
 | 
    type: Function, 
 | 
    default: () => {}, 
 | 
  }, 
 | 
  border: { 
 | 
    type: Boolean, 
 | 
    default: true, 
 | 
  }, 
 | 
  isSelection: { 
 | 
    type: Boolean, 
 | 
    default: false, 
 | 
  }, 
 | 
  isShowSummary: { 
 | 
    type: Boolean, 
 | 
    default: false, 
 | 
  }, 
 | 
  highlightCurrentRow: { 
 | 
    type: Boolean, 
 | 
    default: false, 
 | 
  }, 
 | 
  headerCellStyle: { 
 | 
    type: Object, 
 | 
    default: () => ({}), 
 | 
  }, 
 | 
  column: { 
 | 
    type: Array, 
 | 
    default: () => [], 
 | 
  }, 
 | 
  rowClassName: { 
 | 
    type: Function, 
 | 
    default: () => "", 
 | 
  }, 
 | 
  rowStyle: { 
 | 
    type: [Object, Function], 
 | 
    default: () => ({}), 
 | 
  }, 
 | 
  tableData: { 
 | 
    type: Array, 
 | 
    default: () => [], 
 | 
  }, 
 | 
  rowKey: { 
 | 
    type: String, 
 | 
    default: 'id', 
 | 
  }, 
 | 
  page: { 
 | 
    type: Object, 
 | 
    default: () => ({ 
 | 
      total: 0, 
 | 
      current: 0, 
 | 
      size: 10, 
 | 
      layout: "total, sizes, prev, pager, next, jumper", 
 | 
    }), 
 | 
  }, 
 | 
  total: { 
 | 
    type: Number, 
 | 
    default: 0, 
 | 
  }, 
 | 
}); 
 | 
  
 | 
// Data 
 | 
const uploadRefs = ref([]); 
 | 
const currentFiles = ref({}); 
 | 
const uploadKeys = ref({}); 
 | 
  
 | 
const indexMethod = (index) => { 
 | 
  return (props.page.current - 1) * props.page.size + index + 1; 
 | 
}; 
 | 
  
 | 
// 点击 link 事件 
 | 
const goLink = (row, linkMethod) => { 
 | 
  if (!linkMethod) { 
 | 
    return ElMessage.warning("请配置 link 事件"); 
 | 
  } 
 | 
  const parentMethod = getParentMethod(linkMethod); 
 | 
  if (typeof parentMethod === "function") { 
 | 
    parentMethod(row); 
 | 
  } else { 
 | 
    console.warn(`父组件中未找到方法: ${linkMethod}`); 
 | 
  } 
 | 
}; 
 | 
  
 | 
// 获取父组件方法(示例实现) 
 | 
const getParentMethod = (methodName) => { 
 | 
  const parentMethods = inject("parentMethods", {}); 
 | 
  return parentMethods[methodName]; 
 | 
}; 
 | 
  
 | 
const dataTypeFn = (val, format) => { 
 | 
  if (typeof format === "function") { 
 | 
    return format(val); 
 | 
  } else return val; 
 | 
}; 
 | 
  
 | 
const formatType = (val, format) => { 
 | 
  if (typeof format === "function") { 
 | 
    return format(val); 
 | 
  } else return ""; 
 | 
}; 
 | 
  
 | 
// 文件变化处理 
 | 
const handleChange = (file, fileList, index) => { 
 | 
  if (fileList.length > 1) { 
 | 
    const earliestFile = fileList[0]; 
 | 
    uploadRefs.value[index]?.handleRemove(earliestFile); 
 | 
  } 
 | 
  currentFiles.value[index] = file; 
 | 
}; 
 | 
  
 | 
// 文件上传前校验 
 | 
const beforeUpload = (rawFile, index) => { 
 | 
  currentFiles.value[index] = {}; 
 | 
  if (rawfile.size > 1024 * 1024 * 10 * 10) { 
 | 
    ElMessage.error("上传文件不超过10M"); 
 | 
    return false; 
 | 
  } 
 | 
  return true; 
 | 
}; 
 | 
  
 | 
// 上传成功 
 | 
const handleSuccessUp = (response, file, fileList, index) => { 
 | 
  if (response.code == 200) { 
 | 
    if (uploadRefs[index]) { 
 | 
      uploadRefs[index].clearFiles(); 
 | 
    } 
 | 
    currentFiles[index] = file; 
 | 
    ElMessage.success("上传成功"); 
 | 
    resetUploadComponent(index); 
 | 
  } else { 
 | 
    ElMessage.error(response.message); 
 | 
  } 
 | 
}; 
 | 
  
 | 
const resetUploadComponent = (index) => { 
 | 
  uploadKeys[index] = Date.now(); 
 | 
}; 
 | 
  
 | 
// 上传失败 
 | 
const onError = (error, file, fileList, index) => { 
 | 
  ElMessage.error("文件上传失败,请重试"); 
 | 
  if (uploadRefs.value[index]) { 
 | 
    uploadRefs.value[index].clearFiles(); 
 | 
  } 
 | 
}; 
 | 
  
 | 
// 文件数量超限提示 
 | 
const onExceed = () => { 
 | 
  ElMessage.warning("超出文件个数"); 
 | 
}; 
 | 
  
 | 
const paginationSearch = ({ page, limit }) => { 
 | 
  emit("pagination", { page: page, limit: limit }); 
 | 
}; 
 | 
  
 | 
const expandChange = (row, expandedRows) => { 
 | 
  emit("expand-change", row, expandedRows); 
 | 
}; 
 | 
  
 | 
const handleSelectionChange = (newSelection) => { 
 | 
  emit("selection-change", newSelection); 
 | 
}; 
 | 
</script> 
 | 
  
 | 
<style scoped lang="scss"> 
 | 
.cell { 
 | 
  white-space: nowrap; 
 | 
  overflow: hidden; 
 | 
  text-overflow: ellipsis; 
 | 
  padding-right: 0 !important; 
 | 
  padding-left: 0 !important; 
 | 
} 
 | 
</style> 
 |