Vuex(Nuxt에서)를 사용하여 고급 검색 및 필터 구축

50761 단어 vuetailwindcsstutorial

우리는 무엇을 짓고 있습니까?


여과기!우리는 우리의 잠재 고객을 검색하여 상태에 따라 그들을 선별하고 주문서를 변경할 수 있기를 희망한다.그러나 우리도 모든 필터가 함께 일하고 연결되기를 바란다.

시작하다


따라서 가능한 한 간단명료하게 하기 위해 나는 새로운 Nuxt 프로젝트를 세우는 과정을 겪지 않을 것이다.이것은plainoldVuex에도 적용된다.
다음은 몇 가지 가정입니다.
  • 프로젝트 설정이 이미 있습니다
  • 특정 유형의 데이터를 필터링해야 합니다
  • Vuex와 매장 관리에 대한 기초 지식
  • 프로젝트 구조


    예를 들면, 나는 내가 줄곧 하고 있는 프로젝트를 사용하고 있다. (내가 그것을 언급한 적이 있습니까?👉 https://github.com/messerli90/jobhuntbuddy ).
    우리는 잠재적 고객이라고 불리는 빈자리가 많습니다. 추적하고 싶지만 명단이 길어지고 있습니다.
  • 회사 이름 및 직무별 검색
  • 특정 상태의 잠재 고객만 표시
  • 정렬 기준: 작성 날짜, 회사 이름, 직무 또는 상태
  • 필터가 변경될 때마다 API를 호출하지 말고 목록의 모든 변경 사항을 로컬로 유지해야 합니다
  • 시작합시다.

    Vuex 스토어 설정


    잠재 고객과 현재 잠재 고객 목록이 있는 상점이 있습니다.우리는 우리의 상태에 새로운 filteredLeads 목록과 초기 filter 대상을 추가하고 싶습니다.
    // ~/store/leads.js
    export const state = () => ({
      leads: [],
      filteredLeads: [],
      lead: {},
      filter: {
        search: '',
        status: 'all',
        order: 'createdAt'
      }
    })
    
    API에서 반환된 단서의 초기 목록이 변하지 않기를 원합니다. 따라서 필터를 지울 때 모든 단서를 다시 얻을 수 있습니다.

    행동


    필터를 변경할 때 Vue 구성 요소가 호출할 수 있는 작업을 정의합니다.
    나는 이 모든 방법의 앞에 "filter"를 넣었기 때문에, 나는 그것들이 모두 같은 종류에 속한다는 것을 안다.filterStatus, filterSearch, filterOrder에 대해 우리는 먼저 돌연변이를 제출하여 우리가 방금 만든 필터 대상에 저장합니다.이렇게 하면 우리가 filterLeads 방법을 호출할 때 단일한 실제 출처를 유지할 수 있다.
    모든 필터를 유지하고 싶기 때문에, 어떤 값을 바꾸든지 간에, 최종 filterLeads 작업은 먼저 목록을 우리가 원하는 범위로 축소한 다음에 새 목록을 정렬합니다.
    // ~/store/leads.js
    export const actions = {
    // ...
      async filterOrder ({ commit }, order) {
        await commit('setOrder', order)
        await commit('orderLeads')
      },
      async filterStatus ({ commit, dispatch }, status) {
        await commit('setFilterStatus', status)
        dispatch('filterLeads')
      },
      async filterSearch ({ commit, dispatch }, search) {
        await commit('setFilterSearch', search)
        dispatch('filterLeads')
      },
      async filterLeads ({ commit }) {
        await commit('filterLeads')
        await commit('orderLeads')
      },
      // ...
    }
    

    돌변하다


    이제 우리가 방금 진행한 돌변을 살펴보자.setFilteredLeads 새 필터를 적용한 후에 호출되므로 Vue 구성 요소는 초기 목록을 잃어버리지 않고 원하는 단서만 표시합니다.setFilterStatus, setFilterSearchsetOrder 객체의 해당 값만 변경합니다.filter 먼저 모든 단서의 로컬 사본을 제작한다.모든 Lead를 포함하도록 filteredLeads 목록을 다시 설정합니다.마지막으로, 우리는 우리의 필터 방법을 호출하여 이 새 목록을 상태에 저장합니다.
    이와 유사하게, filterLeads 이 새로운 Filtered Leads 목록을 가져와 정렬 방법에 전달하고 새 목록을 저장합니다.
    // ~/store/leads.js
    import * as Filters from '~/helpers/filters'
    
    export const mutations = {
      // ...
      setFilteredLeads (state, leads) { state.filteredLeads = leads },
    
      setFilterStatus (state, status) { state.filter.status = status },
      setFilterSearch (state, search) { state.filter.search = search },
      setOrder (state, order) { state.filter.order = order },
    
      filterLeads (state) {
        const leads = [...state.leads]
        state.filteredLeads = leads
        state.filteredLeads = Filters.filterLeads(state.filter, leads)
      },
      orderLeads (state) {
        const leads = [...state.filteredLeads]
        state.filteredLeads = Filters.orderLeads(state.filter.order, leads)
      }
      // ...
    }
    
    이것이 바로 우리가 Vuex 상점에서 변화해야 할 모든 것이다.우리의 필터 조수 방법을 계속 토론합시다

    보조 객체 필터링


    여기가 마술이 일어나는 곳이야.마지막 단계에서 orderLeadsFilter.filterLeads(state.filter, leads)라는 돌연변이를 보았습니다. 이것들을 만들고 정렬합시다!
    면책 성명: 이것은 가능하지만, 나는 결코 자바스크립트 록 스타가 아니다. 만약 당신이 이 점을 어떻게 최적화하는지에 대한 힌트가 있다면, 나는 당신을 듣고 매우 기쁘다.
    요점을 요약하여 다시 말하다.
    기억Filter.orderLeads(state.filter.order, leads) 객체의 모양:
    filter: {
      search: '',
      status: 'all',
      order: 'createdAt'
    }
    

    필터 지시선(필터, 지시선)


    // ~/helpers/filters.js
    export function filterLeads (filter, leads) {
      let filteredList = [...leads]
    
      // Filter status
      if (filter.status !== 'all') {
        const filtered = filteredList.filter(lead => lead.status === filter.status)
        filteredList = filtered
      }
    
      // Search
      if (filter.search !== '') {
        const searchList = []
        const searchTerm = filter.search.toLowerCase()
        for (let i = 0; i < filteredList.length; i++) {
          if (
            (filteredList[i].companyName !== null && filteredList[i].companyName.toLowerCase().includes(searchTerm)) ||
            (filteredList[i].jobTitle !== null && filteredList[i].jobTitle.toLowerCase().includes(searchTerm))
          ) {
            searchList.push(filteredList[i])
          }
        }
        filteredList = searchList
      }
    
      return filteredList
    }
    
    DN에서 filter에 대한 추가 정보: String.prototype.includes()
    검색 순환이 텍스트의 일치를 위해 모든 단서를 훑어보기 때문에, 불필요한 교체를 피하기 위해 마지막으로 이렇게 할 것입니다.우선 목록을 필터링하여 상태 필터링과 일치하는 잠재적 고객을 찾습니다.
    현재 우리는 이 비교적 짧은 목록을 가지고 있으며, 우리는 그것을 검색 논리로 전달할 수 있다.검색 필드가 비어 있으면 전체 단계를 건너뛰어야 합니다.호출하기 전에 filteredLeads 목록을 초기 leads 목록으로 재설정합니다.그렇지 않으면, 검색어와 필터링할 속성에 includes() 을 사용하십시오. 왜냐하면 자바스크립트는 "A"와 "A"의 처리 방식이 다르기 때문입니다. 그렇지 않으면 일치하지 않습니다.어떤 경기든 우리의 새 .toLowerCase() 로 밀려나고 우리의 searchList 로 바뀔 것이다.

    orderLeads(주문, leads)


    // ~/helpers/filters.js
    import moment from 'moment'
    export function orderLeads (order, leads) {
      const orderedList = [...leads]
    
      if (order === 'createdAt') {
        orderedList.sort(function (a, b) {
          const unixA = moment(a.createdAt).unix()
          const unixB = moment(b.createdAt).unix()
          return unixA < unixB ? -1 : 1
        })
      } else {
        orderedList.sort(function (a, b) {
          const nameA = a[order] ? a[order].toLowerCase() : 'zzz'
          const nameB = b[order] ? b[order].toLowerCase() : 'zzz'
          return nameA < nameB ? -1 : 1
        })
      }
    
      return orderedList
    }
    
    DN에서 filteredList에 대한 추가 정보: Array.prototype.sort()
    이것은 우리의 주문 방법입니다.현재 우리는 회사 이름, 직무, 상태, 창설 시간에만 따라 주문하기 때문에, 우리는 날짜와 문자열 두 가지 유형의 주문 함수만 필요로 한다.
    따라서 순서가'created At'이고 sort()이 시간 스탬프라는 것을 알고 있다면, 비교를 위해 유닉스 시간 스탬프로 변환합니다.나는 여기서 사용한다Moment.js. 이것은 좀 지나친 것 같다.
    그렇지 않으면, 우리의 다른 정렬 방법은 모두 문자열이기 때문에, 우리는 그것들을 똑같이 처리할 수 있다. (우리의 순서와 대상 키가 같다고 가정하자!)잠재 고객이 특정한 값 (즉job Title) 이 없으면'zzz'로 기본값으로 설정하기 때문에 목록의 끝까지 밀어내기로 했습니다.
    그런 다음 주문 목록으로 돌아갑니다(필터링됨)

    표현층


    현재 저희 Vuex 상점은 모든 기초 작업을 마쳤습니다. 이 모든 것을 결합한 Vue 구성 요소에 대해 계속 토론합시다.

    납 여과기


    우리의 필터 구성 요소
    // ~/components/leads/leadFilter.vue
    <template>
      <div>
        <div class="w-full mb-2">
          <input
            :value="search"
            type="search"
            class="h-12 p-4 mb-1 w-full bg-white border-2 border-gray-300 rounded-full"
            placeholder="Search company name or job title"
            aria-label="Search by company name or job title"
            @input="handleSearch"
          >
        </div>
        <div class="mb-4 w-full">
          <div class="flex flex-wrap items-center justify-center md:justify-between w-full text-gray-800">
            <button
              class="bg-gray-400 rounded-full px-3 py-2 font-medium text-center text-sm m-1 hover:bg-gray-500"
              :class="{ 'bg-indigo-700 text-white hover:bg-indigo-800' : status === 'all' }"
              @click="handleStatusFilter('all')"
            >
              All Leads
            </button>
            <button
              class="bg-gray-400 rounded-full px-3 py-2 font-medium text-center text-sm m-1 hover:bg-gray-500"
              :class="{ 'bg-yellow-500 text-white hover:bg-yellow-600' : status === 'prospect' }"
              @click="handleStatusFilter('prospect')"
            >
              Prospects
            </button>
            <button
              class="bg-gray-400 rounded-full px-3 py-2 font-medium text-center text-sm m-1 hover:bg-gray-500"
              :class="{ 'bg-green-500 text-white hover:bg-green-600' : status === 'application-sent' }"
              @click="handleStatusFilter('application-sent')"
            >
              Application Sent
            </button>
            <button
              class="bg-gray-400 rounded-full px-3 py-2 font-medium text-center text-sm m-1 hover:bg-gray-500"
              :class="{ 'bg-blue-500 text-white hover:bg-blue-600' : status === 'interview-set' }"
              @click="handleStatusFilter('interview-set')"
            >
              Interview Set
            </button>
            <button
              class="bg-gray-400 rounded-full px-3 py-2 font-medium text-center text-sm m-1 hover:bg-gray-500"
              :class="{ 'bg-red-500 text-white hover:bg-red-600' : status === 'rejected' }"
              @click="handleStatusFilter('rejected')"
            >
              Rejected
            </button>
          </div>
        </div>
        <div class="flex justify-start">
          <div class="relative mb-3 pr-8">
            <p
              v-click-outside="closeOrderDropDown"
              class="text-gray-700 cursor-pointer flex items-center"
              @click="orderOpen = !orderOpen"
            >
              <fa :icon="['fas', 'sort-amount-down']" class="h-4 mx-1" />
              <span class="mr-1">Order By</span>
              <span v-show="orderChanged" class="font-semibold">{{ orderText }}</span>
            </p>
            <ul v-show="orderOpen" class="bg-white absolute z-20 px-3 py-2 mt-1 rounded shadow-lg text-gray-700 min-w-full">
              <li
                class="cursor-pointer pb-1 hover:text-indigo-600"
                :class="{ 'text-indigo-600 font-semibold' : order === 'createdAt' }"
                @click="handleFilterOrder('createdAt')"
              >
                Created Date
              </li>
              <li
                class="cursor-pointer pb-1 hover:text-indigo-600"
                :class="{ 'text-indigo-600 font-semibold' : order === 'companyName' }"
                @click="handleFilterOrder('companyName')"
              >
                Company Name
              </li>
              <li
                class="cursor-pointer hover:text-indigo-600"
                :class="{ 'text-indigo-600 font-semibold' : order === 'jobTitle' }"
                @click="handleFilterOrder('jobTitle')"
              >
                Job Title
              </li>
              <li
                class="cursor-pointer hover:text-indigo-600"
                :class="{ 'text-indigo-600 font-semibold' : order === 'status' }"
                @click="handleFilterOrder('status')"
              >
                Status
              </li>
            </ul>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    import { debounce } from '~/helpers/index'
    export default {
      data () {
        return {
          orderOpen: false,
          orderChanged: false
        }
      },
      computed: {
        search () {
          return this.$store.state.leads.filter.search
        },
        status () {
          return this.$store.state.leads.filter.status
        },
        order () {
          return this.$store.state.leads.filter.order
        },
        orderText () {
          switch (this.order) {
            case 'companyName':
              return 'Company Name'
            case 'jobTitle':
              return 'Job Title'
            case 'status':
              return 'Status'
            default:
              return 'Created Date'
          }
        }
      },
      methods: {
        handleStatusFilter (status) {
          this.$store.dispatch('leads/filterStatus', status)
        },
        handleSearch: debounce(function (e) {
          this.$store.dispatch('leads/filterSearch', e.target.value)
        }, 500),
        handleFilterOrder (orderBy) {
          this.orderOpen = false
          this.orderChanged = true
          this.$store.dispatch('leads/filterOrder', orderBy)
        },
        closeOrderDropDown (e) {
          this.orderOpen = false
        }
      }
    }
    </script>
    
    나는 이미 네가 말하는 것을 들었다. "이것은 순풍 CSS가 많은데..."나 는 알지만, 우리 는 자력 갱생 하고 있다😉. 우리가 관심을 가지는 것이 무엇인지 봅시다.
    컴퓨터 () 에서, 우리는 우리가 관심을 가지는 세 개의 필터의 현재 상태: 검색, 상태, 순서를 얻는다.그리고 우리의 주문서를 읽을 수 있게 합니다. 왜냐하면 우리는 그들로 하여금 == 키를 앞서게 하기 때문입니다.
    우리의 방법 () 은 모두 매우 직접적이며, 우리가 이전에 만든 동작만 스케줄링합니다.이 모든 것은 수동적이며 Vuex가 처리합니다!

    잠재 고객 목록


    이것은 우리의 색인 페이지입니다. 우리의 모든 단서를 열거하였습니다
    // ~/pages/leads/index.vue
    <template>
      <div id="lead-index-wrapper" class="container pt-4 px-2 w-full md:w-2/3 lg:w-1/2 xl:w-1/3">
        <div>
          <div v-if="leads.length">
            <LeadFilter />
            <nuxt-link v-for="lead in filteredLeads" :key="lead.id" :to="'/leads/' + lead.id">
              <IndexCard :lead="lead" />
            </nuxt-link>
            <NoLeadsCard v-if="!filteredLeads.length" />
          </div>
          <OnboardingCard v-if="!leads.length" />
        </div>
      </div>
    </template>
    
    <script>
    import { mapGetters } from 'vuex'
    import LeadFilter from '~/components/leads/leadFilter'
    import IndexCard from '~/components/leads/IndexCard'
    import OnboardingCard from '~/components/leads/onboardingCard'
    import NoLeadsCard from '~/components/leads/noLeadsCard'
    export default {
      middleware: 'authenticated',
      components: { IndexCard, NoLeadsCard, OnboardingCard, LeadFilter },
      computed: {
        ...mapGetters({
          'leads': 'leads/getLeads',
          'filteredLeads': 'leads/getFilteredLeads',
          'lead': 'leads/getLead'
        })
      },
      async fetch ({ store }) {
        await store.dispatch('leads/fetchAllLeads')
      },
      mounted () {
        if (!this.leads.length) {
          this.$store.dispatch('leads/fetchAllLeads')
        }
      }
    }
    </script>
    
    여기에서 모든 내용이 이 안내서와 관련이 있는 것은 아니지만, 전단에서 무슨 일이 일어났는지 봅시다.
    보시다시피 지시선의 존재 여부를 검사하는 것 외에 대부분의 구성 요소는 최초lead.createdAt와 같은 filteredLeads에만 관심을 가지고 있습니다.
    LeadFilter 구성 요소를 가져왔습니다. 이 구성 요소는 매우 어리석고 Vuex 저장소의 상태에만 관심을 갖습니다.

    끝내다


    그렇다. 돌연변이를 제출하고 다른 동작을 스케줄링하는 데 동작을 사용하는 방법을 보았다.우리는 leads와javascript에서 사용sorting()을 토론했다.주로, 나는 모든 방법에 여러 개의 매개 변수가 전달되는 것을 방지하고 단일한 진리의 출처를 유지하는 상태를 어떻게 사용하는지 보여주고 싶다.
    저는 Nuxt와 합작하고 Vuex를 사용하여 국가 관리를 깊이 있게 이해하는 것을 매우 좋아합니다.지난 몇 달 동안 나는 많은 것을 배웠고, 나는 보답하고 싶다.

    잡헌트 버디 회사.


    JobHuntBuddy.co
    나는 내가 현재 하고 있는 프로젝트를 예로 하나 썼다.현재, 나는 새로운 일자리를 찾고 있기 때문에, 이 프로젝트는 나를 도와 일자리를 구하는 것을 관리하고, 잠재 고용주에게 소스 코드 예시를 제공하는데, 정말 일거양득이다.
    ✌️ 즐거운 인코딩!
    트위터에서 팔로우 해주세요.

    좋은 웹페이지 즐겨찾기