Element-ui 常用组件样式修改

3,390 阅读16分钟

引入组件

<div class="element-style">
    <!-- button透明按钮 -->
    <el-button plain class="style-button">透明按钮</el-button>

    <!-- Divider 分割线 -->
    <el-divider class="style-divider"> 以上属性不可调整 </el-divider>

    <!-- progress进度条 -->
    <el-progress class="style-progress" color="#0FF8F8" :format="() => ''" :percentage="50" />

    <!-- input输入框 -->
    <el-input v-model="value" class="style-input" placeholder="请输入"></el-input>

    <!-- textarea输入框 -->
    <el-input v-model="textareaValue" class="style-textarea" :rows="2" type="textarea" placeholder="请输入" />

    <!-- select下拉框 -->
    <el-select v-model="selectValue" popper-class="popper-default" class="style-select" placeholder="请选择" size="default"
      @change="selectChange">
      <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
    </el-select>
    
    <!-- dropdown下拉菜单 -->
    <el-dropdown class="style-dropdown"  popper-class="popper-default">
       <span class="el-dropdown-link">
            导入
          <el-icon class="el-icon--right">
             <arrow-down />
          </el-icon>
       </span>
       <template #dropdown>
           <el-dropdown-menu>
               <el-dropdown-item>导入站房</el-dropdown-item>
               <el-dropdown-item>导入杆塔</el-dropdown-item>
           </el-dropdown-menu>
       </template>
    </el-dropdown>

    <!-- radio单选框组 -->
    <el-radio-group class="style-radios" v-model="adioValue">
      <el-radio v-for="(item, i) in radios" :key="item" :label="i">{{ item }}</el-radio>
    </el-radio-group>

    <!-- switch开关 -->
    <el-switch class="style-switch" v-model="switcValue" />

    <!-- Input Number 数字输入框 -->
    <el-input-number class="style-inputNumber" v-model="inputNumber" :min="1" :max="10" />

    <!-- 单联date-picker日期选择器 -->
    <el-date-picker v-model="dateValue" class="style-input style-picker" popper-class="popper-default" value-format="YYYY-MM-DD"
      type="date" placeholder="请选择日期" size="default" />

    <!-- 双联date-picker日期选择器 -->
    <el-date-picker v-model="dateValue" class="style-input style-picker" popper-class="popper-default" value-format="MM-DD"
      type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" size="default"
      @change="dateChange" />

    <!-- Popover 气泡卡片 -->
    <el-popover popper-class="popper-default" placement="bottom" :width="140" trigger="hover">
      <template #reference>
        <div>Popover入口</div>
      </template>
      <div>卡片内容</div>
    </el-popover>

    <!-- drawer抽屉 -->
    <el-drawer v-model="visible" class="style-drawer" direction="rtl">
      <template #header>
        <div>标题</div>
      </template>
      <template #default>
        <div>内容</div>
      </template>
      <template #footer>
        <el-button color="#0ff8f8" @click="confirmClick">确定</el-button>
        <el-button plain @click="cancelClick">取消</el-button>
      </template>
    </el-drawer>

    <!-- tag标签 -->
    <!-- 事件传额外参数 -->
    <el-check-tag class="style-tag" :checked="true" @change="(status) => onChange(status, '参数')">
      <span>tag内容</span>
    </el-check-tag>
    
    <!-- card卡片 -->
    <el-card class="style-card">故障定位</el-card>
    
    <!-- dialog弹窗 -->
    <el-dialog
            v-model="orderStore.showNotifyDialog"
            :title="title"
            :width="width || 640"
            align-center
            class="dialog-default"
            @close="handleClose"
        >
            <div>333333333333333333333333333333333</div>
            <template #footer>
                <el-button plain class="style-button" @click="handleClose">取消</el-button>
                <el-button color="#0ff8f8" @click="handleSend">发送</el-button>
            </template>
     </el-dialog>
     <!-- menu菜单 -->
        <el-menu unique-opened default-active="1" class="style-menu" @open="handleOpen" @close="handleClose">
            <el-menu-item index="1">
                <span>网架概览</span>
            </el-menu-item>
            <el-sub-menu index="2">
                <template #title>
                    <span>统计维度</span>
                </template>
                <el-menu-item index="2-1">线路长度(km)</el-menu-item>
                <el-menu-item index="2-2">杆塔基数</el-menu-item>
                <el-menu-item index="2-3">接头盒数量</el-menu-item>
                <el-menu-item index="2-4">光纤利用率</el-menu-item>
            </el-sub-menu>
   </el-menu>
           <!-- checkbox 多选框 -->
                <el-checkbox class="style-checkbox" v-model="checkAll" :indeterminate="isIndeterminate" @change="handleCheckAllChange">全部</el-checkbox>
        <el-checkbox-group class="style-checkbox" v-model="checkedList" @change="handleCheckedChange">
            <el-checkbox v-for="option in checkOptions[activeTab]" :key="option" :label="option">
                {{ option }}
            </el-checkbox>
        </el-checkbox-group>
  </div>

修改内置样式

/* 字体颜色 */
$color-theme: #0ff8f8; // 青蓝色主题色
$color-theme-hover: #97ffff; // 鼠标悬浮主题色
$color-white: #ffffff; // 白色,正文标题颜色
$color-black: #333; // 黑色
$color-subtitle: #acaeb3; // 浅灰色,次强调
$color-purple: #303774; // 深紫色
$color-blue: #409eff; // 深蓝色
$color-black: #131725; // 黑色调
$color-red: #f45151; // 红色调
$color-warning: #ff7d00; // 警告色

/* 字体大小 */
$font-size-default: 14px; //默认字体大小
$font-size-12: 12px;
$font-size-16: 16px;
$font-size-18: 18px;
$font-size-20: 20px;
$font-size-24: 24px;

/* 字体粗细 */
$font-weight: 600;

/* 背景颜色 */
$bg-theme: #070b1a; // 深色主题背景
$bg-transparent: transparent; // 透明
$bg-white: #ffffff; // 白色
$bg-default: #000000; // 默认黑色
$bg-gray: #393c48; // 深灰色
$bg-purple: #352c4e; // 深紫色
$bg-li-hover: #0c4649; // 鼠标悬浮列表选背景色
$bg-switch-active: #1ec2c2; // 开关背景色
$bg-switch: #43476b; // 开关背景色
$bg-checkbox-active: #13a6ad; // 多选选中

/* 边框 */
$border-default: 1px solid #797979; // 默认边框大小及颜色
$border-theme: 1px solid #125b64; // 主题边框
$border-purple: 1px solid #323869; // 紫色
$border-theme-hover: 1px solid #0ff8f8; // 鼠标悬浮主题边框
$border-radio: 3px solid #0a868c;

.element-style {

  /* button按钮 */
  .style-button {
    border-radius: 0;

    /* 素按钮 */
    &.is-plain {
      background: $bg-transparent;
      color: $color-white;
      border: $border-default;

      &:hover {
        color: $color-theme;
        border: $border-hover;
      }
    }
  }

  /* Divider 分割线 */
  .style-divider {
    border-top: $border-default;

    /* 中间文本 */
    .el-divider__text {
      width: 120px;
      text-align: center;
      background: $bg-theme;
      color: $color-subtitle;
    }
  }

  /* 进度条 */
  .style-progress {
    width: 350px;

    /* 背景 */
    .el-progress-bar__outer {
      background: $bg-purple;
      border-radius: 0;

      /* 进度条 */
      .el-progress-bar__inner {
        border-radius: 0;
      }
    }

    /* 进度条描述文本 */
    .el-progress__text {
      min-width: 0;
    }
  }
  
    /* dropdown下拉菜单面板 */
  .style-dropdown {
    padding: 8px 0 8px;
    color: #000;
    font-size: 12px;
 }

  /* input输入框 */
  .style-input {

    /* 容器 */
    .el-input__wrapper {
      box-shadow: unset !important;
      background: unset;
      border: $border-theme;
      
      /* 选中,聚焦,悬浮 */
      &.is-active,
      &.is-focus,
      &:hover {
         box-shadow: unset !important;
         border: $border-theme-hover;
      }
      
      /* 点击后 */
      .el-input.is-focus .el-input__wrapper,
      .el-input__wrapper.is-focus {
        box-shadow: unset !important;
        border: $border-theme-hover;
      }

      /* 内容 */
      .el-input__inner,
      /* 日期选择 */
      .el-range-input,
      /* 日期图标 */
      .el-range__icon,
      /* 日期分隔 */
      .el-range-separator
      {
        color: $color-white;
      }
    }
  }

  /* textarea输入框 */
  .style-textarea {

    /* 内容 */
    .el-textarea__inner {
      box-shadow: none;
      color: $color-white;
      border: $border-theme;
      background: $bg-transparent;
      
      &:hover,
      &:focus {
          border: $border-theme-hover;
      }
    }
  }

  /* select下拉框 */
  /* 边框高亮直接给el-select 加上class="style-input" */
  /* select框加背景图 */
  .style-select-bg {
    width: 100px;
    .el-input__wrapper {
            &:hover {
                border: none;
                background: url('../images/btn_hover.svg') 0 0 / 100% 100% no-repeat !important;
            }

            border-radius: 0;
            border: none;
            background: url('../images/btn_default.svg') 0 0 / 100% 100% no-repeat !important;
        }

        .el-input.is-focus .el-input__wrapper,
        .el-input__wrapper.is-focus {
            border: none;
            box-shadow: unset !important;
            background: url('../images/btn_hover.svg') 0 0 / 100% 100% no-repeat !important;
        }
        .el-input_inner {
          color: $color-white;
        }
  }
 /* select纯文字加渲染背景色 */
 .style-select-text {
        width: 130px;
        .el-input__wrapper {
            &:hover {
                border: none;
                background: #285757;
            }

            border-radius: 0;
            border: none;
            background: unset;
        }

        .el-input.is-focus .el-input__wrapper,
        .el-input__wrapper.is-focus {
            border: none;
            box-shadow: unset !important;
            background: #285757;
        }
        .el-input_inner {
          color: $color-white;
        }
    }

  /* radio单选框组 */
  .style-radios {

    /* 单选框 */
    .el-radio {

      /* 选中 */
      &.is-checked {

        /* 字体颜色 */
        .el-radio__label {
          color: $color-theme;
        }
      }

      /* 圆圈 */
      .el-radio__inner {
        border: $border-radio;
        background: $bg-gray;
      }
    }
  }

  /* switch开关 */
  .style-switch {

    /* 关闭 */
    .el-switch__core {
      background: $bg-switch;
      border-color: $bg-switch;
    }

    /* 开启 */
    &.is-checked .el-switch__core {
      background: $bg-switch-active;
      border-color: $bg-switch-active;
    }
  }

  /* Input Number 数字输入框 */
  /* el-input 加上class="style-input" */
  .style-inputNumber {

    /* 减 */
    .el-input-number__decrease {
      color: $color-white;
      border-right: $border-theme;
      background: $bg-transparent;
    }

    /* 增 */
    .el-input-number__increase {
      color: $color-white;
      border-left: $border-theme;
      background: $bg-transparent;
    }
  }

  /* 单联date-picker日期选择器 */
  /* 双联date-picker日期选择器 */
  /* el-date-picker 加上class="style-input" */
  .style-picker {
      width: 200px;
  }


  /* drawer抽屉 */
  .style-drawer {
    width: 340px !important;
    background: $bg-theme;

    /* 头部 */
    .el-drawer__header {
      margin: 0;
      padding: 24px;
      border-bottom: $border-theme;

      /* 删除按钮 */
      .el-drawer__close-btn {
        // display: none;
      }
    }

    /* 内容 */
    .el-drawer__body {
      padding: 4px 24px;
    }

    /* 底部 */
    .el-drawer__footer {
      padding: 20px 24px;
      border-top: $border-theme;
    }
  }

  /* tag标签 */
  .style-tag {
    border-radius: 2px;
    padding: 9px 8px;
    background: $bg-theme;
    color: $color-white;
    
    &:hover {
      background: $color-theme;
    }
    /* 选中 */
    &.is-checked {
      background: $bg-li-hover;
      color: $color-white;
    }
  }

  /* card卡片 */
  .style-card {
      .el-card__body {
        padding: 10px 15px;
        cursor: pointer;
    }
  }

  /* Pagination 分页 */
  /* el-pagination 加上class="style-input" */
  .style-pagination {
          /* 总数,跳转 */
          .el-pagination__total,
          .el-pagination__jump {
            color: $color-title;
          }
          /* 页码,上下一页 */
          li,
          .btn-prev,
          .btn-next {
            color: $color-title;
            background: transparent;

            &:hover {
              color: $color-theme;
            }
          }
        
          /* 页码选中 */
          .is-active {
            background: $background-color-li-active;
            color: $color-theme;
          }
   }
   
    /* message box 弹窗 */
   .el-message-box.style-message-box {
    background: #090b1a;
    border-color: #0d959a;
    .el-message-box__message,
    .el-message-box__title,
    .el-message-box__close {
        color: #fff;
    }

    .el-button {
        border-radius: 0;
        background: #0ff8f8;
        color: #333;

        &:hover {
            background: #0dc9cc;
        }

        &:first-child {
            background: transparent;
            color: #fff;
            border: 1px solid #323869;

            &:hover {
                color: #0dc9cc;
                border: 1px solid #0dc9cc;
            }
        }
    }
}

  /* table表格 */
  .style-table {
    width: 100%;
    height: 100px;
    color: $color-white;
    background: $bg-transparent;
    /* 表格边框 */
    --el-table-border-color: transparent;

    /* 表格下边线 */
    .el-table__inner-wrapper {
      &::before {
        display: none;
      }
    }
    /* 表头 */
    .el-table__header {
         width: 100% !important;
          tr {
            background: $bg-transparent;

            th {
              border-bottom: none;
              color: $color-white;
              background: $bg-transparent;
            }
          }
    }
    /* 表格内容 */
    .el-table__body {
          width: 100% !important;
          /* 行边距 */
          -webkit-border-vertical-spacing: 2px;
          tr {
            background: transparent;

            &.click-row {

              td {
                background: #276366 !important;
              }

            }

            &.check-row {

              td {
                background: #0C4649 !important;
              }
            }

            &:hover {
              cursor: pointer;

              td {
                background: #09232D !important;
              }
            }

            td {
              border-bottom: none;
              background: transparent;
            }
          }
        }
    /* 固定列 */
        .el-table__header,
        .el-table__body {
          .el-table-fixed-column--right,
          .el-table-fixed-column--left {
            background: #080a19;
          }
        }
    /* 复选框 */
        .el-checkbox__inner {
          background: transparent;
          border: 1px solid #0A868C;
          width: 14px;
          height: 14px;
        }
    /* 复选框选中 */
        .el-checkbox__input.is-checked,
        .el-checkbox__input.is-indeterminate{
          .el-checkbox__inner {
            background: #0A868C;
            border: 1px solid #0A868C;
          }
        }
  }
}

/* popper弹窗 */
.popper-default {
  border: $border-theme !important;
  background: $bg-theme;

  /* 箭头颜色背景 */
  // .el-popper__arrow::before {
  //     background: $bg-theme;
  //     border-color: $color-theme;
  // }

  /* 箭头隐藏 */
  .el-popper__arrow {
    display: none;
  }

  /* select下拉框行 */
  .el-select-dropdown__item {
    color: $color-white;
    padding: 0 12px;

    &.hover,
    &:hover {
      background-color: $bg-li-hover;
    }
  }
  
    /* dropdown下拉菜单行 */
    .el-dropdown-menu__item {
        font-size: 12px;
        color: #000;
    }

  /* 单联date-picker日期选择器 */
  /* 面板 */
  .el-picker-panel {
    background: $bg-theme;
    color: $color-white;
  }

  /* 头部文字 */
  .el-date-picker__header-label,
  /* 左右按钮文字 */
  .el-picker-panel__icon-btn,
  /* 星期文字 */
  .el-date-table th,
  /* 月份文字 */
  .el-month-table td .cell,
  /* 月份文字 */
  .el-year-table td .cell {
    color: $color-white;

    &:hover {
      color: $color-blue;
    }
  }

  /* 双联date-picker日期选择器 */
  /* 内容右边框 */
  .el-picker-panel__content {
    border-right: $border-theme;
  }

  /* 选日期月份时范围背景色 */
  .el-date-table td.in-range .el-date-table-cell,
  .el-month-table td.in-range div,
  .el-month-table td.in-range div:hover {
            background: #2b2b2c;
    }

        /* 时间选择器 */ 
    .el-picker-panel.has-time {
        .el-picker-panel__footer {
            background: $background-color-theme;
            .el-button.is-text {
                color: #fff;
                &:hover {
                    background: none;
                    color: $color-theme;
                }
            }
            .el-button.is-plain {
                color: #333;
                border: none;
                background: $color-theme-hover;
                &:hover {
                    background: $color-theme;
                }
            }
        }
        .el-picker-panel__body {
            .el-input__wrapper {
                background: $background-color-theme;
                .el-input__inner {
                    color: #fff;
                }
            }
            .el-time-panel {
                background: $background-color-theme;
            }
        }
    }
    
    
 /* menu菜单 */
    .style-menu {
    padding: 4px 4px 0;

    /* 鼠标移入 */
    .el-menu-item:hover {
        color: #409eff !important;
    }

    /* 选中 */
    .el-menu-item.is-active {
        color: #165dff;
        font-weight: 550;
        background: #e8f3ff;
    }

    /* 一级菜单下拉菜单标题 */
    &>.el-menu-item,
    .el-sub-menu__title {
        margin-bottom: 4px;
        height: 38px;
        line-height: 38px;
    }

    /* 下拉箭头 */
    .el-sub-menu__icon-arrow {
        right: 10px;
        font-size: 16px;
    }

    /* 下拉菜单子项 */
    .el-sub-menu .el-menu-item {
        margin-bottom: 4px;
        padding-left: 20px !important;
        padding-right: 0 !important;
        height: 32px;
        line-height: 32px;
    }
}


 /* checkbox 多选框 */
     .style-checkbox {
        /* 每个选框单独一行 */
        display: block;
        /* 字体颜色 */
        --el-checkbox-checked-text-color: #606266;

        /* 选框颜色 */
        :deep(.el-checkbox__input.is-checked),
        /* 全选框颜色 */
        :deep(.el-checkbox__input.is-indeterminate) {
            .el-checkbox__inner {
                background: #165dff;
                border: 1px solid #165dff;
            }
        }
    }
}


/* dialog弹窗 */
.dialog-default {
        border: $border-purple;
        background: $bg-theme;

        /* 头部 */
        .el-dialog__header {
            padding: 16px;
            border-bottom: $border-purple;
            text-align: center;

            /* 标题 */
            .el-dialog__title {
                color: $color-white;
                font-size: $font-size-16;
                font-weight: $font-weight;
            }

            /* 删除按钮 */
            .el-dialog__headerbtn {
                top: 8px;
                right: 10px;
                font-size: 20px;
                width: auto;
                height: auto;

                i {
                    padding: 5px;
                }

                & :hover {
                    color: $color-white;
                }
            }
        }

        /* 内容 */
        .el-dialog__body {
            padding: 25px 20px;
        }

        /* 底部 */
        .el-dialog__footer {
            border-top: $border-purple;
            padding: 16px 20px;
            text-align: right;
        }
}

/* 消息box */
.el-message-box {
     /* 标题 */
    .el-message-box__title {}
    /* 内容 */
    .el-message-box__content {
        text-align: left;
        color: #1D2129;
    }
     /* 按钮 */
    .el-message-box__btns {
        justify-content: flex-end;

        .el-button.el-button--primary {
            background: #f53f3f;
            border: none;
        }
    }
}

表格+分页

<template>
  <div class="element-style">
    <!-- table表格 -->
    <el-table class="style-table" :style="width: 100%"
      :data="tableData" size="small" :row-class-name="rowClass" @row-click="handleRowClick" @selection-change="selectionChange">
      <!-- 复选框 -->
      <el-table-column type="selection" fixed="left" width="55" />
      <!-- 序号 -->
      <el-table-column prop="index" fixed="left" label="序号" width="50" />
      <!-- 遍历表格列 -->
      <el-table-column v-for="item in tableColunm" :key="item.prop" :label="item.label" :width="item.width"
        table-layout="auto" :align="item.prop == 'level' ? 'center' : 'left'" show-overflow-tooltip>
        <template #default="scope">
          <!-- 工单编号 -->
          <div v-if="item.prop == 'code'">
            {{ scope.row[item.prop] }}
            <el-button v-if="scope.row.index < 5" color="#093a46" size="small"
              style="color: #0ff8f8; margin-left: 5px">最新</el-button>
          </div>
          <!-- 带颜色按钮 -->
          <div v-else-if="item.prop == 'level'" :style="{
            display: 'inline-block',
            padding: '1px 8px',
            borderRadius: '2px',
            color: getLevelColor(scope.row[item.prop], 'color'),
            background: getLevelColor(scope.row[item.prop], 'bg'),
          }">
            {{ scope.row[item.prop] }}
          </div>
          <div v-else>{{ scope.row[item.prop] }}</div>
        </template>
      </el-table-column>
      <!-- 操作 -->
      <el-table-column align="center" label="操作" width="100" fixed="right">
        <template #default="scope">
          <el-popover popper-class="table-popover" placement="bottom" :width="140" trigger="hover">
            <template #reference>
              <div class="table-operate">...</div>
            </template>
            <div v-for="item in tableOperate" :key="item.value" class="popover-item"
              @click="() => handleOperate(scope.row, item)">
              {{ item.label }}
            </div>
          </el-popover>
        </template>
      </el-table-column>
    </el-table>

    <!-- Pagination 分页 -->
    <el-pagination class="style-pagination" :current-page="pageNum" :page-size="pageSize"
      popper-class="popper_default" :page-sizes="[20, 30, 40, 50]" :background="true"
      layout="total, prev, pager, next, sizes, jumper" :total="totalSize" @size-change="handleSizeChange"
      @current-change="handleCurrentChange">
    </el-pagination>

  </div>
</template> 

<script setup lang='ts'>

// 表格行高亮
const rowHighlight = ref('');
// 表格分页页码
const pageNum = ref(1);
// 表格分页页数
const pageSize = ref(20);
// 表格分页总数
const totalSize = ref(50);


// 表格多选项
const multipleSelection = ref([]);
// 表格列
const tableColunm = ref<ITableColunm[]>([
  { prop: 'code', label: TableEnum.OrderCode, width: '200' },
  { prop: 'level', label: TableEnum.UrgencyDegree },
  { prop: 'type', label: TableEnum.BusinessType },
  { prop: 'subType', label: TableEnum.BusinessSub },
  { prop: 'user', label: TableEnum.AppealAndPhone },
  { prop: 'time', label: TableEnum.DealTime },
  { prop: 'content', label: TableEnum.CallContent },
]);
// 表格数据
const tableData = [
  {
    index: 1,
    code: '202201328976543',
    level: '12398',
    type: '客户投诉',
    subType: '停电/停电故障',
    powerType: '商业用电',
    user: '李明博 - 177 9876 5678',
    userCode: '李达康 - 20837898689692',
    time: '2022-12-09 22:25:33',
    content: '一户停电,需要工作人员到场协助开启变电箱。核实处理。',
  },
  {
    index: 2,
    code: '202201328976543',
    level: '督办',
    type: '客户投诉',
    subType: '停电/停电故障',
    powerType: '商业用电',
    user: '李明博 - 177 9876 5678',
    userCode: '李达康 - 20837898689692',
    time: '2022-12-09 22:25:33',
    content: '一户停电,需要工作人员到场协助开启变电箱。核实处理。',
  },
  {
    index: 3,
    code: '202201328976543',
    level: '一般',
    type: '客户投诉',
    subType: '停电/停电故障',
    powerType: '商业用电',
    user: '李明博 - 177 9876 5678',
    userCode: '李达康 - 20837898689692',
    time: '2022-12-09 22:25:33',
    content: '一户停电,需要工作人员到场协助开启变电箱。核实处理。',
  },
  {
    index: 4,
    code: '202201328976543',
    level: '12398',
    type: '客户投诉',
    subType: '停电/停电故障',
    powerType: '商业用电',
    user: '李明博 - 177 9876 5678',
    userCode: '李达康 - 20837898689692',
    time: '2022-12-09 22:25:33',
    content: '一户停电,需要工作人员到场协助开启变电箱。核实处理。',
  },
  {
    index: 5,
    code: '202201328976543',
    level: '12398',
    type: '客户投诉',
    subType: '停电/停电故障',
    powerType: '商业用电',
    user: '李明博 - 177 9876 5678',
    userCode: '李达康 - 20837898689692',
    time: '2022-12-09 22:25:33',
    content: '一户停电,需要工作人员到场协助开启变电箱。核实处理。',
  },
];
/**
 * 表格选中行
 * @param {type} 参数
 * @returns {type} 返回值
 */
const handleRowClick = (val) => {
    orderStore.$patch((state) => {
        state.workNo = val.workNo;
        state.showOrderDetail = true;
        state.showOrderCard = false;
        state.refreshUserInfo = true;
    });
    rowHighlight.value = val.workNo || '';
};
/**
 * 表格多选框
 * @param {type} 参数
 * @returns {type} 返回值
 */
const selectionChange = (val) => {
  multipleSelection.value = val;
};
/**
 * 选中行样式
 * @param {type} 参数
 * @returns {type} 返回值
 */
const rowClass = ({ row, rowIndex }) => {
    // 勾选高亮
    if (multipleSelection.value.find((item: any) => item.workNo === row.workNo)) {
        return 'check-row';
    }
    // 点击高亮
    if (rowHighlight.value && row.workNo === rowHighlight.value) {
        return 'click-row';
    }
};
/**
 * 表格分页页码改变
 * @param {number} val 页码
 */
const handleCurrentChange = (val: number) => {
  pageNum.value = val;
}
/**
 * 表格分页页数改变
 * @param {number} val 页码
 */
const handleSizeChange = (val: number) => {
  pageSize.value = val;
};
/**
 * 获取表格紧急程度颜色
 * @param {type} 参数
 * @returns {type} 返回值
 */
const getLevelColor = (value: string, type: string) => {
    // 表格紧急程度颜色
    const levelColor = [
        { name: '12398', color: '#F53F3F', bgColor: '#371824' },
        { name: '投诉', color: '#F53F3F', bgColor: '#371824' },
        { name: '督办', color: '#FF7D00', bgColor: '#392419' },
        { name: '紧急', color: '#FF7D00', bgColor: '#392419' },
        { name: '预警', color: '#FF7D00', bgColor: '#392419' },
        { name: '一般', color: '#165DFF', bgColor: '#09173c' },
    ];
    const obj = levelColor.find((item) => item.name == value);
    if (type == 'color') {
        return obj?.color;
    }
    return obj?.bgColor;
};
/**
 * 清除表格行高亮
 * @param {type} 参数
 * @returns {type} 返回值
 */
const clearRowBg = () => {
    if (rowHighlight.value) {
        rowHighlight.value = '';
    }
};
</script> 

遍历生成筛选输入下拉框

<template>
    <div class="element-style">
        <!-- 输入框遍历 -->
        <div class="input-filter">
            <div v-for="item in formData" :key="item.name" class="filter-item">
                <div class="item-name" v-if="item.name">{{ item.name }}</div>
                <div class="item-form">
                    <!-- 下拉框 -->
                    <el-select v-if="item.type === 'select'" v-model="item.value" popper-class="popper-default"
                        class="style-select" :placeholder="item.placeholder || '请选择'" size="default"
                        @change="(val) => valueChange(item.type, val)">
                        <el-option v-for="value in item.options" :key="value.value" :label="value.label"
                            :value="value.value" />
                    </el-select>
                    <!-- 单选框 -->
                    <el-radio-group v-else-if="item.type === 'radio'" class="style-radios" v-model="item.value" @change="(val) => valueChange(item.type, val)">
                        <el-radio v-for="(value, i) in item.radio" :key="value" :label="i">{{ value }}</el-radio>
                    </el-radio-group>
                    <!-- 单联日期选择器 -->
                    <el-date-picker v-else-if="item.type === 'dataPicker'" class="style-input style-picker" v-model="item.value" @change="(val) => valueChange(item.type, val)"
                        popper-class="popper-default" value-format="YYYY-MM-DD" type="date"
                        :placeholder="item.placeholder" size="default" />
                    <!-- 双联日期选择器 -->
                    <el-date-picker v-else-if="item.type === 'dataPickers'" class="style-input style-picker" v-model="item.value" @change="(val) => valueChange(item.type, val)"
                        popper-class="elDatePicker" value-format="YYYY-MM-DD" type="daterange" range-separator="To"
                        start-placeholder="开始日期" end-placeholder="结束日期" size="default" @change="dateChange" />
                    <!-- textarea输入框 -->
                    <el-input v-else-if="item.type === 'textarea'" v-model="item.textareaValue" :rows="item.rows || 1" @change="(val) => valueChange(item.type, val)"
                        class="style-input" type="textarea" :placeholder="item.placeholder" />
                    <!-- input输入框 -->
                    <el-input v-else-if="item.type === 'input'" v-model="item.value" class="style-textarea" @change="(val) => valueChange(item.type, val)"
                        :placeholder="item.placeholder" ></el-input>
                </div>
            </div>
            <slot></slot>
        </div>
    </div>
</template>

<script setup lang="ts">
import { watch } from 'vue';
const emits = defineEmits<{
  (e: "change", key: String, value: any,): void;
}>()

// [{ name: '类型', key: 'channelType', value: '', defaultValue: 'all', type: 'select', option: [ { label: '全部', value: 'all' }, { label: '其他', value: '3' }, ] }]

const props = defineProps<{ formData }>();

watch(() => props.formData, (val) => {
  val.forEach(v => {
    if (v.defaultValue) {
      v.value = v.defaultValue
    }
  })

}, { immediate: true })
/**
 * 表单数据改变 
 */ 
const valueChange = (key, value) => { emits("change", key, value); }
</script>

<style scoped lang="scss">
.element-style {
    .input-filter {
        @include flex(5);

        .customer-item {
            margin-bottom: 25px;
            width: 30%;

            .item-name {
                margin-bottom: 15px;
            }
        }
    }
}
</style>

upload上传

<template>
    <div class="element-style">
        <!-- 上传excel -->
        <el-upload
            ref="uploadRef"
            class="style-upload"
            :data="uploadData"
            :auto-upload="false"
            accept=".xlsx, .xls"
            :on-change="handleChange"
            :on-success="handleSuccess"
            :on-error="handleError"
            :on-exceed="handleExceed"
            :before-remove="beforeRemove"
            :action="actionUrl"
            :limit="1"
            :file-list="excelList"
        >
            <el-button slot="trigger" size="small" type="primary">选取文件</el-button>
            <div slot="tip" class="el-upload__tip">只能上传.xls/.xlsx文件</div>
        </el-upload>
        <!-- upload图片上传 -->
        <el-upload
            class="style-upload"
            multiple
            action=""
            accept=".png, .jpg, .jpeg"
            :on-remove="handleImgRemove"
            :on-change="onImgChange"
            :file-list="uploadImg"
            :auto-upload="false"
        >
            <el-button size="small" type="primary">批量上传图片</el-button>
            <div slot="tip" class="el-upload__tip">上传图片名称需与表格图片名称对应, 图片大小4mb以内</div>
        </el-upload>
        <el-button type="success" size="small" @click="handleSubmit">上传到服务器</el-button>



        <!-- Collapse 折叠面板 -->

        <!-- Tree 树形控件 -->
    </div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue';
import { ElMessage, ElLoading } from 'element-plus';

const loading = ref();
const imgActionUrl = window.uploadUrl + '/batchupload';
const actionUrl = window.uploadUrl + 'import';
const uploadImg = ref<any[]>([]); // 图片列表
const excelList = ref<any[]>([]); // 上传文件
const uploadImgSuccessList = ref<any[]>([]); //图片上传成功列表
const uploadRef = ref<any>(null);

// 上传携带参数
const uploadData = computed(() => {
    return {};
});

/**
 * 上传change
 * @param {type} 参数
 * @returns {type} 返回值
 */
function handleChange(file, fileList) {
    // ...
    excelList.value = fileList;
}
/**
 * 上传成功
 * @param {Object} response 响应
 * @param {Object} file 文件
 * @param {Object} fileList 文件夹
 */
function handleSuccess(response, file, fileList) {
    loading.value.close();
    ElMessage({ offset: 100, center: true, message: `上传成功`, type: 'success' });
}
/**
 * 上传错误
 * @param {string} err 错误
 * @param {Object} file 文件
 * @param {Object} fileList 文件夹
 */
function handleError(err, file, fileList) {
    if (err) {
        loading.value.close();
        ElMessage({ offset: 100, center: true, message: `excel上传失败, 请重试`, type: 'error' });
    }
}
/**
 * 文件超出数量上限
 * @param {Object} file 文件
 * @param {Object} fileList 文件夹
 */
function handleExceed(files, fileList) {
    ElMessage({
        offset: 100,
        center: true,
        message: `当前限制选择 1 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`,
        type: 'warning',
    });
}

/**
 * 删除上传文件
 * @param {type} 参数
 * @returns {type} 返回值
 */
function beforeRemove(file, fileList) {
    excelList.value = [];
    return true;
}
/**
 * 处理图片移除
 * @param {Object} file 文件
 * @param {Object} fileList 文件夹
 */
function handleImgRemove(file, fileList) {
    uploadImg.value = [...fileList];
}

/**
 * 图片改变
 * @param {Object} file 文件
 * @param {Object} fileList 文件夹
 */
function onImgChange(file, fileList) {
    if (file.status === 'success') {
        return;
    }
    const types = ['image/jpeg', 'image/png', 'image/jpg'];
    const isJPG = types.includes(file.raw.type);
    const isLt2M = file.size / 1024 / 1024 < 4;

    if (!isJPG) {
        ElMessage({ offset: 100, center: true, message: '图片只能是 JPG/PNG/JPEG 格式!', type: 'error' });
        const index = fileList.map((item) => item.uid).indexOf(file.uid);
        fileList.splice(index, 1);
    }
    if (!isLt2M) {
        ElMessage({ offset: 100, center: true, message: '图片大小不能超过 4MB!', type: 'error' });
        const index = fileList.map((item) => item.uid).indexOf(file.uid);
        fileList.splice(index, 1);
    }
    if (uploadImg.value.length) {
        if (uploadImg.value.map((item) => item.name).includes(file.name)) {
            ElMessage({ offset: 100, center: true, message: '图片重复,已移除!', type: 'error' });
            const index = fileList.map((item) => item.uid).indexOf(file.uid);
            fileList.splice(index, 1);
        }
    }
    uploadImg.value = [...fileList];
}
/**
 * 上传图片
 */
async function uploadImgFn() {
    if (uploadImg.value.length === 0) {
        return;
    }
    const formData = new FormData();
    uploadImg.value.forEach((item) => {
        formData.append('files', item.raw);
    });
    formData.append('isNeedSync', '1');
    const res = await fetch(imgActionUrl, { method: 'POST', body: formData }).then((response) => response.json());
    if (res && res.code === 2000) {
        uploadImgSuccessList.value = res.data;
    }
}
/**
 * 提交上传
 * @param {type} 参数
 * @returns {type} 返回值
 */
async function handleSubmit() {
    //加载中提示
    loading.value = ElLoading.service({
        lock: true,
        text: '上传数据中,请稍等...',
        spinner: 'el-icon-loading',
        background: 'rgba(0, 0, 0, 0.7)',
    });
    await uploadImgFn();
    uploadRef.value.submit();
}
</script>

<style scoped lang="scss">
.element-style {
    /* upload上传 */
    .style-upload {
        margin-top: 20px;

        .el-upload-list {
            max-height: 200px;
            overflow-y: auto;
        }
    }
}
</style>

collapse 折叠面板

<template>
    <div class="element-style">
        <!-- checkbox 多选框 -->

        <!-- Collapse 折叠面板 -->
        <el-collapse
            v-for="(value, key) in collapseList"
            :key="key"
            v-model="activeNames"
            class="style-collapse"
            accordion
            @change="handleChange"
        >
            <el-collapse-item :name="key">
                <template #title>
                    <div>{{ key }}</div>
                    <!-- 操作点 -->
                    <el-popover placement="bottom-start" :visible-arrow="false" width="100" trigger="click">
                        <i slot="reference" style="font-size: 20px" @click.stop>...</i>
                        <!-- 编辑按钮 -->
                        <div class="el-icon-edit list-item-edit" @click.stop="handleEdit(key)">&nbsp;&nbsp;重命名</div>
                        <!-- 删除按钮 -->
                        <div class="el-icon-delete list-item-edit" @click.stop="handleDel(key)">&nbsp;&nbsp;删除</div>
                        <!-- 点 -->
                    </el-popover>
                </template>
                <template v-if="showCollapse"> 内容 </template>
            </el-collapse-item>
        </el-collapse>
        <!-- Tree 树形控件 -->
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const collapseList = ref([]);
const showCollapse = ref(true);
const activeNames = ref(''); // 折叠面板选中项

/**
 * 折叠面板切换事件
 * @param {string} val
 */
function handleChange(val) {
    if (!val) {
        return;
    }
    if (!collapseList.value[activeNames.value]) {
        showCollapse.value = false;
    } else {
        showCollapse.value = false;
        showCollapse.value = true;
    }
}
</script>

<style scoped lang="scss">
.element-style {
    /* Collapse 折叠面板 */
    .style-collapse {
        border-top: none;

        .el-collapse-item__header {
            position: relative;
            padding-left: 15px;
            height: 38px;
            line-height: 38px;
            display: flex;
            justify-content: space-between;

            .el-collapse-item__arrow {
                position: absolute;
                left: 0;
            }
        }

        .el-collapse-item__content {
            padding-bottom: 0;
        }
    }
}
</style>

tree 树形结构

<template>
    <div class="element-style">
        <!-- Tree 树形控件 -->
        <el-card class="main-card">
            <el-input v-model="filterText" class="card-input" placeholder="请输入文本" suffix-icon="el-icon-search"></el-input>
            <div class="card-tree">
                <el-tree
                    ref="treeRef"
                    lazy
                    :data="treeList"
                    :load="loadNode"
                    :props="defaultProps"
                    highlight-current
                    node-key="id"
                    :filter-node-method="filterNode"
                    @node-click="clickNode"
                >
                    <!-- 树形内容 -->
                    <template #default="{ node, data }">
                        <div class="tree-content">
                            <div class="content-text">
                                <!-- 文件夹图标 -->
                                <i v-show="data.type == 0" class="text-icon mzIconfont icon-file"></i>
                                <!-- 杆塔图标 -->
                                <i v-show="data.type != 0" class="mzIconfont icon-tower text-tower"></i>
                                <!-- 名称 -->
                                <el-tooltip
                                    v-if="data.folderName && data.folderName.length > 10"
                                    effect="dark"
                                    :content="data.folderName || data.name"
                                    placement="top"
                                >
                                    <span>{{ data.folderName || data.name }}</span>
                                </el-tooltip>
                                <span v-else>{{ data.folderName || data.name }}</span>
                            </div>
                            <div class="content-icon">
                                <!-- 显示隐藏元素 -->
                                <i
                                    v-if="data.type == 0 && data.status !== undefined"
                                    :class="['mzIconfont', data.status ? 'icon-eye' : 'icon-no_eye']"
                                    @click.stop="openFn(node, data)"
                                ></i>
                                <!-- 关联按钮 -->
                                <i v-if="data.type == 0" class="mzIconfont icon-guanlian1" @click.stop="linkFn(data)"></i>
                            </div>
                        </div>
                    </template>
                </el-tree>
            </div>
        </el-card>
    </div>
</template>

<script setup lang="ts">
import { ref, watch } from 'vue';

const isSearch = ref(false); // 搜索状态
const treeList = ref<any[]>([]); //树形数据
const filterText = ref(''); //过滤关键词
const treeTimer = ref<any>(null); //搜索延时器
const treeRef = ref<any>(null);
//树形数据默认结构
const defaultProps = ref({
    children: 'children',
    label: 'folderName',
    isLeaf: 'isLeaf',
});

watch(filterText, async (newVal, oldVal) => {
    // treeRef.value!.filter(val);
    // 清除延时器
    if (treeTimer.value) {
        clearTimeout(treeTimer.value);
        treeTimer.value = null;
    }
    // 空值
    if (!newVal) {
        //非搜索类型
        isSearch.value = false;
        // 重新请求第一层数据
        const res = await postFolderTreeAPI({});
        res[0].children.splice(0, 5);
        treeList.value = setStatus([...res[0].children], true);
        return;
    }
    // 是搜索类型
    isSearch.value = true;

    treeTimer.value = setTimeout(async () => {
        //模糊搜索请求
        const res = await postFolderSearchAPI({});
        // 要素类型isLeaf改为true
        const portalDataSources = res[0].portalDataSources;
        portalDataSources &&
            portalDataSources.forEach((item) => {
                item.isLeaf = true;
            });
        // 渲染到tree
        treeList.value = [...res[0].children, ...portalDataSources].splice(0, 30);
    }, 500);
});

/**
 * 加载节点
 * @param {type} 参数
 * @returns {type} 返回值
 */
function loadNode(node, resolve) {
    //如果展开第一级节点,从后台加载一级节点列表
    if (node.level === 0) {
        loadfirstnode(resolve);
    }
    //如果展开其他级节点,动态从后台加载下一级节点列表
    if (node.level >= 1) {
        loadchildnode(node, resolve);
    }
}
/**
 * 加载第一级节点
 * @param {type} 参数
 * @returns {type} 返回值
 */
async function loadfirstnode(resolve) {
    const res = await postFolderTreeAPI({});
    res[0].children.splice(0, 5);
    return resolve(setStatus([...res[0].children], true));
}
/**
 * 加载节点的子节点集合
 * @param {type} 参数
 * @returns {type} 返回值
 */
async function loadchildnode(node, resolve) {
    // console.log("超过二级的", node, node.level);
    const res = await postFolderTreeAPI({});

    // 1.节点children不为空,portalDataSources不为空
    if (res[0].children.length > 0 && res[0].portalDataSources.length > 0) {
        const data = res[0].portalDataSources;
        data &&
            data.forEach((item) => {
                item.isLeaf = true;
            });
        const children = setStatus(res[0].children, true);
        children.push(...data);
        return resolve(children);
    }
    // 2.节点children不为空,portalDataSources为空
    if (res[0].children.length > 0 && res[0].portalDataSources.length == 0) {
        return resolve(setStatus(res[0].children, true));
    }
    // 3.节点children为空,portalDataSources不为空
    if (res[0].children.length == 0 && res[0].portalDataSources.length > 0) {
        const data = res[0].portalDataSources;
        data &&
            data.forEach((item) => {
                item.isLeaf = true;
            });
        return resolve(data);
    }
}
// 给子节点设置状态
function setStatus(data, status) {
    // 非搜索类型才加状态
    if (!isSearch.value) {
        data.forEach((item) => {
            item.status = status;
        });
    }
    return data;
}
/**
 * 过滤数据
 * @param {type} 参数
 * @returns {type} 返回值
 */
const filterNode = (value: string, data) => {
    if (!value) {
        return true;
    }
    return data.label.includes(value);
};
/**
 * 点击树形节点
 * @param {type} 参数
 * @returns {type} 返回值
 */
async function clickNode(data, node) {}
</script>

<style scoped lang="scss">
.element-style {
    /* Collapse 折叠面板 */
    .style-collapse {
        border-top: none;

        .el-collapse-item__header {
            position: relative;
            padding-left: 15px;
            height: 38px;
            line-height: 38px;
            display: flex;
            justify-content: space-between;

            .el-collapse-item__arrow {
                position: absolute;
                left: 0;
            }
        }

        .el-collapse-item__content {
            padding-bottom: 0;
        }
    }
}
</style>

el-form表单

<template>
    <div class="element-style">
    <!-- el-form表单 -->
     <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" class="style-form">
                <el-form-item prop="telephone">
                    <div class="item-title">账号</div>
                    <el-input class="style-input" v-model="ruleForm.telephone" size="large" type="text" />
                </el-form-item>
                <el-form-item prop="password">
                    <div class="item-title">密码</div>
                    <el-input class="style-input" v-model="ruleForm.password" size="large" :type="showPass ? 'text' : 'password'" />
                    <el-icon @click="showPassword"><Hide v-if="!showPass" /><View v-else /></el-icon>
                </el-form-item>
                <el-form-item>
                    <el-button size="large" color="#0FF8F8" @click="login(ruleFormRef)">登录</el-button>
                </el-form-item>
        </el-form>
    </div>
</template>
<script setup lang="ts">
// 显示密码
const showPass = ref(false);
// 表单ref
const ruleFormRef = ref<FormInstance>();
// 验证码图片
const authCode = ref({
    key: '',
    image: '',
});
// 表单字段
const ruleForm = reactive({
    password: '',
    telephone: '',
    code: '',
});
// 表单校验
const rules = reactive<FormRules>({
    telephone: [{ required: true, message: '请输入账号', trigger: 'blur' }],
    password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
    code: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
});
/**
 * 获取验证码
 */
const getAuthCode = async () => {
    try {
        const res: any = await UserAPI.getAuthCode();
        authCode.value = res;
    } catch (err: any) {
        console.log(err);
    }
};
/**
 * 登录
 * @param {FormInstance} formEl
 */
const login = async (formEl: FormInstance | undefined) => {
}
/**
 * 显示密码
 */
const showPassword = () => {
    showPass.value = !showPass.value;
};
</script>
<style lang="scss">
.element-style {
  .style-form {
      .el-form-item {
        margin-bottom: vh(45);

        .el-form-item__label {}

        .el-form-item__content {
          .el-input {

            /* 容器 */
            .el-input__wrapper {
              padding-left: 60px;
              border-bottom: 1px solid #5B66BD;

              /* 内容 */
              .el-input__inner {
                font-size: 16px;
              }
            }
          }
          .el-icon {
            position: absolute;
            right: 17px;
            top: 10px;
            font-size: 16px;
            color: #4e5969;
            cursor: pointer;
          }
        }
      }
    }
}
</style>

radio group 生成 tab

image.png

<el-radio-group @change="onTabChange" size="large" v-model="tabValue">
       <el-radio-button label="日"></el-radio-button>
       <el-radio-button label="月"></el-radio-button>
</el-radio-group>


.el-radio-group {
    padding: 3px;
    padding-right: 0;
    background: #f1f2f5;

    /* 中间线 */
    .el-radio-button {
        margin-right: 3px;
        --el-radio-button-checked-border-color: transparent !important;
        /* 按钮 */
        .el-radio-button__inner {
            border: none !important;
            background: unset;
        }
        
        /* 悬浮 */
        &:hover .el-radio-button__inner {
            background: #fff;
            color: #333;
        }

        /* 选中 */
        &.is-active .el-radio-button__inner {
            background: #fff;
            color: #0052d9;
        }
    }
}

image.png

<el-radio-group v-model="radioValue" size="mini">
                <el-radio-button label="medium">中压疑似停电</el-radio-button>
                <el-radio-button label="low">低压疑似停电</el-radio-button>
            </el-radio-group>
            
.el-radio-button {
            .el-radio-button__inner {
                border-radius: 14px 0 0 14px;
            }

            &:last-child .el-radio-button__inner {
                border-radius: 0 14px 14px 0;
                box-shadow: -1px 0 0 0 #BCD9FC;
            }

            &.is-active .el-radio-button__inner {
                background-color: #eff5fe;
                border-color: #BCD9FC;
                color: #006bff;
            }
        }

下拉框加 loading 状态

el-cascader不支持v-loading el-select :loading效果差 需要正常的动画 loading 状态

image.png

实现方案

利用 服务方法 ElLoading, 它支持传dom的类名

实现代码

import { ElLoading } from 'element-plus';
import { ref, nextTick } from "vue";

/**
  * 添加加载状态
  */
export const useLoading = () => {
    const cascaderLoading = ref(null);//加载动画
    
    return {
        cascaderLoading,
        showLoading: (className: string) => {
            let options = {
                target: className,//你自定义的类名
                lock: true,
                text: '加载中...'
            };
            nextTick(() => {
                cascaderLoading.value = ElLoading.service(options);
            });
        },
        closeLoading: () => cascaderLoading.value?.close();
    }
}


  • 使用
<el-cascader popper-class="cascader-loading"v-model="cascaderData" :options="cascaderOptions" @change="cascaderChange" :props="optionProps" clearable />

import { useLoading } from '@/utils'

const { showLoading, closeLoading } = useLoading()

const requestCascaderData = ()=> {
    showLoading('.cascader-loading')
    //....
    closeLoading()
}

带加载状态的消息弹窗

要实现一个类似element的ElMessage但是带加载状态的效果, ElMessage上加的图标默认是静态的, 所以仿ElMessage写一个组件, 带动画, 支持加载状态

image.png

实现步骤

  1. 利用el-button可以带loading效果的特性, 实现loading动态图标
<el-button text loading>正在校验...</el-button>
  1. 容器和居中定位(注意水平居中定位不能用 transform: translate, 会影响动画用的transform: translate)
 <div v-if="uploadStatus === 1" class="content-checkout">
    <el-button text loading>正在校验...</el-button>
 </div>
 
  .content-checkout {
        position: absolute;
        top: -130px;
        left: 50%;
        margin-left: -80px;
        padding: 8px 16px 8px 8px;
        border-radius: 5px;
        background: #fff;

        .el-button {
            color: #0052d9;

            :deep(.el-icon) {
                font-size: 18px;
            }
        }
    }
  1. 利用vue的动画组件Transition实现顶部移入移出动画效果
 <Transition name="slide-fade">
    ...
 </Transition>
 
 .slide-fade-enter-active,
.slide-fade-leave-active {
    transition: all 0.5s;
}

.slide-fade-enter-from,
.slide-fade-leave-to {
    transform: translateY(-50px);
}

实现代码

<Transition name="slide-fade">
    <div v-if="uploadStatus === 1" class="content-checkout">
        <el-button text loading>正在校验...</el-button>
    </div>
</Transition>
            
            
.slide-fade-enter-active,
.slide-fade-leave-active {
    transition: all 0.5s;
}

.slide-fade-enter-from,
.slide-fade-leave-to {
    transform: translateY(-50px);
}

.content-checkout {
        position: absolute;
        top: -130px;
        left: 50%;
        margin-left: -80px;
        padding: 8px 16px 8px 8px;
        border-radius: 5px;
        background: #fff;

        .el-button {
            color: #0052d9;

            :deep(.el-icon) {
                font-size: 18px;
            }
        }
    }

轮播图+ 预览+ 上传+ 删除

实现轮播图能够预览, 还支持上传和删除图片的功能 html

<!-- 轮播图 -->
<el-carousel :initial-index="1" trigger="click" height="180px" :autoplay="false">
    <el-carousel-item style="text-align:center" v-for="(item, index) in urlList" :key="item">
        <!-- 图片 -->
        <img @mouseenter="handleMouseenter(index)" style="height:100%" :src="item" alt="">
        <!-- 遮罩层 -->
        <div v-show="activeIndex == index" @mouseout="handleMouseleave"
             style="position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);display:flex;justify-content: center;align-items: center;color:#fff;font-size:24px">
            <!-- 打开大图 -->
            <i @click="openImg(item)" style="margin-right:20px;cursor:pointer" class="el-icon-zoom-in"></i>
            <!-- 删除图片 -->
            <i @click="delImg(index)" style="cursor:pointer" class="el-icon-delete"></i>
        </div>
    </el-carousel-item>
    <!-- 新增图片 -->
    <el-carousel-item>
        <div style="margin-top:18px;text-align:center">
            <el-upload :data="uploadData" :action="uploadUrl" list-type="picture-card" :show-file-list="false"
                       :on-success="handleSuccess">
                <i class="el-icon-plus"></i>
            </el-upload>
        </div>
    </el-carousel-item>
</el-carousel>
<el-dialog :visible.sync="dialogVisible">
    <img width="100%" :src="dialogImageUrl" alt="">
</el-dialog>

js

  data() {
    return {
      activeIndex: null, // 当前下标
      dialogVisible: false, // 显示大图
      dialogImageUrl: '', // 大图路径
      urlList: [], // 图片路径
      files: [], // 图片资源
      uploadUrl: window['uploadUrl'] + '/thp/filesvrapi/v1/fileservice/singleupload',
      uploadData: { isNeedSync: 1 }
    }
  },
watch: {
    activedItem: {
      handler(val) {
        this.urlList = [];
        let files = []
        if (val.files.length > 0) {
          files= JSON.parse(JSON.stringify(val.files))
        }
        this.files = files
        if (files.length > 0) {
          this.getImgUrl(files)
        }
      },
      deep: true
    }
  },
methods: {
    /**
     * 图片上传成功
     * @param {type} 参数
     * @returns {type} 返回值
     */
    handleSuccess(res) {
      const file = res.data
      // 更新图片展示
      this.getImgUrl([file])
      // 更新图片数据
      this.files.push(file)
      // 更新项目详情
      // this.updateDetail()
    },
    /**
     * 更新项目详情
     * @param {type} 参数
     * @returns {type} 返回值
     */
    async updateDetail() {
      const data = JSON.parse(JSON.stringify(this.activedItem))
      data.files = this.files
      const res = await postWellImgUpdate(data)
      if (res && res.code == 2000) {
        this.$message({ offset: 100, center: true, message: '删除成功', type: 'success' })
      }
    },
    /**
     * 删除图片
     * @param {number} index
     */
    delImg(index) {
      this.$confirm(`确定移除该图片?`).then(() => {
        // 删除路径
        this.urlList.splice(index, 1);
        // 删除文件服务图片
        const id = this.files[index] ? this.files[index].id : '';
        this.handleFilesDel(id)
        // 删除详情图片
        this.files.splice(index, 1)
        // 更新项目详情
        // this.updateDetail()
      })
    },
    /**
  * 文件服务删除
  * @param {string} ids
  */
    handleFilesDel(ids) {
      if (!ids) return
      fetch(`${window['uploadUrl']}/thp/filesvrapi/v1/fileservice/delete`, {
        method: 'post', headers: {
          'Content-Type': 'application/json'
        }, body: JSON.stringify({
          "fileIds": ids
        })
      })
    },
    /**
     * 打开大图
     * @param {string} url
     */
    openImg(url) {
      this.dialogImageUrl = url;
      this.dialogVisible = true;
    },
    /**
     * 鼠标移入图片
     * @param {number} index 下标
     */
    handleMouseenter(index) {
      this.activeIndex = index
    },
    /**
 * 鼠标移出图片
 * @param {number} index 下标
 */
    handleMouseleave() {
      this.activeIndex = null
    },
    /**
     * 请求图片路径
     * @param {[]} files
     */
    getImgUrl(files) {
      files.forEach(item => {
        fetch(`${window['uploadUrl']}/thp/filesvrapi/v1/fileservice/singledownload?fileId=` + item.id, {
          method: 'get',
          responseType: 'blob'
        }).then(res => res.blob()).then(blob => {
          const url = window.URL.createObjectURL(new Blob([blob], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }));
          this.urlList.push(url)
        })
      })
    }
  }

封装element-ui 的表格, columns 改成配置式

支持传入column配置, 支持render方法, 以prop为名的命名插槽 /src/components/Table.vue

  <el-table class="gTable" :data="$attrs.data" style="width: 100%" v-bind="$attrs" v-on="$listeners">
    <template v-for=" (item, index)  in  $attrs.columns ">
      <!-- 复选框 -->
      <el-table-column v-if="item.prop === 'selection'" :type="item.prop" v-bind="item"></el-table-column>
      <!-- 序号 -->
      <el-table-column v-else-if="item.prop === 'index'" :type="item.prop" v-bind="item"></el-table-column>
      <el-table-column v-else v-bind="item">
        <template slot-scope="scope">
          <!-- render方法 -->
          <div v-if="item.customRender" v-html="customRender(scope.row)"></div>
          <!-- prop为名的插槽 -->
          <div v-else-if="$scopedSlots[item.prop]">
            <slot :name="item.prop" v-bind="scope.row"></slot>
          </div>
          <!-- 文本 -->
          <div v-else>{{ scope.row[item.prop] }}</div>
        </template>
      </el-table-column>
    </template>
  </el-table>
  • 使用
<Table border :data="tableData" :columns="tableColumns">
          <template v-slot:operate="{ row }">
            <el-button>查看</el-button>
          </template>
        </Table>

import { Table } from '/@/components/index.ts'

  const tableData = [
    {
      no: '040500400183445',
      area: '040500400183445',
      lineName: '馈线名称',
      pbName: '宜州市怀远镇罗山村',
      type: '问题类型',
      typeDetail: '问题类型明细',
      from: '问题来源',
    }
  ]
 const  tableColumns = [
    { prop: 'index', label: '序号' },
    { prop: 'no', label: '问题编码' },
    { prop: 'area', label: '区域名称' },
    { prop: 'lineName', label: '馈线名称' },
    { prop: 'pbName', label: '配变名称' },
    { prop: 'type', label: '问题类型' },
    { prop: 'typeDetail', label: '问题类型明细' },
    { prop: 'from', label: '问题来源' },
    { prop: 'operate', label: '操作' },
  ]

步骤条关联标题, 实现点击步骤滚动到标题位置

使用了el-step步骤条组件, 但是组件不支持click事件, 加个容器, click事件冒泡到容器 点击事件中修改activeStep值, 动态改选中step 获取标题元素, 利用dom 的scrollIntoView方法滚动内容

image.png

    <!-- 左边-步骤条 -->
      <div class="container-steps" @click="clickStep">
        <el-steps space="60px" direction="vertical" :active="activeStep">
          <el-step style="cursor: pointer" v-for="step in steps" :key="step" :title="step"></el-step>
        </el-steps>
      </div>
      <!-- 左边-信息 -->
      <div class="container-program">
    <!-- 方案基础信息 -->
        <div class="program-title" @click="clickStep">
          {{ steps[0] }}
        </div>
        <!-- 中压部分 -->
        <div class="program-title" @click="clickStep">
          {{ steps[1] }}
        </div>
        <div style="height: 300px"></div>
        <!-- 低压部分 -->
        <div class="program-title" @click="clickStep">
          {{ steps[2] }}
        </div>
        <div style="height: 300px"></div>

        <!-- 配电自动化 -->
        <div class="program-title" @click="clickStep">
          {{ steps[3] }}
        </div>
        <div style="height: 300px"></div>

        <!-- 智能电表及低压集抄 -->
        <div class="program-title" @click="clickStep">
          {{ steps[4] }}
        </div>
        <div style="height: 300px"></div>

        <!-- 配电网通信 -->
        <div class="program-title" @click="clickStep">
          {{ steps[5] }}
        </div>
        <div style="height: 300px"></div>

        <!-- 智能配电 -->
        <div class="program-title" @click="clickStep">
          {{ steps[6] }}
        </div>
        <div style="height: 300px"></div>
      </div>


  steps = [
    '基础信息',
    '中压部分',
    '低压部分',
    '自动化',
    '电表',
    '通信',
    '配电',
  ]
  activeStep = 1

  /**
   * 点击步骤
   */
  clickStep(e) {
    const title = e.target.innerText
    const index = this.steps.findIndex(v => v === title)
    this.activeStep = index + 1

    // 获取对应的标题元素
    const titleElement = document.querySelectorAll('.program-title')[index];
    // 滚动到标题元素所在的位置
    titleElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
  }