HarmonyOS项目开发经历-省市区联动业务

189 阅读3分钟

省市区联动

d3pc3-pr546.gif

上代码:

import http from '@ohos.net.http';

interface IResponse {
  message: string
  list: string[]
}

@Entry
@Component
struct AreaChange {
  @State range: string[][] = [[], [], []] //TextPicker的values数组
  @State selected: number[] = [0, 0, 0] //TextPicker的index数组
  @State values: string[] = ['北京', '北京市', '东城区'] //选中后用于展示的数组
  req: http.HttpRequest = http.createHttp()
  @State showSheet: boolean = false //半模态框
  timeId: number = -1 //防抖处理的延时器Id

  async aboutToAppear() {
    //进入半模态框后需要马上发起请求存入range数组中先进行展示
    const res1 = await this.req.request("https://hmajax.itheima.net/api/province")
    const provinces = (JSON.parse(res1.result as string) as IResponse).list
    const res2 = await this.req.request("https://hmajax.itheima.net/api/city?pname=" + encodeURIComponent(provinces[0]))
    const citys = (JSON.parse(res2.result as string) as IResponse).list
    const res3 = await this.req.request(`https://hmajax.itheima.net/api/area?pname=${encodeURIComponent(provinces[0])}&cname=${encodeURIComponent(citys[0])}`)
    const areas = (JSON.parse(res3.result as string) as IResponse).list
    this.range[0] = provinces
    this.range[1] = citys
    this.range[2] = areas
  }

  build() {
    Column({ space: 10 }) {
      Text('居住地选择')
        .fontWeight(FontWeight.Bold)
        .textAlign(TextAlign.Center)
        .width('100%').fontSize(30).margin({ bottom: 20 })
      Row({ space: 10 }) {
        Text('居住地:')
          .fontWeight(FontWeight.Bold)
        Text(this.values.join('/'))
          .layoutWeight(1)
          .fontColor(Color.Gray)
          .onClick(() => {
            this.showSheet = true
          })
      }
      Divider()
      Blank()
    }
    .height('100%').width('100%').padding(20)
    .alignItems(HorizontalAlign.Start)
    .bindSheet($$this.showSheet, this.areaSheet(), { height: 300 })
  }

  @Builder
  areaSheet() {
    Column() {
      TextPicker({
        range: this.range, //['北京', '北京市', '东城区']
        selected: $$this.selected, //[0, 0, 0]
      })
        .canLoop(false)// 不要循环
        .onChange((values) => {
          //进行防抖处理,防止滚动期间多次发送请求
          clearTimeout(this.timeId)
          this.timeId = setTimeout(async () => {

            //当省份被改变
            if (this.values[0] !== values[0]) {
              const res2 = await this.req.request("https://hmajax.itheima.net/api/city?pname=" + encodeURIComponent(values[0]))
              const citys = (JSON.parse(res2.result as string) as IResponse).list
              const res3 = await this.req.request(`https://hmajax.itheima.net/api/area?pname=${encodeURIComponent(values[0])}&cname=${encodeURIComponent(citys[0])}`)
              const areas = (JSON.parse(res3.result as string) as IResponse).list
              this.range[1] = citys //发送请求拿到省份,存入range数组中
              this.range[2] = areas //根据省份发送请求,拿到对应的城市,存入range数组中

              //当省份发生变化需要把城市置零,当城市发生变化需要把地区置零
              this.selected[1] = 0
              this.selected[2] = 0

              //内容页面展示需要同步
              this.values[0] = values[0]
              this.values[1] = this.range[1][0]
              this.values[2] = this.range[2][0]

              //当城市被改变
            } else if (this.values[1] !== values[1]) {
              const res3 = await this.req.request(`https://hmajax.itheima.net/api/area?pname=${encodeURIComponent(values[0])}&cname=${encodeURIComponent(values[1])}`)
              const areas = (JSON.parse(res3.result as string) as IResponse).list
              this.range[2] = areas //根据省份和城市发送请求,拿到对应地区,存入range数组中

              //当城市发生变化需要把地区置零
              this.selected[2] = 0

              //内容页面展示需要同步
              this.values[1] = values[1]
              this.values[2] = this.range[2][0]

              //当地区被改变
            } else if (this.values[2] !== values[2]) {

              //内容页面展示需要同步
              this.values[2] = values[2]
            }

          }, 1000)
        })
    }
  }
}

另外,当我们在开发中需要完成以下类似的联动业务,例如:通讯录,城市联动等:

List列表联动字母索引表是怎么实现的呢?

image.png

咱们分两步来做:

  1. 滚动 List,同步选中对应的AlphabetIndexer
    .onScrollIndex((firstIndex: number) => {
      this.selectedIndex = firstIndex
    })
  1. 选中AlphabetIndexer的区域,同步滚动List
    AlphabetIndexer({ arrayValue: this.alphabets, selected: $$this.selectedIndex })
      .width(20)
      .onSelect((index: number) => {
        this.listScroller.scrollToIndex(index)
      })

具体代码如下:

interface BKCityContent {
  initial: string
  cityNameList: string[]
}
​
@Entry
@Component
struct ListPageBK {
  @State selectedIndex: number = 0;
  @State isShow: boolean = false
  hotCitys: string[] = ['北京', '上海', '广州', '深圳', '天津', '杭州', '南京', '苏州', '成都', '武汉', '重庆', '西安', '香港', '澳门', '台北']
  historyCitys: string[] = ['北京', '上海', '广州', '深圳', '重庆']
  cityContentList: BKCityContent[] = [
    {
      initial: 'A',
      cityNameList: ['阿拉善', '鞍山', '安庆', '安阳', '阿坝', '安顺']
    },
    {
      initial: 'B',
      cityNameList: ['北京', '保定', '包头', '巴彦淖尔', '本溪', '白山']
    },
    {
      initial: 'C',
      cityNameList: ['成都', '重庆', '长春', '长沙', '承德', '沧州']
    },
    {
      initial: 'D',
      cityNameList: ['大连', '东莞', '大同', '丹东', '大庆', '大兴安岭']
    },
    {
      initial: 'E',
      cityNameList: ['鄂尔多斯', '鄂州', '恩施', '额尔古纳市', '二连浩特市', '恩施市']
    },
    {
      initial: 'F',
      cityNameList: ['福州', '佛山', '抚顺', '阜新', '阜阳', '抚州']
    },
    {
      initial: 'G',
      cityNameList: ['广州', '贵阳', '赣州', '桂林', '贵港', '广元']
    },
    {
      initial: 'H',
      cityNameList: ['杭州', '海口', '哈尔滨', '合肥', '呼和浩特', '邯郸']
    },
    {
      initial: 'J',
      cityNameList: ['济南', '晋城', '晋中', '锦州', '吉林', '鸡西']
    },
    {
      initial: 'K',
      cityNameList: ['昆明', '开封', '康定市', '昆山', '康保县', '宽城满族自治县']
    },
    {
      initial: 'L',
      cityNameList: ['兰州', '廊坊', '临汾', '吕梁', '辽阳', '辽源']
    },
    {
      initial: 'M',
      cityNameList: ['牡丹江', '马鞍山', '茂名', '梅州', '绵阳', '眉山']
    },
    {
      initial: 'N',
      cityNameList: ['南京', '宁波', '南昌', '南宁', '南通', '南平']
    },
    {
      initial: 'P',
      cityNameList: ['盘锦', '莆田', '萍乡', '平顶山', '濮阳', '攀枝花']
    },
    {
      initial: 'Q',
      cityNameList: ['青岛', '秦皇岛', '齐齐哈尔', '七台河', '衢州', '泉州']
    },
    {
      initial: 'R',
      cityNameList: ['日照', '日喀则', '饶阳县', '任丘市', '任泽区', '饶河县']
    },
    {
      initial: 'S',
      cityNameList: ['上海', '苏州', '深圳', '沈阳', '石家庄', '朔州']
    },
    {
      initial: 'T',
      cityNameList: ['天津', '太原', '唐山', '通辽', '铁岭', '通化']
    },
    {
      initial: 'W',
      cityNameList: ['无锡', '武汉', '乌海', '乌兰察布', '温州', '芜湖']
    },
    {
      initial: 'X',
      cityNameList: ['厦门', '西安', '西宁', '邢台', '忻州', '兴安盟']
    },
    {
      initial: 'Y',
      cityNameList: ['扬州', '阳泉', '运城', '营口', '延边', '伊春']
    },
    {
      initial: 'Z',
      cityNameList: ['郑州', '珠海', '张家口', '镇江', '舟山', '漳州']
    }
  ]
  alphabets: string[] = ['#', '热', "A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "W", "X", "Y", "Z"]
  listScroller: Scroller = new Scroller();

  build() {
    Column() {
      Button('显示城市选择')
        .onClick(() => this.isShow = true)
        .bindContentCover(this.isShow, this.ContentCoverBuilder())
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f8f8f8')
  }

  @Builder
  ContentCoverBuilder() {
    Stack({ alignContent: Alignment.End }) {
      Column() {
        // 顶部
        this.TopBuilder();
        // 列表
        this.ListBuilder();
      }
      .backgroundColor(Color.White)

      // 导航
      this.AlphabetBuilder()
    }
  }

  @Builder
  AlphabetBuilder() {
    AlphabetIndexer({ arrayValue: this.alphabets, selected: $$this.selectedIndex })
      .width(20)
      .onSelect((index: number) => {
        this.listScroller.scrollToIndex(index)
      })
  }

  @Builder
  ListBuilder() {
    List({ space: 30, scroller: this.listScroller }) {
      // 历史
      this.LocationListItemBuilder()
      // 热门
      this.HotListItemBuilder()

      // A-B的区域
      this.LetterListItemBuilder()
    }
    .divider({ startMargin: 20, endMargin: 20, color: '#f3f3f3', strokeWidth: 2 })
    .width('100%')
    .layoutWeight(1)
    .sticky(StickyStyle.Header)
    .onScrollIndex((firstIndex: number) => {
      this.selectedIndex = firstIndex
    })
  }

  @Builder
  LetterListItemBuilder() {
    // A-B的区域
    ForEach(this.cityContentList, (item: BKCityContent) => {
      ListItemGroup({ header: this.ListItemGroupHeaderBuilder(item.initial) }) {
        ForEach(item.cityNameList, (city: string) => {
          ListItem() {
            Text(city)
              .width('100%')
              .padding({ left: 20 })
          }
          .width('100%')
          .height(50)
          .backgroundColor(Color.White)
        })
      }
      .padding({ bottom: 20 })
      .divider({ startMargin: 20, endMargin: 20, color: '#f3f3f3', strokeWidth: 2 })
    })
  }

  @Builder
  ListItemGroupHeaderBuilder(title: string) {
    Text(title)
      .padding({ left: 20, bottom: 15, top: 20 })
      .fontSize(14)
      .fontColor(Color.Gray)
      .backgroundColor('#f8f8f8')
      .width('100%')
  }

  @Builder
  HotListItemBuilder() {
    // 热门
    ListItem() {
      Column({ space: 10 }) {
        Text('热门城市')
          .alignSelf(ItemAlign.Start)
          .fontColor(Color.Gray)
          .fontSize(14)
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.hotCitys, (item: string) => {
            Text(item)
              .height(25)
              .backgroundColor(Color.White)
              .width('25%')
              .margin({ bottom: 10 })
          })
        }
        .padding({ left: 20, right: 20 })
      }
      .width('100%')
      .padding({ left: 20, right: 20, bottom: 10 })
    }
  }

  @Builder
  LocationListItemBuilder() {
    ListItem() {
      Column({ space: 15 }) {
        // 定位地址
        Row() {
          Text('北京')
          Text() {
            ImageSpan($r('app.media.ic_public_location_fill_blue'))
              .width(20)
            Span('开启定位')
          }
        }
        .width('100%')
        .padding({ top: 10, bottom: 10, right: 20, left: 20 })
        .justifyContent(FlexAlign.SpaceBetween)
        .backgroundColor(Color.White)

        // 历史
        Column({ space: 10 }) {
          Text('历史')
            .fontColor(Color.Gray)
            .alignSelf(ItemAlign.Start)
            .fontSize(14)
          Flex({ wrap: FlexWrap.Wrap }) {
            ForEach(this.historyCitys, (city: string, index: number) => {
              Text(city)
                .height(25)
                .backgroundColor(Color.White)
                .width('25%')
                .margin({ bottom: 10 })
            })
          }
          .padding({ left: 20, right: 20 })
        }
        .width('100%')
        .padding({ left: 20, right: 20 })
      }
    }
    .padding({ top: 20 })
  }

  @Builder
  TopBuilder() {
    Column() {
      // X + 输入框
      Row({ space: 20 }) {
        Image($r('app.media.ic_public_cancel'))
          .width(30)
          .fillColor(Color.Gray)

        Row({ space: 5 }) {
          Image($r('app.media.ic_public_search'))
            .width(18)
          Text('请输入城市名称')
            .layoutWeight(1)
        }
        .height(50)
        .border({ width: .5, color: Color.Gray, radius: 5 })
        .padding({ left: 5 })
        .layoutWeight(1)
        .shadow({
          radius: 20,
          color: '#f6f6f7'
        })
      }
      .padding({
        left: 15,
        right: 15,
        top: 15
      })
​
      // 国内城市
      Column() {
        Text('国内城市')
          .fontSize(15)
          .fontWeight(800)
          .padding(5)
        Row()
          .width(20)
          .height(2)
          .backgroundColor('#0094ff')
          .borderRadius(2)
      }

    }
    .width('100%')
    .backgroundColor(Color.White)
    .height(100)
    .border({
      width: { bottom: 4 },
      color: '#f6f6f7',
    })
  }
}