9afce6f6创建于 2025年5月7日历史提交
/*
* Copyright (c) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { AlphabetListItemView, AlphabetListItemType, CITY_DATA, HotListItemView, HOT_CITY } from '../model/DetailData';
import { SearchView } from './SearchView';
import { AlphabetListView } from '../utils/AlphabetListView';
import { promptAction } from '@kit.ArkUI';
import { CommonConstants } from '../common/CommonConstants';

const SEARCH_BUTTON_TEXT = '搜索';

/**
 * 功能说明: 本示例介绍城市选择场景的使用:通过AlphabetIndexer实现首字母快速定位城市的索引条导航
 *
 * 推荐场景: 首字母快速定位的列表场景
 *
 * 核心组件:
 * 1. AlphabetListView: 列表视图
 *
 * 实现步骤:
 * 1. 数据准备。初始化城市列表和热门城市列表数组。
 * @example
 * // 热门城市列表
   hotCityList: HotCityListItemView[] = [];
   // 城市列表
   cityList: CityListItemView[] = [];

   aboutToAppear(): void {
     CITY_DATA.forEach((cityItem: CityType) => {
       this.cityList.push(new CityListItemView(cityItem, wrapBuilder(cityListItemSection)))
     })
     HOT_CITY.forEach((hotCityItem: string) => {
       this.hotCityList.push(new HotCityListItemView(hotCityItem, wrapBuilder(hotCitySection)))
     })
   }
 * 以上代码中,HotCityListItemView为热门城市列表项的类,CityListItemView为城市列表项的类。两者都包含列表项、
 * 列表项视图,开发者可以自行配置,也可以使用默认的属性配置。
 *
 * 2. 构建自定义城市视图。
   AlphabetListView({
     hotSelectList: this.hotCityList,
     hotSelectListTitle: $r('app.string.citysearch_hotCity'),
     alphabetSelectList: this.cityList,
     hotSelectHandle: (hotSelectValue: string) => {
       this.hotSelectHandle(hotSelectValue);
     },
     alphabetSelectHandle: (alphabetSelectValue: string) => {
       this.alphabetSelectHandle(alphabetSelectValue);
     }
   })
 *
 */
@Component
export struct CitySearch {
  // 搜索值
  @State changeValue: string = '';
  // 热门城市列表
  hotCityList: HotListItemView[] = [];
  // 城市列表
  cityList: AlphabetListItemView[] = [];
  // 占位
  placeholder: string = CommonConstants.PLACE_HOLDER_TEXT;
  // 搜索控制器
  controller: SearchController = new SearchController();
  // 是否展示搜索结果列表
  @State isSearchState: boolean = false;
  // 搜索结果
  @State searchList: string[] = [];

  /**
   * 弹窗函数
   */
  showToast() {
    promptAction.showToast({
      message: $r('app.string.citysearch_only_show_for_ux')
    })
  }

  aboutToAppear(): void {
    CITY_DATA.forEach((cityItem: AlphabetListItemType) => {
      this.cityList.push(new AlphabetListItemView(cityItem, wrapBuilder(cityListItemSection)))
    })
    HOT_CITY.forEach((hotCityItem: string) => {
      this.hotCityList.push(new HotListItemView(hotCityItem, wrapBuilder(hotCitySection)))
    })
  }

  build() {
    Column() {
      // 搜索框
      Search({ value: this.changeValue, placeholder: this.placeholder, controller: this.controller })
        .id('searchCity')
        .searchButton(SEARCH_BUTTON_TEXT)
        .width(CommonConstants.VIEW_FULL)
        .height($r('app.integer.citysearch_search_height'))
        .margin({ left: $r('app.integer.citysearch_search_margin_left') })
        .backgroundColor($r('app.color.citysearch_search_bgc'))
        .placeholderColor(Color.Grey)
        .placeholderFont({ size: $r('app.integer.citysearch_placeholderFont_size'), weight: CommonConstants.WEIGHT })
        .textFont({ size: $r('app.integer.citysearch_placeholderFont_size'), weight: CommonConstants.WEIGHT })
        .onSubmit((value: string) => {
          // 如果没有输入数据,就使用占位符作为默认数据
          if (value.length === 0) {
            value = this.placeholder;
          }
          // 将值赋值给changeValue
          this.changeValue = value;
          this.isSearchState = true;
          this.searchCityList(value);
          if (this.searchList.length === 0) {
            this.showToast();
          }
        })
        .onChange((value: string) => {
          this.changeValue = value;
          // 搜索
          this.searchCityList(value);
          if (value.length === 0) {
            // 关闭搜索列表
            this.isSearchState = false;
            // 清空数据
            this.searchList.splice(0, this.searchList.length);
          } else {
            this.changeValue = value;
            // 搜索
            this.searchCityList(value);
            this.isSearchState = true;
            if (this.searchList.length === 0) {
              this.showToast();
            }
          }
        })

      /**
       * 城市列表组件
       * @param hotSelectList - 热门选择列表
       * @param alphabetSelectList - 字母选择数据列表
       * @param hotSelectListTitle - 热门选择列表标题
       * @param hotSelectHandle - 点击热门选择列表项处理逻辑
       * @param alphabetSelectHandle - 点击字母列表项处理逻辑
       */
      AlphabetListView({
        hotSelectList: this.hotCityList,
        hotSelectListTitle: $r('app.string.citysearch_hotCity'),
        alphabetSelectList: this.cityList,
        hotSelectHandle: (hotSelectValue: string) => {
          this.hotCityHandle(hotSelectValue);
        },
        alphabetSelectHandle: (alphabetSelectValue: string) => {
          this.cityHandle(alphabetSelectValue);
        }
      })
        .margin({ top: $r('app.integer.citysearch_list_margin_top') })/**
         * 性能知识点:由于需要通过搜索按钮频繁的控制自定义组件的显隐状态,因此推荐使用显隐控制替代条件渲染,
         * 参考合理选择条件渲染和显隐控制文章:
         * https://gitee.com/harmonyos-cases/cases/blob/master/docs/performance/proper-choice-between-if-and-visibility.md
         */
        .visibility(this.isSearchState ? Visibility.None : Visibility.Visible)
        .layoutWeight(1)

      /**
       * 搜索组件,将数据传递到搜索列表
       * @param searchList: 搜索列表数据
       * @param changeValue: 选定的城市
       */
      SearchView({ searchList: $searchList, changeValue: $changeValue })
        .width(CommonConstants.VIEW_FULL)
        .layoutWeight(1)
        .visibility(this.isSearchState ? Visibility.Visible : Visibility.None)
    }
    .width(CommonConstants.VIEW_FULL)
    .height(CommonConstants.VIEW_FULL)
    .padding({ left: $r('app.integer.citysearch_padding_left'), right: $r('app.integer.citysearch_padding_right') })
    .backgroundColor($r('app.color.citysearch_bgc'))
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Start)
    .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
  }

  // 搜索城市展示逻辑
  searchCityList(value: string) {
    let cityNames: string[] = [];
    CITY_DATA.forEach(item => {
      if (item.name.includes(value.toUpperCase())) {
        // 当搜索城市拼音首字母,将相关城市信息展示。例如输入"a",会出现"阿尔山"、"阿勒泰地区"、"安庆"、"安阳"。
        // 输入"b",会出现"北京"、"亳州"、"包头"、"宝鸡"。
        cityNames = item.dataList;
        return;
      }
      item.dataList.forEach(cityName => {
        // 当搜索汉字,会进行模糊搜索,将相关城市信息展示。例如搜索"长":展示"长沙","长春"。
        if (cityName.includes(value)) {
          cityNames.push(cityName);
        }
      })
    })
    // 判断是否有输入的值
    if (value.length !== 0) {
      this.searchList = cityNames;
    } else {
      this.searchList = [];
    }
  }

  cityHandle(alphabetSelectValue: string) {
    // TODO:待开发者根据选定列表的城市名称时执行的逻辑操作,比如说筛选等
    // 当前调用Toast显示提示:此样式仅为案例展示
    promptAction.showToast({ message: $r('app.string.citysearch_only_show_ui') });
  }

  hotCityHandle(hotSelectValue: string) {
    this.changeValue = hotSelectValue;
  }
}

@Builder
function hotCitySection(hotCity: string) {
  Text(`${hotCity}`)
    .margin({
      bottom: $r('app.integer.citysearch_text_margin_bottom'),
      left: $r('app.integer.citysearch_text_margin_left2')
    })
    .width(CommonConstants.HOT_CITY_TEXT_WIDTH)
    .height($r('app.integer.citysearch_text_height'))
    .textAlign(TextAlign.Center)
    .fontSize($r('app.integer.citysearch_text_font'))
    .maxLines(CommonConstants.MAX_LINES)
    .fontColor($r('app.color.citysearch_text_font_color2'))
    .backgroundColor($r('app.color.citysearch_text_bgc'))
    .borderRadius($r('app.integer.citysearch_text_border_radius'))
}

@Builder
function cityListItemSection(cityItem: string) {
  Text(cityItem)
    .height($r('app.integer.citysearch_list_item_height'))
    .fontSize($r('app.integer.citysearch_text_font'))
    .width(CommonConstants.VIEW_FULL)
}