vue仿寫teambition的篩選功能(使用餓了麼UI)

問題描述

teambition軟件是企業辦公協同軟件,相信部分朋友的公司應該用過這款軟件。裏面的篩選功能挺有意思,本篇文章,就是仿寫其功能。咱們先看一下最終作出來的效果圖
imagehtml

大體的功能效果有以下

  • 需求一:經常使用篩選條件放在上面直接看到,不經常使用篩選條件放在添加篩選條件裏面
  • 需求二:篩選的方式有輸入框篩選、下拉框篩選、時間選擇器篩選等
  • 需求三:若是以爲經常使用篩選條件比較多的話,能夠鼠標移入點擊刪除,使之進入不經常使用的篩選條件裏
  • 需求四:也能夠從不經常使用的篩選條件裏面點擊對應篩選條件使之「蹦到」經常使用篩選條件裏
  • 需求五:點擊重置使之恢復到初試的篩選條件
  • 需求六:用戶如果沒輸入內容點擊確認按鈕,就提示用戶要輸入篩選條件

思路分析

  • 對於需求一和需求二,咱們首先要搞兩個全屏幕彈框,而後在data中定義兩個數組,一個是放經常使用條件的數組,另一個是放不經常使用條件的數組,經常使用條件v-for到第一個彈框裏面,不經常使用條件v-for到第二個彈框裏面。數組裏面的每一項都要配置好對應內容,好比要有篩選字段名字,好比姓名、年齡什麼的。有了篩選篩選字段名字之後,還有有一個類型type,在html中咱們要寫三個類型的組件、好比input輸入框組件,select組件,時間選擇器組件。使用根據type類型經過v-show顯示對應字段,好比input的type爲1,select的type爲2,時間選擇器的type爲3。是哪一個type,就顯示哪一個組件。

對應兩個數組以下:後端

topData: [ // 配置經常使用的篩選項
        {
          wordTitle: "姓名",
          type: 1, // 1 爲input 2爲select 3爲DatePicker
          content: "", // content爲輸入框綁定的輸入數據
          options: [], // options爲全部的下拉框內容,能夠發請求拿到存進來,這裏是模擬
          optionArr: [], // optionArr爲選中的下拉框內容
          timeArr: [], // timeArr爲日期選擇區間
        },
        {
          wordTitle: "年齡",
          type: 1,
          content: "",
          options: [],
          optionArr: [],
          timeArr: [],
        },
        {
          wordTitle: "授課班級",
          type: 2,
          content: "",
          options: [ // 發請求獲取下拉框選項
            {
              id: 1,
              value: "一班",
            },
            {
              id: 2,
              value: "二班",
            },
            {
              id: 3,
              value: "三班",
            },
          ],
          optionArr: [],
          timeArr: [],
        },
        {
          wordTitle: "入職時間",
          type: 3, 
          content: "", 
          options: [], 
          optionArr: [], 
          timeArr: [], 
        },
      ],
      bottomData: [ // 配置不經常使用的篩選項
        {
          wordTitle: "工號",
          type: 1,
          content: "",
          options: [],
          optionArr: [],
          timeArr: [],
        },
        {
          wordTitle: "性別",
          type: 2,
          content: "",
          options: [
            {
              id: 1,
              value: "男",
            },
            {
              id: 2,
              value: "女",
            },
          ],
          optionArr: [],
          timeArr: [],
        },
      ],

對應html代碼以下:api

<div class="rightright">
                  <el-input
                    v-model.trim="item.content"
                    clearable
                    v-show="item.type == 1"
                    placeholder="請輸入"
                    size="small"
                    :popper-append-to-body="false"
                  ></el-input>
                  <el-select
                    v-model="item.optionArr"
                    v-show="item.type == 2"
                    multiple
                    placeholder="請選擇"
                  >
                    <el-option
                      v-for="whatItem in item.options"
                      :key="whatItem.id"
                      :label="whatItem.value"
                      :value="whatItem.id"
                      size="small"
                    >
                    </el-option>
                  </el-select>
                  <el-date-picker
                    v-model="item.timeArr"
                    v-show="item.type == 3"
                    type="daterange"
                    range-separator="至"
                    start-placeholder="開始日期"
                    end-placeholder="結束日期"
                    format="yyyy-MM-dd"
                    value-format="yyyy-MM-dd"
                  >
                  </el-date-picker>
                </div>
完整代碼在最後,你們先順着思路看哦
  • 對於需求三需求四,可描述爲,刪除上面的掉到下面。點擊下面的蹦到上面。因此對應操做就是把上面數組某一項追加到下面數組,而後把上面數組的這一項刪掉;把下面數組的某一項追加到上面數組,而後把這一行刪掉。(注意還有一個索引)對應代碼以下:
/* 點擊某一項的刪除小圖標,把這一項添加到bottomData數組中
       而後把這一項從topData數組中刪除掉(根據索引判別是哪一項) 
       最後刪除一個就把索引置爲初始索引 -1   */
    clickIcon(i) {
      this.bottomData.push(this.topData[i]);
      this.topData.splice(i, 1);
      this.whichIndex = -1;
    },
    // 點擊底部的項的時候,經過事件對象,看看點擊的是底部的哪一項
    // 而後把對應的那一項追加到topData中用於展現,同時把bottom數組
    // 中的哪一項進行刪除
    clickBottomItem(event) {
      this.bottomData.forEach((item, index) => {
        if (item.wordTitle == event.target.innerText) {
          this.topData.push(item);
          this.bottomData.splice(index, 1);
        }
      });
    },
  • 對於需求五需求六就簡單了,對應代碼以下,完整代碼註釋中已經寫好了

完整代碼

<template>
  <div id="app">
    <div class="filterBtn">
      <el-button type="primary" size="small" @click="filterMaskOne = true">
        數據篩選<i class="el-icon-s-operation el-icon--right"></i>
      </el-button>
      <transition name="fade">
        <div
          class="filterMaskOne"
          v-show="filterMaskOne"
          @click="filterMaskOne = false"
        >
          <div class="filterMaskOneContent" @click.stop>
            <div class="filterHeader">
              <span>數據篩選</span>
            </div>
            <div class="filterBody">
              <div class="outPrompt" v-show="topData.length == 0">
                暫無篩選條件,請添加篩選條件...
              </div>
              <div
                class="filterBodyCondition"
                v-for="(item, index) in topData"
                :key="index"
              >
                <div
                  class="leftleft"
                  @mouseenter="mouseEnterItem(index)"
                  @mouseleave="mouseLeaveItem(index)"
                >
                  <span
                    >{{ item.wordTitle }}:
                    <i
                      class="el-icon-error"
                      v-show="whichIndex == index"
                      @click="clickIcon(index)"
                    ></i>
                  </span>
                </div>
                <div class="rightright">
                  <el-input
                    v-model.trim="item.content"
                    clearable
                    v-show="item.type == 1"
                    placeholder="請輸入"
                    size="small"
                    :popper-append-to-body="false"
                  ></el-input>
                  <el-select
                    v-model="item.optionArr"
                    v-show="item.type == 2"
                    multiple
                    placeholder="請選擇"
                  >
                    <el-option
                      v-for="whatItem in item.options"
                      :key="whatItem.id"
                      :label="whatItem.value"
                      :value="whatItem.id"
                      size="small"
                    >
                    </el-option>
                  </el-select>
                  <el-date-picker
                    v-model="item.timeArr"
                    v-show="item.type == 3"
                    type="daterange"
                    range-separator="至"
                    start-placeholder="開始日期"
                    end-placeholder="結束日期"
                    format="yyyy-MM-dd"
                    value-format="yyyy-MM-dd"
                  >
                  </el-date-picker>
                </div>
              </div>
            </div>
            <div class="filterFooter">
              <div class="filterBtn">
                <el-button
                  type="text"
                  icon="el-icon-circle-plus-outline"
                  @click="filterMaskTwo = true"
                  >添加篩選條件</el-button
                >
                <transition name="fade">
                  <div
                    class="filterMaskTwo"
                    v-show="filterMaskTwo"
                    @click="filterMaskTwo = false"
                  >
                    <div class="filterMaskContentTwo" @click.stop>
                      <div class="innerPrompt" v-show="bottomData.length == 0">
                        暫無內容...
                      </div>
                      <div
                        class="contentTwoItem"
                        @click="clickBottomItem"
                        v-for="(item, index) in bottomData"
                        :key="index"
                      >
                        <div class="mingzi">
                          {{ item.wordTitle }}
                        </div>
                      </div>
                    </div>
                  </div>
                </transition>
              </div>
              <div class="resetAndConfirmBtns">
                <el-button size="small" @click="resetFilter">重置</el-button>
                <el-button type="primary" size="small" @click="confirmFilter"
                  >確認</el-button
                >
              </div>
            </div>
          </div>
        </div>
      </transition>
    </div>
  </div>
</template>

<script>
export default {
  name: "app",
  data() {
    return {
      filterMaskOne: false, // 分別用於控制兩個彈框的顯示與隱藏
      filterMaskTwo: false,
      whichIndex: -1, // 用於記錄點擊的索引
      apiFilterArr:[], //存儲用戶填寫的篩選內容
      topData: [ // 配置經常使用的篩選項
        {
          wordTitle: "姓名",
          type: 1, // 1 爲input 2爲select 3爲DatePicker
          content: "", // content爲輸入框綁定的輸入數據
          options: [], // options爲全部的下拉框內容
          optionArr: [], // optionArr爲選中的下拉框內容
          timeArr: [], // timeArr爲日期選擇區間
        },
        {
          wordTitle: "年齡",
          type: 1,
          content: "",
          options: [],
          optionArr: [],
          timeArr: [],
        },
        {
          wordTitle: "授課班級",
          type: 2,
          content: "",
          options: [ // 發請求獲取下拉框選項
            {
              id: 1,
              value: "一班",
            },
            {
              id: 2,
              value: "二班",
            },
            {
              id: 3,
              value: "三班",
            },
          ],
          optionArr: [],
          timeArr: [],
        },
        {
          wordTitle: "入職時間",
          type: 3, 
          content: "", 
          options: [], 
          optionArr: [], 
          timeArr: [], 
        },
      ],
      bottomData: [ // 配置不經常使用的篩選項
        {
          wordTitle: "工號",
          type: 1,
          content: "",
          options: [],
          optionArr: [],
          timeArr: [],
        },
        {
          wordTitle: "性別",
          type: 2,
          content: "",
          options: [
            {
              id: 1,
              value: "男",
            },
            {
              id: 2,
              value: "女",
            },
          ],
          optionArr: [],
          timeArr: [],
        },
      ],
    };
  },
  mounted() {
    // 在初始化加載的時候,咱們就把咱們配置的經常使用和不經常使用的篩選項保存一份
    // 當用戶點擊重置按鈕的時候,再取出來使其恢復到最初的篩選條件狀態
    sessionStorage.setItem("topData",JSON.stringify(this.topData))
    sessionStorage.setItem("bottomData",JSON.stringify(this.bottomData))
  },
  methods: {
    //鼠標移入顯示刪除小圖標
    mouseEnterItem(index) {
      this.whichIndex = index;
    },
    // 鼠標離開將索引回覆到默認-1
    mouseLeaveItem() {
      this.whichIndex = -1;
    },
    /* 點擊某一項的刪除小圖標,把這一項添加到bottomData數組中
       而後把這一項從topData數組中刪除掉(根據索引判別是哪一項) 
       最後刪除一個就把索引置爲初始索引 -1   */
    clickIcon(i) {
      this.bottomData.push(this.topData[i]);
      this.topData.splice(i, 1);
      this.whichIndex = -1;
    },
    // 點擊底部的項的時候,經過事件對象,看看點擊的是底部的哪一項
    // 而後把對應的那一項追加到topData中用於展現,同時把bottom數組
    // 中的哪一項進行刪除
    clickBottomItem(event) {
      this.bottomData.forEach((item, index) => {
        if (item.wordTitle == event.target.innerText) {
          this.topData.push(item);
          this.bottomData.splice(index, 1);
        }
      });
    },
    // 點擊確認篩選
    async confirmFilter() {
      // 若是全部的輸入框的content內容爲空,且選中的下拉框數組爲空,且時間選擇器選中的數組爲空
      // 就說明用戶沒有輸入內容,那麼咱們就提示用戶要輸入內容之後再進行篩選
      let isEmpty = this.topData.every((item)=>{
        return (item.content == "") && (item.optionArr.length == 0) && (item.timeArr.length == 0)
      })
      if(isEmpty == true){
         this.$alert('請輸入內容之後再進行篩選', '篩選提示', {
          confirmButtonText: '肯定'
        });
      }else{
        // 收集參數發篩選請求,這裏要分類型,把不爲空的既有用戶輸入內容的
        // 存到存到數據篩選的數組中去,而後發請求給後端。
        this.topData.forEach((item)=>{
          if(item.type == 1){
            if(item.content != ""){
              let filterItem = {
                field:item.wordTitle,
                value:item.content
              }
              this.apiFilterArr.push(filterItem)
            }
          }else if(item.type == 2){
            if(item.optionArr.length > 0){
              let filterItem = {
                field:item.wordTitle,
                value:item.optionArr
              }
              this.apiFilterArr.push(filterItem)
            }
          }else if(item.type == 3){
            if(item.timeArr.length > 0){
              let filterItem = {
                field:item.wordTitle,
                value:item.timeArr
              }
              this.apiFilterArr.push(filterItem)
            }
          } 
        })
        // 把篩選的內容放到一個數組裏面,傳遞給後端(固然不必定把參數放到數組裏面)
        // 具體以怎樣的形式傳遞給後端,能夠具體商量
        console.log("帶着篩選內容發請求",this.apiFilterArr);
      }
    },
    // 重置時,再把最初的配置篩選項取出來賦給對應的兩個數組
    resetFilter() {
      this.topData = JSON.parse(sessionStorage.getItem("topData"))
      this.bottomData = JSON.parse(sessionStorage.getItem("bottomData"))
    },
  },
};
</script>
<style lang="less" scoped>
.filterBtn {
  width: 114px;
  height: 40px;
  .filterMaskOne {
    top: 0;
    left: 0;
    position: fixed;
    width: 100%;
    height: 100%;
    z-index: 999;
    background-color: rgba(0, 0, 0, 0.3);
    .filterMaskOneContent {
      position: absolute;
      top: 152px;
      right: 38px;
      width: 344px;
      height: 371px;
      background-color: #fff;
      box-shadow: 0px 0px 4px 3px rgba(194, 194, 194, 0.25);
      border-radius: 4px;
      .filterHeader {
        width: 344px;
        height: 48px;
        border-bottom: 1px solid #e9e9e9;
        span {
          display: inline-block;
          font-weight: 600;
          font-size: 16px;
          margin-left: 24px;
          margin-top: 16px;
        }
      }
      .filterBody {
        width: 344px;
        height: 275px;
        overflow-y: auto;
        overflow-x: hidden;
        box-sizing: border-box;
        padding: 12px 24px 0 24px;
        .outPrompt {
          color: #666;
        }
        .filterBodyCondition {
          width: 100%;
          min-height: 40px;
          display: flex;
          margin-bottom: 14px;
          .leftleft {
            width: 88px;
            height: 40px;
            display: flex;
            align-items: center;
            margin-right: 20px;
            span {
              position: relative;
              font-size: 14px;
              color: #333;
              i {
                color: #666;
                right: -8px;
                top: -8px;
                position: absolute;
                font-size: 15px;
                cursor: pointer;
              }
              i:hover {
                color: #5f95f7;
              }
            }
          }
          .rightright {
            width: calc(100% - 70px);
            height: 100%;
            /deep/ input::placeholder {
              color: rgba(0, 0, 0, 0.25);
              font-size: 13px;
            }
            /deep/ .el-input__inner {
              height: 40px;
              line-height: 40px;
            }
            /deep/ .el-select {
              .el-input--suffix {
                /deep/ input::placeholder {
                  color: rgba(0, 0, 0, 0.25);
                  font-size: 13px;
                }
                .el-input__inner {
                  border: none;
                }
                .el-input__inner:hover {
                  background: rgba(95, 149, 247, 0.05);
                }
              }
            }
            .el-date-editor {
              width: 100%;
              font-size: 12px;
            }
            .el-range-editor.el-input__inner {
              padding-left: 2px;
              padding-right: 0;
            }
            /deep/.el-range-input {
              font-size: 13px !important;
            }
            /deep/ .el-range-separator {
              padding: 0 !important;
              font-size: 12px !important;
              width: 8% !important;
              margin: 0;
            }
            /deep/ .el-range__close-icon {
              width: 16px;
            }
          }
        }
      }
      .filterFooter {
        width: 344px;
        height: 48px;
        display: flex;
        justify-content: space-between;
        align-items: center;
        box-sizing: border-box;
        padding-left: 24px;
        padding-right: 12px;
        border-top: 1px solid #e9e9e9;
        .filterBtn {
          .filterMaskTwo {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.3);
            z-index: 1000;
            .filterMaskContentTwo {
              width: 240px;
              height: 320px;
              background: #ffffff;
              box-shadow: 0px 0px 4px 3px rgba(194, 194, 194, 0.25);
              border-radius: 4px;
              position: absolute;
              top: 360px;
              right: 180px;
              overflow-y: auto;
              box-sizing: border-box;
              padding: 12px 0 18px 0;
              overflow-x: hidden;
              .innerPrompt {
                color: #666;
                width: 100%;
                padding-left: 20px;
                margin-top: 12px;
              }
              .contentTwoItem {
                width: 100%;
                height: 36px;
                line-height: 36px;
                font-size: 14px;
                color: #333333;
                cursor: pointer;
                .mingzi {
                  width: 100%;
                  height: 36px;
                  box-sizing: border-box;
                  padding-left: 18px;
                }
              }
              .contentTwoItem:hover {
                background: rgba(95, 149, 247, 0.05);
              }
            }
          }
        }
      }
    }
  }
}
// 控制淡入淡出效果
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s;
}
.fade-enter,
.fade-leave-to {
  opacity: 0;
}
</style>

總結

這裏面須要注意的就是鼠標移入移出顯示對應的刪除小圖標。思路大體就這樣,敲代碼不易,我們共同努力。數組

相關文章
相關標籤/搜索