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>


目录
相关文章
|
2天前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1519 4
|
29天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
5天前
|
人工智能 Rust Java
10月更文挑战赛火热启动,坚持热爱坚持创作!
开发者社区10月更文挑战,寻找热爱技术内容创作的你,欢迎来创作!
503 19
|
2天前
|
存储 SQL 关系型数据库
彻底搞懂InnoDB的MVCC多版本并发控制
本文详细介绍了InnoDB存储引擎中的两种并发控制方法:MVCC(多版本并发控制)和LBCC(基于锁的并发控制)。MVCC通过记录版本信息和使用快照读取机制,实现了高并发下的读写操作,而LBCC则通过加锁机制控制并发访问。文章深入探讨了MVCC的工作原理,包括插入、删除、修改流程及查询过程中的快照读取机制。通过多个案例演示了不同隔离级别下MVCC的具体表现,并解释了事务ID的分配和管理方式。最后,对比了四种隔离级别的性能特点,帮助读者理解如何根据具体需求选择合适的隔离级别以优化数据库性能。
179 1
|
8天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
21天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
9天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
457 5
|
7天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
314 2
|
23天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
25天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2608 22
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析