zhengnaiwen_citu 5 ماه پیش
والد
کامیت
d4984a119f
2فایلهای تغییر یافته به همراه337 افزوده شده و 46 حذف شده
  1. 97 46
      components/FilterList/index.vue
  2. 240 0
      components/FilterList/mFilter.vue

+ 97 - 46
components/FilterList/index.vue

@@ -1,31 +1,38 @@
 <template>
   <view class="labelColor itemBox" style="height: 45px;">
-    <view class="item" v-for="(item, index) in filterList" :key="item[props.idValue]">
-      <!-- v-slot:default="{ data, error }" -->
-      <uni-data-picker
-        ref="pickerRef"
-        v-model="item.value"
-        v-slot:default="{ error }"
-        :localdata="item.array"
-        :popup-title="'请选择' + item[labelValue]" 
-        :map="item.map || { text: 'label', value: 'value' }"
-        @change="e => handleClick(e, item)"
+    <view class="item" v-for="(item) in filterList" :key="item[props.idValue]">
+      <m-filter
+        class="itemFilter"
+        :items="item.array"
+        :label="item.label"
+        :multiple="item.multiple"
+        :item-label="item.itemLabel"
+        :item-value="item.itemValue"
+        @change="($event, $name) => handleClick($event, $name, item)"
       >
-        <view v-if="error" class="error">
-          <text>{{error}}</text>
-        </view>
-        <view v-else class="d-flex">
-          <view
-            class="selectText font-weight-bold"
-            :style="`color: ${item.value? '#008978' : ''}`"
-          >
-            {{ item[labelValue] }}
-            <!-- {{ item.text || item[labelValue] }} -->
+        <view
+          :class="(item.multiple && item.value?.length) || (!item.multiple && item.value) ? 'active' : ''"
+          class="name"
+        >
+          <view class="over">
+            {{ item.name ?? item.label }}
           </view>
-          <uni-icons v-if="!item.value" type="icon-arrow-sortdown-smal" custom-prefix="iconfont" color="#999"/>
+          <template v-if="item.multiple && item.value?.length > 1">
+            <uni-icons
+              class="point"
+              type="smallcircle-filled"
+              color=""
+              size="6"
+            />
+            {{item.value.length}}
+          </template>
+          <uni-icons
+            type="down"
+            color=""
+            size="12"
+          />
         </view>
-      </uni-data-picker>
-      <uni-icons v-if="item.value" class="marginT2" type="closeempty" size="16" color="#999" @click="handleClear(item, index)"></uni-icons>
+      </m-filter>
     </view>
   </view>
 </template>
@@ -33,43 +40,60 @@
 <script setup>
 import { ref, watch } from 'vue'
 import { getDict } from '@/hooks/useDictionaries'
+import MFilter from './mFilter.vue'
 
 const emit = defineEmits(['change'])
 const props = defineProps({
   list: { type: Array, default: () => [] },
   idValue: { type: String, default: 'id' },
   labelValue: { type: String, default: 'label' },
-  selectIdValue: { type: String, default: 'id' },
-  selectLabelValue: { type: String, default: 'label' },
-  useApiData: { type: Boolean, default: true },
+  // selectIdValue: { type: String, default: 'id' },
+  // selectLabelValue: { type: String, default: 'label' },
+  // useApiData: { type: Boolean, default: true },
   lazy: { type: Boolean, default: false },
 })
 
-const pickerRef = ref()
-const handleClick = (e, item) => {
-  const obj = e.detail.value?.length ? e.detail.value[e.detail.value.length-1] : {}
-  const obj0 = e.detail.value?.length ? e.detail.value[0] : {}
-  //
-  let val = obj.value
-  item.value = obj.value
-  item.text = obj.text || ''
-  if (typeof val === 'string' && val.includes('unlimited') && obj0) {
-    val = Number.isInteger(obj0.id) ? Number(val.split('unlimited')[0]) : val.split('unlimited')[0]
+const popup = ref()
+
+const handleOpen = (index) => {
+  popup.value[index].open('bottom')
+}
+const handleChangeItem = (i, item) => {
+  if (i === item.active) {
+    return
   }
-  emit('change', item.key, val)
+  item.active = i
 }
 
-const handleClear = (item, index) => {
-  item.value = null
-  item.text = null
-  // pickerRef.value[index]?.clear()
-  emit('change', item.key, item.value)
+
+const handleClick = (e, name, item) => {
+  item.value = e
+  item.name = name ?? item.label
+  // const obj = e.detail.value?.length ? e.detail.value[e.detail.value.length-1] : {}
+  // const obj0 = e.detail.value?.length ? e.detail.value[0] : {}
+  // //
+  // let val = obj.value
+  // item.value = obj.value
+  // item.text = obj.text || ''
+  // if (typeof val === 'string' && val.includes('unlimited') && obj0) {
+  //   val = Number.isInteger(obj0.id) ? Number(val.split('unlimited')[0]) : val.split('unlimited')[0]
+  // }
+  emit('change', item.key, e)
 }
 
+// const handleClear = (item, index) => {
+//   item.value = e.multiple ? [] : null
+//   item.text = null
+//   emit('change', item.key, item.value)
+// }
+
 // 获取字典数据
 const getData = (e) => {
   getDict(e.dictType, e.map ? {} : null, e.map ? e.dictType : 'dict').then(({ data }) => {
     e.array = data.data
+    e.itemLabel = e.map?.text
+    e.itemValue = e.map?.value
+    e.value = []
   })
 }
 
@@ -83,9 +107,15 @@ const setItemSelectData = () => {
 const filterList = ref([])
 watch(() => props.list, 
   (newVal) => {
-    filterList.value = newVal ? [...newVal] : []
-    filterList.value.forEach(e => e.value = null)
-    if (filterList.value.length && !props.lazy) setItemSelectData()
+    if (!newVal) {
+      filterList.value = []
+      return
+    }
+    filterList.value = newVal
+    
+    if (!props.lazy) {
+      setItemSelectData()
+    }
   },
   { immediate: true }
 )
@@ -94,14 +124,35 @@ watch(() => props.list,
 
 <style scoped lang="scss">
 .labelColor { color: #5c5c5c; }
-.marginT2 { margin-top: 2px; }
 .itemBox {
   display: flex;
   justify-content: space-between;
   .item {
     display: flex;
     align-items: center;
+    justify-content: center;
+    flex: 1;
+    width: 0;
     font-size: 14px;
+    .itemFilter {
+      width: 100%;
+    }
+    .name {
+      width: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      .over {
+        overflow: hidden;
+        white-space: nowrap;
+      }
+      .point {
+        margin: 0 5px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+      }
+    }
   }
 }
 </style>

+ 240 - 0
components/FilterList/mFilter.vue

@@ -0,0 +1,240 @@
+<template>
+  <view>
+    <view @tap="handleOpen">
+      <slot></slot>
+    </view>
+    <uni-popup ref="popup">
+      <view class="popup-content">
+        <view class="popup-content-label">
+          <view class="popup-content-label-item active">{{ label }}</view>
+        </view>
+        <view class="popup-content-body">
+          <view v-for="(arr, i) in showList" :key="i" class="popup-content-body-list">
+            <!-- <view class="popup-content-body-list-label" :class="active === i ? 'active' : ''">{{ arr.label }}</view> -->
+            <view class="popup-content-body-list-items">
+              <view class="content">
+                <view
+                  v-for="_arr in arr.data"
+                  :key="_arr[props.itemValue]"
+                  class="py-1 dFlex"
+                  :class="arr.choose === _arr[props.itemValue] || (Array.isArray(arr.choose) && arr.choose.includes(_arr[props.itemValue]))? 'active' : ''"
+                  @tap="handleNext(_arr, i)"
+                >
+                  {{ _arr[props.itemLabel] }}
+                  <uni-icons
+                    v-if="Array.isArray(arr.choose) && arr.choose.includes(_arr[props.itemValue])"
+                    type="checkmarkempty"
+                    color="#008978"
+                    size="16"
+                  />
+                </view>
+              </view>
+            </view>
+          </view>
+        </view>
+        <view class="popup-content-footer">
+          <button class="btn cancel" @tap="reset">重置</button>
+          <button class="btn submit" @tap="submit">确定</button>
+        </view>
+      </view>
+    </uni-popup>
+  </view>
+</template>
+
+<script setup>
+import { ref, watch } from 'vue'
+const emit = defineEmits(['change'])
+const props = defineProps({
+  items: {  // 树结构
+    type: Array,
+    default: () => []
+  },
+  label: {
+    type: String,
+    default: ''
+  },
+  multiple: {
+    type: Boolean,
+    default: false
+  },
+  itemLabel: {
+    type: String,
+    default: 'label'
+  },
+  itemValue: {
+    type: String,
+    default: 'value'
+  }
+})
+
+const popup = ref()
+// const active = ref(0)
+const showList = ref([])
+
+watch(
+  () => props.items,
+  (val) => {
+    // 初始化赋值
+    showList.value = [{
+      choose: -1,
+      data: val
+    }]
+  },
+  {
+    immediate: true,
+    deep: true
+  }
+)
+
+
+const handleOpen = () => {
+  popup.value.open('bottom')
+}
+
+const handleNext = (item, index) => {
+  const _i = index + 1
+  // active.value = _i
+  showList.value.splice(_i, showList.value.length - _i)
+  showList.value[index].label = item[props.itemLabel]
+  if (item.children && item.children.length) {
+    showList.value[index].choose = item[props.itemValue]
+    showList.value.push({
+      choose: -1,
+      data: item.children
+    })
+    return
+  }
+  if (!props.multiple) {
+    showList.value[index].choose = item[props.itemValue]
+    return
+  }
+  if (!Array.isArray(showList.value[index].choose)) {
+    showList.value[index].choose = [item[props.itemValue]]
+    return
+  }
+  if (!showList.value[index].choose.includes(item[props.itemValue])) {
+    showList.value[index].choose.push(item[props.itemValue])
+    return
+  }
+  const _index = showList.value[index].choose.indexOf(item[props.itemValue])
+  showList.value[index].choose.splice(_index, 1)
+}
+
+const reset = () => {
+  showList.value = [{
+    choose: -1,
+    data: props.items
+  }]
+  emit('change', props.multiple ? [] : null)
+  popup.value.close()
+}
+const submit = () => {
+  const item = showList.value[showList.value.length - 1]
+  if (item.choose === -1) {
+    const _item = showList.value[showList.value.length - 2]
+    if (!item) {
+      emit('change', props.multiple ? [] : null)
+      popup.value.close()
+      return
+    }
+    emit('change', props.multiple ? [_item.choose] : _item.choose, props.multiple ? showList.value[0].label : _item.label)
+    popup.value.close()
+    return
+  }
+  emit('change', item.choose, props.multiple ? showList.value[0].label : item.label)
+  popup.value.close()
+}
+
+</script>
+
+<style lang="scss" scoped>
+.popup-content {
+  height: 600px;
+  background: #FFF;
+  border-radius: 20rpx 20rpx 0 0;
+  padding: 10px 10px 80px 10px;
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  &-label {
+    height: 50px;
+    display: flex;
+    font-size: 32rpx;
+    font-weight: bold;
+    margin-bottom: 10px;
+    border-bottom: 2rpx solid #EEE;
+    &-item {
+      display: flex;
+      align-items: center;
+      padding: 0 15px;
+      box-sizing: border-box;
+      &.active {
+        border-bottom: 2px solid #008978;
+      }
+    }
+  }
+  &-body {
+    flex: 1;
+    height: 0;
+    display: flex;
+    &-list {
+      height: 100%;
+      min-width: 100px;
+      margin-right: 20px;
+      display: flex;
+      flex-direction: column;
+      &-label {
+        border-bottom: 1px solid #eee;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        height: 50px;
+        box-sizing: border-box;
+        margin-bottom: 10px;
+        &.active {
+          border-bottom: 2px solid #008978;
+        }
+      }
+      &-items {
+        flex: 1;
+        height: 0;
+        .content {
+          height: 100%;
+          overflow-x: hidden;
+          overflow-y: auto;
+          .active {
+            color: #008978;
+          }
+        }
+      }
+    }
+  }
+  &-footer {
+    height: 50px;
+    display: flex;
+    align-items: center;
+    .btn {
+      font-size: 28rpx;
+      height: 30px;
+      line-height: 30px;
+      &.cancel {
+        width: 40%;
+        margin-right: 10px;
+      }
+      &.submit {
+        flex: 1;
+        background: #008978;
+        color: #FFF ;
+      }
+    }
+  }
+}
+.dFlex {
+  display: flex;
+  justify-content: space-between;
+  height: 20px;
+}
+.py-1 {
+  padding: 10px 0;
+}
+</style>