Element Plus图片上传组件二次扩展

简介: Element Plus图片上传组件二次扩展

Element Plus 的图片上传组件主要通过 <el-upload> 实现,该组件支持多种配置和功能,如文件类型限制、文件大小限制、自动上传、手动上传、预览、删除等。以下是对 Element Plus 图片上传组件的详细介绍和使用示例:

功能概述

  • 文件类型限制:可以指定允许上传的文件类型,如图片(jpg, png等)。
  • 文件大小限制:可以限制上传文件的大小。
  • 自动上传:选择文件后自动上传到服务器。
  • 手动上传:选择文件后不立即上传,而是通过按钮手动触发上传。
  • 预览:上传前或上传后可以预览图片。
  • 删除:已上传的文件可以删除。

使用示例

以下是一个基本的 Element Plus 图片上传组件的使用示例,包括前端 Vue 组件代码和后端 Spring Boot 接口代码。

前端 Vue 组件基本调用

<template>  
  <el-upload  
    class="upload-demo"  
    action="http://localhost:8080/upload/image" <!-- 后端接收上传文件的接口 -->  
    :on-preview="handlePreview"  
    :on-remove="handleRemove"  
    :file-list="fileList"  
    :auto-upload="false" <!-- 设置为false,则不自动上传 -->  
    :http-request="customUpload"  
    list-type="picture-card"  
  >  
    <i class="el-icon-plus"></i>  
  </el-upload>  
  <el-button slot="trigger" size="small" type="primary">选取文件</el-button>  
  <el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload">上传到服务器</el-button>  
  <el-dialog  
    title="预览"  
    :visible.sync="dialogVisible"  
    width="30%"  
  >  
    <img :src="dialogImageUrl" class="dialog-image" />  
  </el-dialog>  
</template>  
  
<script>  
import { ref } from 'vue';  
  
export default {  
  setup() {  
    const fileList = ref([]);  
    const dialogImageUrl = ref('');  
    const dialogVisible = ref(false);  
  
    const handlePreview = (file) => {  
      dialogImageUrl.value = file.url;  
      dialogVisible.value = true;  
    };  
  
    const handleRemove = (file, fileList) => {  
      console.log(file, fileList);  
    };  
  
    const customUpload = (options) => {  
      // 这里可以自定义上传逻辑,比如使用axios发送请求  
      // 这里只是简单模拟  
      console.log('自定义上传', options.file);  
      // 假设上传成功,更新fileList  
      // fileList.value.push({ name: options.file.name, url: '上传后的文件URL' });  
    };  
  
    const submitUpload = () => {  
      // 这里可以调用自定义的上传函数,或者将fileList中的文件逐个上传  
      // 这里只是示例,没有实现具体的上传逻辑  
      console.log('提交上传', fileList.value);  
    };  
  
    return {  
      fileList,  
      dialogImageUrl,  
      dialogVisible,  
      handlePreview,  
      handleRemove,  
      customUpload,  
      submitUpload,  
    };  
  },  
};  
</script>  
  
<style>  
.dialog-image {  
  width: 100%;  
}  
</style>

DIY可视化二次扩展

输入上传式图片组件

<template>
  <el-input
    type="text"
    ref="formImg"
    :class="url && type == 'image' ? 'diygw-img-input' : ''"
    v-model="url"
    clearable
    :placeholder="'请选择' + title"
  >
    <template #append>
      <el-image
        :src="url"
        v-if="url && type == 'image'"
        style="width: 30px; height: 30px"
        :preview-teleported="true"
        :preview-src-list="[url]"
      ></el-image>
      <el-button @click="handleStorage"> 选择{{ title }} </el-button>
    </template>
  </el-input>
  <diy-storage ref="storage" :type="type" :accept="accept" :limit="1" @confirm="getAttachmentFileList"></diy-storage>
</template>
 
<script lang="ts" setup>
import { nextTick, ref, defineEmits, watch } from 'vue';
import { useVModel } from '@vueuse/core';
import DiyStorage from './storage.vue';
 
const props = defineProps({
  // 外部v-model值
  modelValue: {
    type: String,
    default: '',
  },
  title: {
    type: String,
    default: '',
  },
  type: {
    type: String,
    default: 'image',
  },
  accept: {
    type: String,
    default: 'image/*',
  },
});
 
const storage = ref(null);
 
const emit = defineEmits(['update:modelValue', 'change', 'blur']);
 
const url = useVModel(props, 'modelValue', emit);
 
const handleStorage = () => {
  nextTick(() => {
    storage.value!.handleStorageDlg('', '上传' + props.title);
  });
};
watch(url, () => {
  emit('change');
  emit('blur');
});
const formImg: EmptyObjectType = ref(null);
// 获取商品相册资源
const getAttachmentFileList = (files = <any>[]) => {
  if (!files.length) {
    return;
  }
  url.value = files[0].url;
  nextTick(() => {
    formImg.value?.input.focus();
    formImg.value?.input.blur();
  });
};
</script>
 
<style lang="scss" scoped>
.diygw-img-input ::v-deep(.el-input-group__append) {
  padding-left: 0px;
}
.diygw-img-input {
  ::v-deep(.el-image) {
    margin-right: 15px;
  }
}
.sortable-ghost {
  opacity: 0;
}
</style>


多图式上传

<template>
  <div style="line-height: 0; width: 100%" class="flex justify-start">
    <VueDraggable
      tag="ul"
      v-bind="{ animation: 200, disabled: false, ghostClass: 'ghost' }"
      v-model="imageList"
      class="el-upload-list el-upload-list--picture-card"
      group="subform"
      item-key="url"
    >
      <li v-for="(item, index) in imageList" class="el-upload-list__item" :style="`width:${width}px; height:${height}px;`">
        <el-image :style="`width:100%; height:100%`" :src="item" fit="contain" />
        <span v-show="!state.isdrag" :class="{ 'el-upload-list__item-actions': true, 'diy-cm': isMove }">
          <SvgIcon style="color: #fff" name="ele-Plus" class="margin-right" @click="handleStorage(index)" :size="20" />
          <SvgIcon style="color: #fff" name="ele-Delete" @click="remove(index)" :size="20" />
        </span>
      </li>
    </VueDraggable>
 
    <div
      v-if="limit == 0 || (imageList.length == 0 && limit == 1)"
      tabindex="0"
      style="margin-bottom: 8px"
      class="el-upload el-upload--picture-card"
      :style="`width:${width}px; height:${height}px;`"
      @click="handleStorage(-1)"
    >
      <SvgIcon name="ele-Plus" :size="20" />
    </div>
    <diy-storage ref="storage" :limit="limit" @confirm="getAttachmentFileList"></diy-storage>
  </div>
</template>
 
<script setup>
import { nextTick, ref, reactive } from 'vue';
import { useVModel } from '@vueuse/core';
import { VueDraggable } from 'vue-draggable-plus';
 
import { ElMessageBox, useFormItem } from 'element-plus';
 
import DiyStorage from './storage.vue';
const { formItem } = useFormItem();
 
const props = defineProps({
  // 外部v-model值
  modelValue: {
    type: Array,
    default: () => [],
  },
  width: {
    default: 80,
  },
  height: {
    default: 80,
  },
  isMove: {
    default: true,
  },
  limit: {
    default: 0,
  },
  customer: {
    type: Object,
    required: false,
    default: () => {},
  },
  contractId: {
    required: false,
    default: '',
  },
  storageType: {
    type: String,
    required: false,
    default: '',
  },
});
 
const state = reactive({
  isdrag: false,
  index: -1,
});
const storage = ref();
 
const emit = defineEmits(['update:modelValue']);
 
const imageList = useVModel(props, 'modelValue', emit);
 
const handleStorage = (index = -1) => {
  state.index = index;
  nextTick(() => {
    storage.value.handleStorageDlg('', '上传图片');
  });
};
// 获取商品相册资源
const getAttachmentFileList = (files = []) => {
  if (!files.length) {
    return;
  }
  if (state.index != -1) {
    imageList.value[state.index] = files[state.index].url;
  } else {
    files.forEach((item) => {
      imageList.value.push(item.url);
    });
  }
  nextTick(() => {
    formItem?.validate?.('blur').catch((err) => {
      console.log(err);
    });
    formItem?.validate?.('change').catch((err) => {
      console.log(err);
    });
  });
};
 
const remove = (index = -1) => {
  ElMessageBox({
    message: '是否删除该文件?',
    title: '警告',
    showCancelButton: true,
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(function () {
    imageList.value.splice(index, 1);
  });
};
</script>
 
<style lang="scss" scoped>
.sortable-ghost {
  opacity: 0;
}
</style>

上传组件调用

<template>
  <el-dialog :title="title" top="5vh" v-model="visible" append-to-body center width="800px">
    <div class="flex storages">
      <div class="categorys diy-tree file-border flex flex-direction-column">
        <el-button type="primary" @click="handleAdd()">新增目录</el-button>
        <div class="mt-1 text-center" plain :class="[!form.parentId ? 'selected' : '']" @click="selectCategory('')">全部</div>
        <div
          class="mt-1 p-1 text-center"
          plain
          :key="item"
          :class="[form.parentId == item.storageId ? 'selected' : '']"
          @click="selectCategory(item.storageId)"
          v-for="item in categorys"
        >
          {{ item.name }}
        </div>
      </div>
      <div class="flex1 file-border">
        <!-- 文件列表开始 -->
        <div class="flex justify-between">
          <div class="diy-mb-20">
            <el-input
              v-model="form.name"
              label-width="0px"
              placeholder="输入名称搜索"
              style="width: 250px"
              @keyup.enter.native="handleSearch()"
              @clear="handleSearch()"
              :clearable="true"
            >
              <template #suffix>
                <SvgIcon @click="handleSearch" name="ele-Search" :size="20" />
              </template>
            </el-input>
          </div>
 
          <diy-upload
            ref="upload"
            :upload-tip="uploadConfig.uploadTip"
            :multiple="uploadConfig.multiple"
            :type="type"
            :accept="accept"
            :limit="uploadConfig.limit"
            :parent-id="form.parentId"
            @confirm="_getUploadFileList"
          ></diy-upload>
        </div>
        <div class="files" :max="limit !== 0 ? limit : 100">
          <div class="file-list" v-loading="loading">
            <div :key="index" v-for="(item, index) in currentTableData" class="item" :data-label="item.name" :class="item.selectclz">
              <div
                class="file-image"
                :style="{
                  backgroundImage: 'url(' + getImageThumb(item) + ')',
                }"
                @click="selectFile(item)"
              ></div>
              <div class="file-name">
                {{ item.name }}
              </div>
              <div class="mask" @click="selectFile(item)">
                <SvgIcon name="ele-Check" :size="20" />
              </div>
              <div class="el-dropdown" @click="handleDelete(item.storageId)">
                <SvgIcon class="delete" name="ele-Delete" :size="20" />
              </div>
            </div>
          </div>
        </div>
 
        <!-- 翻页开始 -->
        <diy-pagefooter
          style="margin: 0; padding: 20px 0 0 0"
          :current="page.current"
          :size="page.size"
          :total="page.total"
          :is-size="false"
          @change="handlePaginationChange"
        />
      </div>
    </div>
 
    <!-- 确认,取消 -->
    <template #footer v-if="isSelect == '1'" class="dialog-footer">
      <div style="float: left; font-size: 13px">
        <span v-if="checkList.length > limit && limit !== 0" style="color: #f56c6c">
          当前已选 {{ checkList.length }} 个,最多允许选择 {{ limit }} 个文件
        </span>
        <span v-else>当前已选 {{ checkList.length }} 个文件</span>
      </div>
      <el-button @click="visible = false">取消</el-button>
      <el-button type="primary" :loading="loadingCollection" :disabled="checkList.length > limit && limit !== 0" @click="handleConfirm"
        >确定</el-button
      >
    </template>
  </el-dialog>
 
  <el-dialog :title="nameMap[nameStatus]" v-model="nameFormVisible" append-to-body center top="5vh" width="600px">
    <el-form :model="nameForm" :rules="rules" ref="refform" label-width="50px" label-position="left" @submit.native.prevent>
      <el-form-item label="名称" prop="name">
        <el-input v-model="nameForm.name" placeholder="请输入目录名称" @keyup.enter.native="nameStatus === 'add' ? add() : rename()" ref="input" />
      </el-form-item>
    </el-form>
 
    <template #footer>
      <el-button @click="nameFormVisible = false">取消</el-button>
 
      <el-button v-if="nameStatus === 'add'" type="primary" :loading="dialogLoading" @click="add">确定</el-button>
 
      <el-button v-else type="primary" :loading="dialogLoading" @click="rename">修改</el-button>
    </template>
  </el-dialog>
</template>
 
<script lang="ts">
import { defineComponent, reactive, ref, nextTick, toRefs } from 'vue';
import { listData, listAllData, addData, delData } from '@/api/index';
import { ElMessageBox, ElMessage } from 'element-plus';
import DiyUpload from './upload.vue';
import DiyPagefooter from './pagefooter.vue';
 
export default defineComponent({
  name: 'DiyStorage',
  components: {
    DiyUpload,
    DiyPagefooter,
  },
  props: {
    type: {
      default: 'image',
    },
    accept: {
      default: 'image/*',
    },
    // 最大选择数(0表示不限制)
    limit: {
      type: Number,
      required: false,
      default: 0,
    },
    isSelect: {
      default: '1',
    },
  },
  setup(props, { emit }) {
    const data = reactive({
      baseUrl: '',
      title: '',
      isLoad: false,
      icontype: 'default',
      visible: false,
      loading: false,
      uploadConfig: {
        uploadTip: '请选择文件进行上传,',
        multiple: true,
        accept: 'image/*',
        limit: 0,
        type: 'image',
        replace: false,
      },
      storageType: props.type,
      source: '',
      loadingCollection: false,
      checkList: [],
      categorys: [],
      syscategorys: [],
      currentTableData: [],
      currentSysTableData: [],
      searchSysTableData: [],
      dialogLoading: false,
      nameForm: {
        type: '',
        name: undefined,
        parentId: undefined,
      },
      nameFormVisible: false,
      nameStatus: 'edit',
      nameMap: {
        edit: '重命名',
        add: '新增目录',
      },
      iconfonturl: '',
      rules: {
        name: [
          {
            required: true,
            message: '目录名称不能为空',
            trigger: 'blur',
          },
          {
            max: 255,
            message: '长度不能大于 255 个字符',
            trigger: 'blur',
          },
        ],
      },
      form: {
        name: undefined,
        parentId: undefined,
        order_type: 'desc',
        order_field: 'storageId',
      },
      sysform: {
        parentId: 'icon1',
        name: '',
        type: 'system',
      },
      page: {
        current: 1,
        size: 15,
        total: 0,
      },
    });
 
    const upload = ref(null);
 
    const getImageThumb = (item: any) => {
      if (item.type == 'mp3') {
        return '/static/images/mp3.png';
      } else if (item.type == 'mp4') {
        return '/static/images/mp3.png';
      } else if (item.type == 'image' || item.type == 'scene') {
        return item.url;
      } else {
        return '/static/images/file.png';
      }
    };
 
    const handleStorageDlg = (source = '', title = '') => {
      data.title = title ? title : '文件管理';
      // if (type == 'mp3') {
      //  data.uploadConfig.accept = '.mp3';
      // } else if (type == 'mp4') {
      //  data.uploadConfig.accept = '.mp4';
      // } else {
      //  data.uploadConfig.accept = 'image/*';
      // }
      data.visible = true;
      data.storageType = props.type;
      data.source = source;
      data.checkList = [];
      data.currentTableData.forEach((item: any) => {
        item.selectclz = '';
      });
 
      if (!data.isLoad) {
        data.loadingCollection = false;
        data.currentTableData = [];
        data.uploadConfig.type = props.type;
        handleSubmit();
        nextTick(() => {
          getStorageDirectory();
          if (upload.value) {
            upload.value?.handleClose();
          }
        });
      }
    };
    const handlePaginationChange = (val: any) => {
      data.page = val;
      nextTick(() => {
        handleSubmit();
      });
    };
    const handleSearch = () => {
      data.page.current = 1;
      handleSubmit();
    };
 
    const handleSubmit = () => {
      data.loading = true;
      listData('/sys/storage', {
        ...data.form,
        type: data.storageType,
        pageNum: data.page.current,
        pageSize: data.page.size,
      })
        .then((res) => {
          data.currentTableData = res.rows || [];
          data.page.total = res.total;
          data.isLoad = true;
        })
        .finally(() => {
          data.loading = false;
        });
    };
 
    const handleSysSubmit = () => {
      data.loading = true;
      listData('/sys/storage', {
        ...data.sysform,
      })
        .then((res) => {
          data.currentSysTableData = res.rows || [];
          data.searchSysTableData = res.rows || [];
        })
        .finally(() => {
          data.loading = false;
        });
    };
 
    const handleSyscatesSubmit = () => {
      data.loading = true;
      listData('/sys/storage', {
        ...data.sysform,
      })
        .then((res) => {
          data.syscategorys = res.rows || [];
          data.sysform.type = 'system';
          data.sysform.parentId = res.rows[0].storageId;
          handleSysSubmit();
        })
        .finally(() => {
          data.loading = false;
        });
    };
 
    const handleSysSearch = () => {
      let findData = data.currentSysTableData.filter((item: any) => {
        return item.name.indexOf(data.sysform.name) >= 0 || item.cname.indexOf(data.sysform.name) >= 0;
      });
      data.searchSysTableData = findData;
    };
 
    const handleAllSearch = () => {
      if (data.loading) {
        ElMessage.error('点击过快,正在加载数据');
        return;
      }
      data.sysform.parentId = '';
      handleSysSubmit();
    };
 
    const handleConfirm = () => {
      if (data.checkList.length <= 0) {
        emit('confirm', [], data.source);
        data.visible = false;
        return;
      }
      let checkList = data.checkList;
      // let finddata = data.currentTableData.filter((item) => {
      //   return checkList.includes(item.storageId);
      // });
      // let finddata2 = data.currentSysTableData.filter((item) => {
      //   return checkList.includes(item.storageId);
      // });
      // finddata = finddata.concat(finddata2)
      data.loadingCollection = false;
      //data.checkList = [];
      data.visible = false;
      emit('confirm', checkList || [], data.source);
    };
    // 上传文件
    // const handleUpload = () => {
    //  upload.value.handleUploadDlg();
    // };
 
    const selectCategory = (id: any) => {
      if (data.loading) {
        ElMessage.error('点击过快,正在加载数据');
        return;
      }
      data.form.parentId = id;
      handleSubmit();
    };
 
    const selectSystemCategory = (item: any) => {
      if (data.loading) {
        ElMessage.error('点击过快,正在加载数据');
        return;
      }
      data.sysform.name = '';
      data.iconfonturl = item.url;
      data.sysform.parentId = item.storageId;
      handleSysSubmit();
    };
 
    const selectFile = (item: any) => {
      if (props.limit === 1) {
        data.currentSysTableData.forEach((item: any) => {
          item.selectclz = '';
        });
        data.currentTableData.forEach((item: any) => {
          item.selectclz = '';
        });
        let index = data.checkList.findIndex((checkitem: any) => {
          return checkitem == item.storageId;
        });
        if (index >= 0) {
          data.checkList.splice(index, 1);
          item.selectclz = '';
        } else if (data.checkList.length > 0) {
          data.checkList.splice(0, 1, item);
          item.selectclz = 'active';
        } else {
          data.checkList.push(item);
          item.selectclz = 'active';
        }
      } else {
        let index = data.checkList.findIndex((checkitem: any) => {
          return checkitem.storageId == item.storageId;
        });
        if (index >= 0) {
          data.checkList.splice(index, 1);
          item.selectclz = '';
        } else {
          data.checkList.push(item);
          item.selectclz = 'active';
        }
      }
    };
 
    // 批量删除
    const handleDelete = (val: any) => {
      const storageId = val ? [val] : data.checkList;
      if (storageId.length === 0) {
        ElMessage.error('请选择要操作的数据');
        return;
      }
      ElMessageBox.confirm('确定要执行该操作吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
        closeOnClickModal: false,
      })
        .then(() => {
          delData('/sys/storage', storageId).then(() => {
            for (let i = data.currentTableData.length - 1; i >= 0; i--) {
              if (storageId.indexOf(data.currentTableData[i].storageId) !== -1) {
                data.currentTableData.splice(i, 1);
              }
            }
            ElMessage.success('操作成功');
          });
        })
        .catch(() => {});
    };
    // 文件上传成功后处理
    const _getUploadFileList = (files: any) => {
      for (const value of files) {
        let stroage = value.response.data;
        let index = data.currentTableData.findIndex((item: any) => {
          return item.url == stroage.url;
        });
        if (index == 0) {
          continue;
        }
        if (index > 0) {
          data.currentTableData.splice(index, 1);
        }
        data.currentTableData.splice(0, 0, stroage);
      }
    };
 
    const refform = ref();
    const refinput = ref();
 
    // 获取可选择目录
    const getStorageDirectory = () => {
      //if (!this.directoryList.length) {
      listAllData('/sys/storage', { type: 'category' }).then((res) => {
        data.categorys = res.rows;
      });
    };
 
    const add = () => {
      refform.value.validate((valid: boolean) => {
        if (valid) {
          data.dialogLoading = true;
          data.nameForm.type = 'category';
 
          addData('/sys/storage', { ...data.nameForm })
            .then((res) => {
              data.categorys.unshift({
                ...res.data,
                is_default: 0,
              });
              //getStorageDirectory();
              data.nameFormVisible = false;
              ElMessage.success('操作成功');
            })
            .catch(() => {
              data.dialogLoading = false;
            });
        }
      });
    };
 
    const rename = () => {
      refform.value.validate((valid: boolean) => {
        if (valid) {
          data.dialogLoading = true;
          // renameStorageItem(data.nameForm.storageId, data.nameForm.name)
          //   .then(res => {
          //     data.currentTableData[data.nameForm.index].name = data.nameForm.name
          //     // data.directoryList = []
          //     getStorageDirectory()
          //     data.nameFormVisible = false
          //     data.$message.success('操作成功')
          //   })
          //   .catch(() => {
          //     data.dialogLoading = false
          //   })
        }
      });
    };
 
    const handleAdd = () => {
      data.nameForm['name'] = undefined;
      data.nameForm['parentId'] = data.form.parentId;
 
      data.dialogLoading = false;
      data.nameStatus = 'add';
      data.nameFormVisible = true;
 
      nextTick(() => {
        refform.value.clearValidate();
      });
    };
 
    // for(let i=1 ;i<=9;i++){
    //   data.syscategorys.push({
    //     id:'icon'+i,
    //     name:'图标库'+i
    //   })
    // }
    const handleClick = () => {
      if (data.syscategorys.length == 0 && data.icontype == 'system') {
        data.sysform.type = 'systemcategorys';
        handleSyscatesSubmit();
      }
    };
    return {
      refform,
      refinput,
      selectSystemCategory,
      rename,
      add,
      handleAdd,
      handleClick,
      ...toRefs(data),
      _getUploadFileList,
      selectCategory,
      getImageThumb,
      selectFile,
      handleDelete,
      handleSearch,
      handleAllSearch,
      handleConfirm,
      handlePaginationChange,
      handleStorageDlg,
      handleSysSearch,
    };
  },
});
</script>
<style>
.storages .el-input__suffix {
  top: 5px;
  height: auto;
}
</style>
<style lang="scss" scoped>
.breadcrumb {
  border: 1px solid #dcdfe6;
  padding: 10px !important;
}
.files {
  height: 50vh;
}
.file-height {
  min-height: calc(40vw - 190px);
}
.file-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  :deep(.el-loading-mask) {
    background-color: transparent;
  }
}
.mt-1 {
  margin-top: 0.25rem;
}
.p-1 {
  padding: 0.25rem;
}
.delete {
  color: #fff;
}
.file-list .item {
  flex: none;
  position: relative;
  width: calc(20% - 20px);
  margin: 10px;
  text-align: center;
  vertical-align: middle;
 
  .file-image {
    width: 100%;
    height: 100%;
    background-color: #eee;
    border-radius: 4px;
    background-size: contain;
    background-repeat: no-repeat;
    background-position: 50% 50%;
    position: absolute;
    left: 0;
    top: 0;
  }
  &:before {
    content: '';
    display: inline-block;
    padding-bottom: 100%;
    width: 0.1px;
    vertical-align: middle;
  }
  .file-name {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    line-height: 34px;
    height: 34px;
    background: rgba(0, 0, 0, 0.5);
    color: #fff;
 
    text-align: center;
    z-index: 2;
    border-bottom-left-radius: 4px;
    border-bottom-right-radius: 4px;
    text-overflow: ellipsis;
    overflow: hidden;
    white-space: nowrap;
    box-sizing: border-box;
  }
  .mask {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 5;
    background-color: rgba(0, 0, 0, 0.5);
    text-align: center;
    display: none;
    width: 100%;
    height: 100%;
    color: #fff;
    font-size: 40px;
    background: rgba(66, 139, 202, 0.8);
  }
  .el-dropdown {
    position: absolute;
    width: 34px;
    line-height: 34px;
    text-align: center;
    background-color: #3296fa;
    cursor: pointer;
    bottom: 0;
    right: 0;
    z-index: 6;
    display: none;
  }
 
  &:hover {
    &:after {
      position: absolute;
      bottom: -20px;
      left: 0px;
      position: absolute;
      padding: 3px 5px;
      font-size: 12px;
      font-weight: 700;
      color: #fff;
      white-space: nowrap;
      background-color: #006eff;
      border-radius: 3px;
      content: attr(data-label);
    }
    .el-dropdown {
      display: block;
    }
  }
  &.active {
    .mask {
      display: flex;
      align-items: center;
      justify-content: center;
    }
  }
}
 
.brother-showing i {
  width: 16px;
}
.diy-mb-5 {
  margin-bottom: 5px !important;
}
.diy-tree {
  width: 200px;
}
.file-border {
  border: 1px solid #dcdfe6;
  padding: 10px;
  :deep(.el-form-item__content) {
    margin-left: 0 !important;
  }
  .selected {
    border: 1px solid var(--el-color-primary);
    border-radius: 3px;
    color: var(--el-color-primary);
  }
}
 
.tree-scroll {
  overflow: auto;
  padding-bottom: 1px;
}
.custom-tree-node {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 14px;
  padding-right: 8px;
}
 
.diy-table {
  display: table-row;
}
.diy-cell {
  display: table-cell;
}
</style>


目录
相关文章
|
存储 JSON 数据库
vue3中实现文件上传---通过element-plus的upload组件
vue3中实现文件上传---通过element-plus的upload组件
|
JavaScript
vue 实现表格循环滚动 vue-seamless-scroll插件的安装与使用
vue 实现表格循环滚动 vue-seamless-scroll插件的安装与使用
1362 0
|
存储 前端开发 Java
Element el-upload 文件上传/图片上传/拖拽上传/附带参数/附带请求头部详解
文目录 1. 前言 2. 基本用法 2.1 前端部分 2.2 后端部分 2.3 获取后端返回信息 3. 外观功能介绍 3.1 拖拽上传 3.2 显示图片 3.3 设置文件列表样式 3.4 显示提示信息 4. 事件功能介绍 4.1 限制上传文件数量 4.2 限制上传文件类型和大小 4.3 移除文件处理 4.4 手动上传 5. 附带参数 6. 附带请求头部 7. 小结
8069 0
|
JavaScript 前端开发
Element_文件上传&&多个文件上传
Element_文件上传&&多个文件上传
1201 0
|
Web App开发 JavaScript 数据可视化
vue3扩展echart封装为组件库-快速复用
vue3扩展echart封装为组件库-快速复用
885 7
element plus 的图片上传组件回显
element plus 的图片上传组件回显
517 0
|
前端开发 数据格式
vue+element-plus上传图片功能以及回显问题还有数量限制
vue+element-plus上传图片功能以及回显问题还有数量限制
1010 0
element-ui 上传图片回显
element-ui 上传图片回显
568 0
|
网络协议 应用服务中间件 网络安全
宝塔用IP建站“无法访问此网站”解决办法
宝塔用IP建站“无法访问此网站”解决办法

热门文章

最新文章