浏览代码

feat: 省市选择器替换为省市区选择器

cuiyalong 1 年之前
父节点
当前提交
e450864711
共有 2 个文件被更改,包括 735 次插入2 次删除
  1. 4 2
      src/components/search/common/filters/schema.js
  2. 731 0
      src/components/selector/area-three-sidebar/index.vue

+ 4 - 2
src/components/search/common/filters/schema.js

@@ -1,5 +1,5 @@
 import ScopeSelectorContent from '@/components/selector/scope'
-import AreaCitySidebar from '@/components/selector/area-city-sidebar'
+import AreaCitySidebar from '@/components/selector/area-three-sidebar'
 import DateTimeGroup from '@/components/selector/date-time-group'
 import DateTimeList from '@/components/selector/date-time-list'
 import InfoTypeSideBar from '@/components/selector/info-type-sidebar'
@@ -44,7 +44,9 @@ export function createFilterOutsideSchemaItem(conf = {}) {
         content: {
           component: componentTypeMap[componentType],
           props: {
-            beforeChange: beforeAreaChange
+            beforeChange: beforeAreaChange,
+            tagTextOnlySelectedCount: true,
+            useProvinceCitySplit: true,
           }
         }
       })

+ 731 - 0
src/components/selector/area-three-sidebar/index.vue

@@ -0,0 +1,731 @@
+<template>
+  <SideBar
+    ref="firstSidebar"
+    class="area-city-sidebar-selector area-three-sidebar first-sidebar"
+    :height="height"
+    :class="{
+      'province-city-split': useProvinceCitySplit
+    }"
+    :whenParentLevel0Selected="whenParentLevel0Selected"
+    :sourceList="provinceList"
+    :childrenAddText="childrenAddText"
+    @onChange="onChangeFirst"
+    :beforeChildChange="beforeChildChange"
+    :showParentWhenChildrenEmpty="false"
+    :lazyRender="true"
+  >
+    <template #tab-tag="{ parent }">
+      <slot name="tab-tag" :parent="parent">
+        <van-tag
+          round
+          type="primary"
+          v-if="
+            parent.value &&
+            sourceFirstCount[parent.value] &&
+            sourceFirstCount[parent.value]._children_selectedCount
+          "
+          :class="{
+            visible:
+              sourceFirstCount[parent.value]._children_selectedCount === 0
+          }"
+        >
+          {{ sourceFirstCount[parent.value]._children_selectedCount }}
+        </van-tag>
+      </slot>
+    </template>
+    <template #tab-content="{ parent }">
+      <!-- 嵌套的 side-bar 组件 -->
+      <side-bar
+        :ref="'secondSidebar_' + parent.id || ''"
+        class="area-city-sidebar-selector second-sidebar"
+        :class="{
+          'disabled-city': disabledCitySelect,
+          'province-city-split': useProvinceCitySplit
+        }"
+        :height="height"
+        :childrenAddText="childrenAddText"
+        :source-list="parent.children"
+        @onChange="onChange($event, parent.value)"
+        :beforeParentChange="beforeParentChange"
+        :beforeChildChange="beforeChildChange"
+        :showParentWhenChildrenEmpty="false"
+      >
+        <template #tab-item="{ parent }">
+          <div class="title-name" v-show="!isGAT(parent.name)">
+            {{ parent.name }}
+          </div>
+          <div class="title-text" v-show="!isGAT(parent.name)">
+            <slot name="tab-tag" :parent="parent">
+              <van-tag
+                round
+                type="primary"
+                v-if="
+                  tagTextOnlySelectedCount && parent._children_selectedCount
+                "
+              >
+                {{ parent._children_selectedCount }}
+              </van-tag>
+            </slot>
+          </div>
+        </template>
+        <template #default="{ parent, child }">
+          <slot name="default" v-bind="{ parent, child }">
+            <template
+              v-if="
+                parent.level !== 0 && child.level === 0 && useProvinceCitySplit
+              "
+            >
+              <van-cell is-link :border="false">
+                {{ child.name }}
+                <template #right-icon>
+                  <span
+                    class="j-icon checkbox"
+                    :class="{ checked: child._selected }"
+                  ></span>
+                </template>
+              </van-cell>
+              <div class="children-title border-line-b" @click.stop>
+                <div class="c-title-text">市级</div>
+                <van-tag
+                  v-if="disabledCitySelect"
+                  round
+                  plain
+                  type="danger"
+                  @click="disabledCityClick"
+                  >开通</van-tag
+                >
+              </div>
+            </template>
+          </slot>
+        </template>
+      </side-bar>
+    </template>
+  </SideBar>
+</template>
+
+<script>
+import { Cell, Tag } from 'vant'
+import SideBar from '@/ui/sidebar-selector'
+import chinaMapJSON from '@/assets/js/china_area'
+import { provinceListMapExp } from '@/data'
+import { getRandomString } from '@/utils/'
+
+export default {
+  name: 'AreaThreeSidebar',
+  components: {
+    [Cell.name]: Cell,
+    [Tag.name]: Tag,
+    SideBar
+  },
+  props: {
+    height: {
+      type: String,
+      default: ''
+    },
+    value: {
+      type: Object,
+      default() {
+        return {}
+      }
+    },
+    /**
+     * 省份城市布局分割
+     */
+    useProvinceCitySplit: {
+      type: Boolean,
+      default: false
+    },
+    /**
+     * 城市选择禁用
+     */
+    disabledCitySelect: {
+      type: Boolean,
+      default: false
+    },
+    /**
+     * 数据统计tag显示
+     */
+    tagTextOnlySelectedCount: {
+      type: Boolean,
+      default: false
+    },
+    /**
+     * 二级子项 "全部" 文字的替换
+     */
+    childrenAddText: String,
+    /**
+     * 更改确认函数
+     *
+     * 如果传入该函数,点击二级子项会触发,返回true会确认成功,返回false则会阻止更改
+     */
+    beforeChange: Function
+  },
+  model: {
+    prop: 'value',
+    event: 'modelChange'
+  },
+  watch: {
+    value(newVal) {
+      this.setState(newVal)
+    }
+  },
+  data() {
+    return {
+      whenParentLevel0Selected: false,
+      // 原始城市数据
+      // chinaMapJSON,
+      // 省份与字母IndexBar对照表
+      provinceListMapExp,
+      provinceListMap: {
+        // A: [
+        //     {
+        //         name: '安徽',
+        //         expanded: false,
+        //         canExpanded: true,
+        //         selectedState: '',
+        //         children: []
+        //     }
+        // ]
+      },
+      provinceList: [],
+      // indexBar数据
+      indexList: [],
+      provinceExp: {
+        name: '安徽',
+        value: '',
+        // 展开状态
+        expanded: false,
+        // 是否可以展开
+        canExpanded: false,
+        children: []
+      },
+      sourceFirstCount: {},
+      // 全国二级菜单名
+      allCountryRefName: null,
+      // 二级菜单ref名
+      secondRefNameObj: {}
+    }
+  },
+  created() {
+    window.t = this
+    this.init(this.provinceListMapExp)
+  },
+  mounted() {
+    this.setState()
+    this.setActiveTab(1)
+  },
+  methods: {
+    setActiveTab(num) {
+      const { firstSidebar } = this.$refs
+      firstSidebar.setActiveTab(num)
+    },
+    // 整理城市数据列表(并初始化indexBar数据)
+    init(provinceListMapExp) {
+      console.log('------初始化数据---------')
+      // 整理数据得到List,同时获得indexList
+      const provinceListMap = {}
+      const indexList = []
+      this.secondRefNameObj = {}
+      this.allCountryRefName = null
+      for (const key in provinceListMapExp) {
+        const areaArr = []
+        indexList.push(key)
+        provinceListMapExp[key].forEach((pName) => {
+          const provinceExp = JSON.parse(JSON.stringify(this.provinceExp))
+          provinceExp.name = pName
+          provinceExp.id = `ap-${getRandomString(8).toLowerCase()}`
+          // 记录下二级ref
+          this.$set(
+            this.secondRefNameObj,
+            pName,
+            'secondSidebar_' + provinceExp.id
+          )
+          if (pName === '全国') {
+            this.allCountryRefName = 'secondSidebar_' + provinceExp.id
+          }
+          if (pName !== '全国') {
+            const cities = this.getCitiesFromJSONMap(pName)
+            // console.log(pName, cities)
+            // 筛选掉直辖市和特别行政区(台湾省也不不需要展开)
+            if (cities.ProRemark === '省份' || cities.ProRemark === '自治区') {
+              if (cities.ProID === 32) {
+                provinceExp.children = []
+                provinceExp.canExpanded = false
+              } else {
+                cities.city.forEach((c) => {
+                  // 将区县数据取出,处理成新数组,放入城市
+                  let districtChildren = []
+                  if (c.area && c.area.length > 0) {
+                    districtChildren = c.area.map((dItem) => {
+                      return {
+                        district: dItem,
+                        selected: false,
+                        canSelected: true,
+                        id: `adi-${getRandomString(8).toLowerCase()}`
+                      }
+                    })
+                  }
+                  // 将市区重组成一个新的对象
+                  provinceExp.children.push({
+                    city: c.name,
+                    selected: false,
+                    canSelected: true,
+                    id: `ac-${getRandomString(8).toLowerCase()}`,
+                    children: districtChildren // 区县
+                  })
+                })
+              }
+            } else if (cities.ProRemark === '直辖市') {
+              // 直辖市,将区县放置到第二级
+              if (cities.city && Array.isArray(cities?.city)) {
+                const orgDistrict = cities?.city[0].area || []
+                // 将区县数据取出,处理成新数组,放入城市
+                let districtChildren = orgDistrict.map((dItem) => {
+                  return {
+                    district: dItem,
+                    selected: false,
+                    canSelected: true,
+                    id: `adi-${getRandomString(8).toLowerCase()}`
+                  }
+                })
+                provinceExp.children.push({
+                  city:
+                    provinceExp.name?.indexOf('市') > -1
+                      ? provinceExp.name
+                      : provinceExp.name + '市',
+                  selected: false,
+                  canSelected: true,
+                  id: `ac-${getRandomString(8).toLowerCase()}`,
+                  children: districtChildren
+                })
+              }
+            } else {
+              provinceExp.children = []
+              provinceExp.canExpanded = false
+            }
+          }
+
+          provinceExp.canExpanded = provinceExp.children.length !== 0
+          areaArr.push(provinceExp)
+        })
+        provinceListMap[key] = areaArr
+      }
+      this.provinceListMap = provinceListMap
+      this.indexList = indexList.filter((i) => i !== '#')
+      // 给provinceListMap赋值
+      for (const k in provinceListMap) {
+        this.$set(this.provinceListMap, k, provinceListMap[k])
+      }
+      this.initProvinceList(provinceListMap)
+    },
+    // 循环chinaMapJSON,找到对应省下面对应的市
+    getCitiesFromJSONMap(provinceName) {
+      let temp = null
+      for (let i = 0; i < chinaMapJSON.length; i++) {
+        const findThis = chinaMapJSON[i].name.indexOf(provinceName) !== -1
+        // 如果找到了,就不再循环后面的了
+        if (findThis) {
+          temp = chinaMapJSON[i]
+          break
+        }
+      }
+      return temp
+    },
+    initProvinceList(provinceListMap) {
+      const provinceList = []
+      for (const key in provinceListMap) {
+        const provinces = provinceListMap[key]
+        provinces.forEach((province) => {
+          if (province.name === '全国') {
+            province.level = 0
+          } else {
+            province.level = 1
+          }
+          const cities = []
+          if (Array.isArray(province.children)) {
+            province.children.forEach((city) => {
+              // 将区县数据取出,处理成新数组,放入城市
+              let districtChildren = []
+              if (Array.isArray(city.children)) {
+                districtChildren = city.children.map((dItem) => {
+                  return {
+                    topName: province.name,
+                    parentName: city.city,
+                    name: dItem.district,
+                    value: dItem.district,
+                    id: dItem.id,
+                    level: 3
+                  }
+                })
+              }
+              cities.push({
+                id: city.id,
+                name: city.city,
+                value: city.city,
+                level: 2,
+                parentName: province.name,
+                sourceChildrenCount: districtChildren.length, // 子集真实数量
+                children: districtChildren // 区县
+              })
+            })
+          }
+
+          provinceList.push({
+            id: province.id,
+            name: province.name,
+            value: province.name,
+            level: province.level,
+            sourceChildrenCount: cities.length, // 子集真实数量
+            children: cities,
+            hasThree: true
+          })
+        })
+      }
+      this.provinceList = provinceList
+    },
+    disabledCityClick() {
+      this.$emit('onDisabledCityClick')
+    },
+    beforeChildChange(parent, child) {
+      if (this.beforeChange) {
+        return this.beforeChange(parent, child)
+      }
+      if (this.disabledCitySelect) {
+        if (child.level === 0 && parent.level === 0) {
+          return true
+        } else {
+          this.disabledCityClick()
+          return false
+        }
+      } else {
+        return true
+      }
+    },
+    // 二级城市选择,拦截处理
+    beforeParentChange(level) {
+      if (this.disabledCitySelect) {
+        if (level === 0) {
+          return true
+        } else {
+          this.disabledCityClick()
+          return false
+        }
+      } else {
+        return true
+      }
+    },
+    getState() {
+      const { state } = this.getStateMore()
+      return state
+    },
+    /**
+     * 最后输出数据结构
+     * 参数area示例
+     * {
+     *    澳门: {}
+     *    北京: {
+     *      北京: ['朝阳区']
+     *    },
+     *    河南: {
+     *      南阳市: [],
+     *      郑州: ['金水区'],
+     *      洛阳市: ['栾川县','老城区']
+     *    }
+     * }
+     */
+    getStateMore() {
+      // 选中状态
+      const state = {}
+      // 选中数量统计
+      const stateCounter = {}
+      // 所有二级市区遍历
+      for (let refKey in this.secondRefNameObj) {
+        const refName = this.secondRefNameObj[refKey]
+        const renderList = this.$refs[refName]?.renderList || []
+
+        for (let i = 0; i < renderList.length; i++) {
+          const parent = renderList[i]
+          const children = parent.children
+          const provinceName = parent.parentName
+          // 全国选中
+          if (parent.level === 0 && provinceName === '全国') {
+            if (parent._selected) {
+              break
+            }
+            continue
+          }
+          // 当前省份下,全部选中(保存当前省份)
+          if (parent.level === 0 && provinceName !== '全国') {
+            // 市区【全部】和区县【全部】都选中
+            if (parent._selected) {
+              state[provinceName] = {}
+              break
+            }
+            continue
+          }
+          // 当前市全选中(【市区】和【全部】选中)
+          if (parent.level === 2 && parent._selected) {
+            if (!state[provinceName]) {
+              state[provinceName] = {}
+            }
+            this.$set(state[provinceName], parent.name, [])
+            continue
+          }
+          // 区县选择
+          if (Array.isArray(children)) {
+            const childSelected = []
+            for (let j = 0; j < children.length; j++) {
+              const child = children[j]
+              if (child.level === 0) {
+                continue
+              } else {
+                if (child._selected) {
+                  childSelected.push(child.value)
+                }
+              }
+            }
+
+            if (childSelected.length > 0) {
+              if (!state[provinceName]) {
+                state[provinceName] = {}
+              }
+              if (!state[provinceName][parent.name]) {
+                this.$set(state[provinceName], parent.name, [])
+              }
+              state[provinceName][parent.name] = childSelected
+            }
+          }
+        }
+      }
+      return {
+        state,
+        stateCounter
+      }
+    },
+    /**
+     * 设置组件状态
+     * getState中获取的数据能够直接传入进行状态恢复
+     * 全部不选中传入-1
+     */
+    setState(state = {}) {
+      const firstSidebar = this.$refs.firstSidebar
+      firstSidebar.setAllState(false)
+      firstSidebar.refreshAllChildrenState(false)
+      if (!state || Object.keys(state).length === 0) {
+        // 重置第二级,并且选中全国
+        this.resetSelect()
+      } else {
+        this.setSidebarState(state)
+        this.$refs[this.allCountryRefName]?.setParentLevel0State(false)
+      }
+      // 重新计算parent数据统计
+      firstSidebar.refreshAllChildrenState()
+      this.$emit('modelChange', state || {})
+    },
+    setSidebarState(state) {
+      for (const proName in state) {
+        const refName = this.secondRefNameObj[proName]
+        const refItem = this.$refs[refName]
+        if (!refItem) {
+          return
+        }
+        // 选择了全省
+        const stateProvince = state[proName]
+        // 第一级省份下选中的市区数量
+        let _children_selectedCount = 0
+        refItem.setState((renderList) => {
+          // 选择了全省
+          if (stateProvince && Object.keys(stateProvince).length === 0) {
+            refItem.setAllState(true)
+            refItem.refreshAllChildrenState(true)
+            // 更新第一级 side-bar 的状态
+            _children_selectedCount = renderList.length - 1
+          } else {
+            renderList.forEach((parent) => {
+              // 当前市下选择的区域数组
+              const stateDistrict = stateProvince[parent.name]
+              if (stateDistrict) {
+                if (Array.isArray(stateDistrict)) {
+                  _children_selectedCount += 1
+                  // 选择了全市
+                  if (stateDistrict.length === 0) {
+                    refItem.setChildrenState(parent.children, true)
+                  } else {
+                    // 选择了部分区域
+                    parent.children.forEach((child) => {
+                      if (stateDistrict.includes(child.value)) {
+                        child._selected = true
+                      }
+                    })
+                  }
+                }
+                refItem.checkChildrenAllChecked(parent, parent.children)
+              }
+            })
+          }
+          // 更新第一级 side-bar 的状态
+          this.$set(this.sourceFirstCount, proName, {
+            _children_selectedCount: _children_selectedCount,
+            _children_count: renderList.length - 1
+          })
+        })
+        refItem.refreshAllChildrenState()
+      }
+    },
+    // 重置所有选择
+    resetSelect() {
+      const { firstSidebar } = this.$refs
+      firstSidebar.setAllState(false)
+      firstSidebar.refreshAllChildrenState(false)
+      const firstRenderList = firstSidebar.renderList
+      for (let item of firstRenderList) {
+        const secondRef = this.$refs['secondSidebar_' + item.id]
+        secondRef?.setAllState(false)
+        secondRef?.refreshAllChildrenState(false)
+        if (item.name === '全国') {
+          secondRef?.setParentLevel0State(true)
+        }
+      }
+      this.sourceFirstCount = {}
+    },
+    // 处理第一级 side-bar 组件的变化事件
+    onChangeFirst({ parent, child, renderList }) {
+      if (parent.level === 0 && parent.name === '全国') {
+        this.resetSelect()
+      } else {
+        this.$set(this.sourceFirstCount, parent.value, {
+          _children_selectedCount: parent._children_selectedCount,
+          _children_count: parent._children_count
+        })
+        this.$refs.firstSidebar.setParentLevel0State(false)
+      }
+    },
+    // 处理第二级 side-bar 组件的变化事件
+    onChange({ parent, child, renderList }, value) {
+      this.disposeSecondCount({ parent, child, renderList }, value)
+      const stateValue = this.getState()
+      const payload = { parent, child, value: stateValue }
+      this.$emit('modelChange', payload.value)
+      this.$emit('change', payload)
+    },
+    // 处理二级菜单选择
+    disposeSecondCount({ parent, child, renderList }, value) {
+      if (parent.level === 0 && parent.parentName === '全国') {
+        this.resetSelect()
+        return
+      }
+      // 将全国选择重置掉
+      if (this.allCountryRefName) {
+        this.$refs[this.allCountryRefName]?.setParentLevel0State(false)
+      }
+      const selectedNum =
+        renderList?.filter((temp) => temp._children_selectedCount > 0)
+          ?.length || 0
+      const result = {
+        _children_selectedCount: selectedNum,
+        _children_count: renderList.length - 1
+      }
+      // 更新第一级 side-bar 的状态
+      this.$set(this.sourceFirstCount, value, result)
+      this.$refs.firstSidebar.setParentLevel0State(false)
+    },
+    // 是否是港澳台地区
+    isGAT(val) {
+      const GATList = ['香港', '澳门', '台湾']
+      return val && GATList.indexOf(val) > -1
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.province-city-split {
+  .content-list-item-container {
+    &::after {
+      height: 0;
+    }
+  }
+  .children-title {
+    display: flex;
+    align-items: center;
+    padding: 8px 16px;
+    font-size: 12px;
+    color: #171826;
+    line-height: 22px;
+    &.border-line-b {
+      &::after {
+        left: 16px;
+        right: 16px;
+      }
+    }
+    .c-title-text {
+      margin-right: 8px;
+    }
+  }
+}
+
+.area-three-sidebar {
+  .disabled-city {
+    .content-list-item-container {
+      opacity: 0.6;
+    }
+    .content-list-item-container.allChildBtn {
+      opacity: 1;
+    }
+    .van-tab .title-name {
+      opacity: 0.6;
+    }
+  }
+
+  .van-tab {
+    padding: 10px 4px 10px 12px;
+    font-size: 14px;
+    line-height: 20px;
+    .van-tag {
+      display: inline-block;
+      font-size: 11px;
+      padding: 1px 4px;
+      min-width: 18px;
+      text-align: center;
+    }
+    .title-name {
+      width: 84px;
+    }
+    .title-list-item {
+      width: 100%;
+    }
+  }
+  .van-cell {
+    padding: 10px 4px 10px 12px;
+    font-size: 14px;
+    line-height: 20px;
+    .van-cell__title {
+      margin-right: 10px;
+    }
+    .j-icon {
+      width: 16px;
+      height: 16px;
+    }
+    &.active-cell {
+      .van-cell__title {
+        color: $main;
+      }
+    }
+  }
+  &.first-sidebar {
+    .van-tab {
+      width: 100px;
+      .title-text {
+        margin-left: 2px;
+      }
+    }
+  }
+  &.second-sidebar {
+    .van-tab {
+      width: 132px;
+      .title-text {
+        margin-left: 5px;
+      }
+    }
+  }
+}
+</style>