zhangwencui
15 小时以前 b7ddb64be82d402dca628e92fa432558fd712289
src/components/Dialog/FileListDialog.vue
@@ -1,164 +1,328 @@
<template>
  <el-dialog
    v-model="dialogVisible"
    :title="title"
    :width="width"
    :before-close="handleClose"
  >
    <el-table :data="tableData" border :height="tableHeight">
      <el-table-column
        :label="nameColumnLabel"
        :prop="nameColumnProp"
        :min-width="nameColumnMinWidth"
        show-overflow-tooltip
      />
      <el-table-column
        v-if="showActions"
        fixed="right"
        label="操作"
        :width="actionColumnWidth"
        align="center"
      >
  <el-dialog v-model="dialogVisible"
             :title="title"
             :width="width"
             :before-close="handleClose">
    <div class="file-list-toolbar"
         v-if="showToolbar">
      <template v-if="useBuiltInUpload">
        <el-upload v-model:file-list="uploadFileList"
                   class="upload-demo"
                   :action="uploadAction"
                   :headers="uploadHeaders"
                   :show-file-list="false"
                   :on-success="handleDefaultUploadSuccess"
                   :on-error="handleDefaultUploadError">
          <el-button v-if="showUploadButton"
                     type="primary"
                     size="small">
            上传附件
          </el-button>
        </el-upload>
      </template>
      <template v-else>
        <el-button v-if="showUploadButton"
                   type="primary"
                   size="small"
                   @click="handleUpload">
          新增附件
        </el-button>
      </template>
    </div>
    <el-table :data="tableData"
              border
              :height="tableHeight">
      <el-table-column :label="nameColumnLabel"
                       :prop="nameColumnProp"
                       :min-width="nameColumnMinWidth"
                       show-overflow-tooltip />
      <el-table-column v-if="showActions"
                       fixed="right"
                       label="操作"
                       :width="actionColumnWidth"
                       align="center">
        <template #default="scope">
          <el-button
            v-if="showDownload"
            link
            type="primary"
            size="small"
            @click="handleDownload(scope.row)"
          >
          <el-button v-if="showDownload"
                     link
                     type="primary"
                     size="small"
                     @click="handleDownload(scope.row)">
            下载
          </el-button>
          <el-button
            v-if="showPreview"
            link
            type="primary"
            size="small"
            @click="handlePreview(scope.row)"
          >
          <el-button v-if="showPreview"
                     link
                     type="primary"
                     size="small"
                     @click="handlePreview(scope.row)">
            预览
          </el-button>
          <slot name="actions" :row="scope.row"></slot>
          <el-button v-if="showDeleteButton"
                     link
                     type="danger"
                     size="small"
                     @click="handleDelete(scope.row, scope.$index)">
            删除
          </el-button>
          <slot name="actions"
                :row="scope.row"></slot>
        </template>
      </el-table-column>
      <slot name="columns"></slot>
    </el-table>
    <pagination v-if="isShowPagination"
                style="margin-bottom: 20px;"
                :total="page.total"
                :page="page.current"
                :limit="page.size"
                @pagination="paginationSearch"
                @change="handleChange" />
  </el-dialog>
  <filePreview v-if="showPreview" ref="filePreviewRef" />
  <filePreview v-if="showPreview"
               ref="filePreviewRef" />
</template>
<script setup>
import { ref, computed, getCurrentInstance } from 'vue'
import filePreview from '@/components/filePreview/index.vue'
  import { ref, computed, getCurrentInstance } from "vue";
  import pagination from "@/components/Pagination/index.vue";
  import { ElMessage } from "element-plus";
  import filePreview from "@/components/filePreview/index.vue";
  import { getToken } from "@/utils/auth";
const props = defineProps({
  modelValue: {
    type: Boolean,
    default: false
  },
  title: {
    type: String,
    default: '附件'
  },
  width: {
    type: String,
    default: '40%'
  },
  tableHeight: {
    type: String,
    default: '40vh'
  },
  nameColumnLabel: {
    type: String,
    default: '附件名称'
  },
  nameColumnProp: {
    type: String,
    default: 'name'
  },
  nameColumnMinWidth: {
    type: [String, Number],
    default: 400
  },
  actionColumnWidth: {
    type: [String, Number],
    default: 100
  },
  showActions: {
    type: Boolean,
    default: true
  },
  showDownload: {
    type: Boolean,
    default: true
  },
  showPreview: {
    type: Boolean,
    default: true
  },
  urlField: {
    type: String,
    default: 'url'
  },
  downloadMethod: {
    type: Function,
    default: null
  },
  previewMethod: {
    type: Function,
    default: null
  }
})
  const props = defineProps({
    modelValue: {
      type: Boolean,
      default: false,
    },
    title: {
      type: String,
      default: "附件",
    },
    width: {
      type: String,
      default: "40%",
    },
    tableHeight: {
      type: String,
      default: "40vh",
    },
    nameColumnLabel: {
      type: String,
      default: "附件名称",
    },
    nameColumnProp: {
      type: String,
      default: "name",
    },
    nameColumnMinWidth: {
      type: [String, Number],
      default: 400,
    },
    actionColumnWidth: {
      type: [String, Number],
      default: 160,
    },
    showActions: {
      type: Boolean,
      default: true,
    },
    showDownload: {
      type: Boolean,
      default: true,
    },
    showPreview: {
      type: Boolean,
      default: true,
    },
    showUploadButton: {
      type: Boolean,
      default: false,
    },
    showDeleteButton: {
      type: Boolean,
      default: false,
    },
    urlField: {
      type: String,
      default: "url",
    },
    downloadMethod: {
      type: Function,
      default: null,
    },
    previewMethod: {
      type: Function,
      default: null,
    },
    uploadMethod: {
      type: Function,
      default: null,
    },
    deleteMethod: {
      type: Function,
      default: null,
    },
    rulesRegulationsManagementId: {
      type: [String, Number],
      default: "",
    },
    uploadUrl: {
      type: String,
      default: `${import.meta.env.VITE_APP_BASE_API}/file/upload`,
    },
    isShowPagination: {
      type: Boolean,
      default: false,
    },
    page: {
      type: Object,
      default: () => ({
        current: 1,
        size: 10,
        total: 0,
      }),
    },
  });
const emit = defineEmits(['update:modelValue', 'close', 'download', 'preview'])
  const emit = defineEmits([
    "update:modelValue",
    "close",
    "download",
    "preview",
    "upload",
    "delete",
  ]);
const { proxy } = getCurrentInstance()
const filePreviewRef = ref(null)
  const { proxy } = getCurrentInstance();
  const filePreviewRef = ref(null);
  const uploadFileList = ref([]);
const dialogVisible = computed({
  get: () => props.modelValue,
  set: (val) => emit('update:modelValue', val)
})
  const dialogVisible = computed({
    get: () => props.modelValue,
    set: val => emit("update:modelValue", val),
  });
const tableData = ref([])
  const tableData = ref([]);
  const showToolbar = computed(() => props.showUploadButton);
  const useBuiltInUpload = computed(() => !props.uploadMethod);
  const uploadAction = computed(() => props.uploadUrl);
  const uploadHeaders = computed(() => ({
    Authorization: `Bearer ${getToken()}`,
  }));
const handleClose = () => {
  emit('close')
  dialogVisible.value = false
}
  const handleClose = () => {
    emit("close");
    dialogVisible.value = false;
  };
const handleDownload = (row) => {
  if (props.downloadMethod) {
    props.downloadMethod(row)
  } else {
    // 默认下载方法
    proxy.$download.name(row[props.urlField])
  }
  emit('download', row)
}
const handlePreview = (row) => {
  if (props.previewMethod) {
    props.previewMethod(row)
  } else {
    // 默认预览方法
    if (filePreviewRef.value) {
      filePreviewRef.value.open(row[props.urlField])
  const handleDownload = row => {
    if (props.downloadMethod) {
      props.downloadMethod(row);
    } else {
      // 默认下载方法
      proxy.$download.name(row[props.urlField]);
    }
  }
  emit('preview', row)
}
    emit("download", row);
  };
const open = (list) => {
  dialogVisible.value = true
  tableData.value = list || []
}
  const handlePreview = row => {
    if (props.previewMethod) {
      props.previewMethod(row);
    } else {
      // 默认预览方法
      if (filePreviewRef.value) {
        filePreviewRef.value.open(row[props.urlField]);
      }
    }
    emit("preview", row);
  };
  const paginationSearch = page => {
    props.page.current = page.page;
    props.page.size = page.limit;
    emit("pagination", page.page, page.limit);
  };
defineExpose({
  open
})
  const open = list => {
    dialogVisible.value = true;
    tableData.value = list || [];
  };
  const handleUpload = async () => {
    if (props.uploadMethod) {
      // 如果提供了自定义上传方法,由父组件负责更新列表(通过 setList)
      // 这里不再自动添加,避免与父组件的 setList 重复
      await props.uploadMethod();
    }
    emit("upload");
  };
  const handleDelete = async (row, index) => {
    if (props.deleteMethod) {
      const result = await props.deleteMethod(row, index);
      if (result === false) {
        return;
      }
      // 如果提供了 deleteMethod,由父组件负责刷新列表,不在这里删除
    } else {
      // 如果没有提供 deleteMethod,才在组件内部删除
      removeAttachment(index);
    }
    emit("delete", row);
  };
  const addAttachment = item => {
    tableData.value = [...tableData.value, item];
  };
  const handleDefaultUploadSuccess = async (res, file) => {
    if (res?.code !== 200) {
      ElMessage.error(res?.msg || "文件上传失败");
      return;
    }
    if (!props.rulesRegulationsManagementId) {
      ElMessage.error("缺少规章制度ID,无法保存附件");
      return;
    }
    const fileName = res?.data?.originalName || file?.name;
    const fileUrl = res?.data?.tempPath || res?.data?.url;
    const payload = {
      fileName,
      fileUrl,
      rulesRegulationsManagementId: props.rulesRegulationsManagementId,
      raw: res?.data || {},
    };
    emit("upload", payload);
  };
  const handleDefaultUploadError = () => {
    ElMessage.error("文件上传失败");
  };
  const removeAttachment = index => {
    if (index > -1 && index < tableData.value.length) {
      const newList = [...tableData.value];
      newList.splice(index, 1);
      tableData.value = newList;
    }
  };
  const setList = list => {
    tableData.value = list || [];
  };
  defineExpose({
    open,
    addAttachment,
    removeAttachment,
    setList,
    handleUpload,
    handleDelete,
  });
</script>
<style scoped>
  .file-list-toolbar {
    margin-bottom: 8px;
    text-align: right;
  }
</style>