《仿盒马》app开发技术分享-- 商品搜索页(搜索记录&商品搜索)(38)


## 技术栈


Appgallery connect

## 开发准备

上一节我们新建了商品搜索页,实现了顶部的搜索bar以及下方的推荐搜索列表,这一节我们要新增一个商品搜索记录列表,以及输入内容之后搜索出对应商品的功能,我们还需要保证搜索内容的唯一性,以及搜索记录的本地数据持久化和搜索记录列表的删除



## 功能分析

商品搜索记录列表,我们可以通过保存输入的搜索内容到用户首选项中实现。商品搜索通过输入的名称与云数据库的商品名称匹配。搜索记录的唯一性是当我们搜索相同的内容,只会产生一条记录,只需要在添加数据的时候进行一次过滤,搜索记录的删除我们通过弹窗来实现,调用封装好的删除方法,根据key删除对应的存储记录



## 代码实现

首先实现存储搜索的内容,在点击搜索时添加


```c

 Text("搜索")

          .border({width:1,radius:10,color:Color.White})

          .fontSize(14)

          .fontColor("#ffffff")

          .padding({left:13,right:13,top:5,bottom:5})

          .borderRadius(10)

          .onClick(async ()=>{

            if (this.text.trim()==''&&this.text.length==0) {

              this.isSearch=false

              showToast("搜索内容不能为空")

            }else {

                this.isSearch=true

                let home_product=new cloudDatabase.DatabaseQuery(home_product_list);

              home_product.equalTo("name",this.text)

                let list = await databaseZone.query(home_product);

                let json = JSON.stringify(list)

                let data:HomeProductList[]= JSON.parse(json)

                this.goodsList=data

                StorageUtils.set("history_list",this.addToStringList(this.searchHistoryList,this.text))

                const history = await StorageUtils.getAll("history_list")

                if (history!=null&&history!=undefined) {

                  this.searchHistoryList=JSON.parse(history)

                }

            }

          })

```

展示存储的搜索列表


```c

//数据初始化

 async aboutToAppear(): Promise {

    const history = await StorageUtils.getAll("history_list")

    if (history!=''&&history!=undefined) {

      this.searchHistoryList=JSON.parse(history)

    }

    let condition = new cloudDatabase.DatabaseQuery(search_hot_txt);

    let listData = await databaseZone.query(condition);

    let json = JSON.stringify(listData)

    let data1:SearchHotTxt[]= JSON.parse(json)

    this.searchTxt=data1

    this.flag=true

  }


  List(){

            ForEach(this.searchHistoryList,(item:string)=>{

              ListItem(){

                Column({space:5}){

                  Text(item)

                    .fontColor("#ffa09a9a")

                    .padding({left:15})

                    .margin({top:10})

                  Divider().width('100%').height(0.8)

                    .color("#e6e6e6")

                }

                .alignItems(HorizontalAlign.Start)

              }

            })

          }


```

现在数据列表已经展示出来了,但是现在我们有相同的内容展示到列表中


```c

//添加的时候处理一下

  addToStringList(list: string[], newItem: string): string {

  if (!list.includes(newItem)) {

    list.push(newItem);

  }

  return JSON.stringify(list);

}


```

根据搜索内容查询对应的商品


```c

let home_product=new cloudDatabase.DatabaseQuery(home_product_list);

              home_product.equalTo("name",this.text)

                let list = await databaseZone.query(home_product);

                let json = JSON.stringify(list)

                let data:HomeProductList[]= JSON.parse(json)

                this.goodsList=data

```

添加商品展示的列表组件


```c

 WaterFlow() {

          ForEach(this.goodsList, (item:HomeProductList, index) => {

            FlowItem() {

              Column() {

                Image(item.url)

                  .width('100%')

                  .aspectRatio(1)

                  .objectFit(ImageFit.Cover)

                  .borderRadius({topLeft:10,topRight:10})


                Column() {

                  Text(item.name)

                    .fontSize(16)

                    .fontColor('#333')

                    .margin({ bottom: 4 })


                  Text(item.text_message)

                    .fontSize(12)

                    .fontColor('#666')

                    .margin({ bottom: 8 })



                  Text("最高立减"+item.promotion_spread_price)

                    .fontSize(12)

                    .fontColor('#ffffff')

                    .visibility(item.promotion_spread_price>0?Visibility.Visible:Visibility.None)

                    .margin({ bottom: 8 })

                    .padding({left:5,right:5,top:2,bottom:2})

                    .linearGradient({

                      angle:90,

                      colors: [[0xff0000, 0], [0xff6666, 0.2], [0xff6666, 1]]

                    })


                  Row(){

                    Text("限购")

                      .width(40)

                      .fontSize(12)

                      .borderRadius(20)

                      .backgroundColor("#FB424C")

                      .padding(3)

                      .textAlign(TextAlign.Center)

                    Text("每人限购"+item.max_loop_amount+"件")

                      .margin({left:5})

                      .fontSize(12)

                      .fontColor("#FB424C")

                  }

                  .borderRadius(20)

                  .padding({top:2,bottom:2,right:10})

                  .backgroundColor("#FEE3E3")

                  .visibility(item.amount>0?Visibility.Visible:Visibility.None)


                  Row() {

                    Text(){

                      Span("¥")

                        .fontColor(Color.Red)

                        .fontSize(14)


                      Span(String(item.price))

                        .fontSize(16)

                        .fontColor(Color.Red)

                    }

                    Text(String(item.original_price))

                      .fontSize(12)

                      .fontColor('#999')

                      .decoration({

                        type: TextDecorationType.LineThrough,

                        color: Color.Gray

                      })

                      .margin({left:10})



                    Blank()

                    Column() {

                      Image($r('app.media.cart'))

                        .width(20)

                        .height(20)

                    }

                    .justifyContent(FlexAlign.Center)

                    .width(36)

                    .height(36)

                    .backgroundColor("#ff2bd2fa")

                    .borderRadius(18)

                  }

                  .margin({top:10})

                  .width('100%')

                  .justifyContent(FlexAlign.SpaceBetween)




                }

                .alignItems(HorizontalAlign.Start)

                .padding(12)

              }

              .backgroundColor(Color.White)

              .borderRadius(12)

              .onClick(() => {

                let product: ProductDetailModel = {

                  id: item.id

                };

                router.pushUrl({

                  url: 'pages/component/ProductDetailsPage',

                  params: product

                }, (err) => {

                  if (err) {

                    console.error(`Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`);

                    return;

                  }

                  console.info('Invoke pushUrl succeeded.');

                });

              })

            }

            .margin({ bottom: 12 })

          })

        }

        .padding(10)

        .columnsTemplate('1fr 1fr')

        .columnsGap(12)

        .onAreaChange((oldVal, newVal) => {

          this.columns = newVal.width > 600 ? 2 : 1

        })

        .layoutWeight(1)

        .visibility(this.isSearch?Visibility.Visible:Visibility.None)

```

现在我们的效果已经实现


接下来是列表的删除


```c

 Text("清空")

              .fontSize(14)

              .fontColor("#ff989b9b")

              .onClick(()=>{

                promptAction.showDialog({

                  title: '提示',

                  message: '是否清空搜索历史?',

                  buttons: [

                    {

                      text: '确认',

                      color: '#ffffff'

                    },

                    {

                      text: '关闭',

                      color: '#ffffff'

                    }

                  ],

                })

                  .then(data => {

                  this.searchHistoryList.length=0

                    StorageUtils.remove("history_list")

                    console.info('showDialog success, click button: ' + data.index);

                  })

                  .catch((err: Error) => {

                    console.info('showDialog error: ' + err);

                  })


              })

```


到这里我们就实现了商品搜索页的内容



全部代码如下


```c

import { promptAction, router } from '@kit.ArkUI'

import showToast from '../utils/ToastUtils'

import { cloudDatabase } from '@kit.CloudFoundationKit'

import { search_hot_txt } from '../clouddb/search_hot_txt'

import { SearchHotTxt } from '../entity/SearchHotTxt'

import { StorageUtils } from '../utils/StorageUtils'

import { HomeProductList } from '../entity/HomeProductList'

import { ProductDetailModel } from '../model/ProductDetailModel'

import { home_product_list } from '../clouddb/home_product_list'


let databaseZone = cloudDatabase.zone('default');


@Entry

@Component

struct ProductSearchPage {

  @State text: string = ''

  controller: TextInputController = new TextInputController()

  @State searchTxt:SearchHotTxt[]=[]

  @State searchHistoryList:string[]=[]

  @State flag:boolean=false

  @State isSearch:boolean=false

  @State goodsList: HomeProductList[]=[]

  @State columns: number = 2



  async aboutToAppear(): Promise {

    const history = await StorageUtils.getAll("history_list")

    if (history!=''&&history!=undefined) {

      this.searchHistoryList=JSON.parse(history)

    }

    let condition = new cloudDatabase.DatabaseQuery(search_hot_txt);

    let listData = await databaseZone.query(condition);

    let json = JSON.stringify(listData)

    let data1:SearchHotTxt[]= JSON.parse(json)

    this.searchTxt=data1

    this.flag=true

  }


  build() {

    Column(){

      Row(){

        Image($r('app.media.left_back'))

          .height(20)

          .width(20)

          .onClick(()=>{

            router.back()

          })


        TextInput({ text: this.text, placeholder: '输入商品名搜索', controller: this.controller })

          .placeholderColor(Color.White)

          .placeholderFont({ size: 16, weight: 400 })

          .caretColor(Color.White)

          .width(200)

          .fontSize(16)

          .fontColor(Color.White)

          .onChange((value: string) => {

              this.text = value

            if (value.length==0) {

              this.isSearch=false

            }

          })

        Text("搜索")

          .border({width:1,radius:10,color:Color.White})

          .fontSize(14)

          .fontColor("#ffffff")

          .padding({left:13,right:13,top:5,bottom:5})

          .borderRadius(10)

          .onClick(async ()=>{

            if (this.text.trim()==''&&this.text.length==0) {

              this.isSearch=false

              showToast("搜索内容不能为空")

            }else {

                this.isSearch=true

                let home_product=new cloudDatabase.DatabaseQuery(home_product_list);

              home_product.equalTo("name",this.text)

                let list = await databaseZone.query(home_product);

                let json = JSON.stringify(list)

                let data:HomeProductList[]= JSON.parse(json)

                this.goodsList=data

                StorageUtils.set("history_list",this.addToStringList(this.searchHistoryList,this.text))

                const history = await StorageUtils.getAll("history_list")

                if (history!=null&&history!=undefined) {

                  this.searchHistoryList=JSON.parse(history)

                }

            }

          })


      }

      .justifyContent(FlexAlign.SpaceBetween)

      .width('100%')

      .padding({top:10,bottom:10,left:15,right:15})

      .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])

      .backgroundColor("#ff0000")

      Column(){

        Text("热门搜索")

          .width('100%')

          .fontSize(16)

          .fontColor("#000000")

          .fontWeight(FontWeight.Bold)

          .padding({left:15,top:15})

        if (this.flag){

          Flex({wrap:FlexWrap.Wrap}){

            ForEach(this.searchTxt,(item:SearchHotTxt,index:number)=>{

              Text(item.txt)

                .backgroundColor("#ffe7e5e5")

                .fontColor("#000000")

                .fontWeight(FontWeight.Bold)

                .fontSize(16)

                .padding(10)

                .margin({top:10,left:10})

                .borderRadius(5)

                .onClick(()=>{

                  this.text=item.txt

                })


            })

          }


          Row(){

            Text("搜索历史")

              .fontSize(16)

              .fontColor("#000000")

              .fontWeight(FontWeight.Bold)


            Text("清空")

              .fontSize(14)

              .fontColor("#ff989b9b")

              .onClick(()=>{

                promptAction.showDialog({

                  title: '提示',

                  message: '是否清空搜索历史?',

                  buttons: [

                    {

                      text: '确认',

                      color: '#ffffff'

                    },

                    {

                      text: '关闭',

                      color: '#ffffff'

                    }

                  ],

                })

                  .then(data => {

                    this.searchHistoryList.length=0

                    StorageUtils.remove("history_list")

                    console.info('showDialog success, click button: ' + data.index);

                  })

                  .catch((err: Error) => {

                    console.info('showDialog error: ' + err);

                  })


              })

          }

          .width('100%')

          .justifyContent(FlexAlign.SpaceBetween)

          .padding({left:15,right:15})

          .margin({top:30})

          List(){

            ForEach(this.searchHistoryList,(item:string)=>{

              ListItem(){

                Column({space:5}){

                  Text(item)

                    .fontColor("#ffa09a9a")

                    .padding({left:15})

                    .margin({top:10})

                  Divider().width('100%').height(0.8)

                    .color("#e6e6e6")

                }

                .alignItems(HorizontalAlign.Start)

              }

            })

          }

        }

      }

      .layoutWeight(1)

      .width('100%')

      .backgroundColor(Color.White)

      .visibility(this.isSearch?Visibility.None:Visibility.Visible)



        WaterFlow() {

          ForEach(this.goodsList, (item:HomeProductList, index) => {

            FlowItem() {

              Column() {

                Image(item.url)

                  .width('100%')

                  .aspectRatio(1)

                  .objectFit(ImageFit.Cover)

                  .borderRadius({topLeft:10,topRight:10})


                Column() {

                  Text(item.name)

                    .fontSize(16)

                    .fontColor('#333')

                    .margin({ bottom: 4 })


                  Text(item.text_message)

                    .fontSize(12)

                    .fontColor('#666')

                    .margin({ bottom: 8 })



                  Text("最高立减"+item.promotion_spread_price)

                    .fontSize(12)

                    .fontColor('#ffffff')

                    .visibility(item.promotion_spread_price>0?Visibility.Visible:Visibility.None)

                    .margin({ bottom: 8 })

                    .padding({left:5,right:5,top:2,bottom:2})

                    .linearGradient({

                      angle:90,

                      colors: [[0xff0000, 0], [0xff6666, 0.2], [0xff6666, 1]]

                    })


                  Row(){

                    Text("限购")

                      .width(40)

                      .fontSize(12)

                      .borderRadius(20)

                      .backgroundColor("#FB424C")

                      .padding(3)

                      .textAlign(TextAlign.Center)

                    Text("每人限购"+item.max_loop_amount+"件")

                      .margin({left:5})

                      .fontSize(12)

                      .fontColor("#FB424C")

                  }

                  .borderRadius(20)

                  .padding({top:2,bottom:2,right:10})

                  .backgroundColor("#FEE3E3")

                  .visibility(item.amount>0?Visibility.Visible:Visibility.None)


                  Row() {

                    Text(){

                      Span("¥")

                        .fontColor(Color.Red)

                        .fontSize(14)


                      Span(String(item.price))

                        .fontSize(16)

                        .fontColor(Color.Red)

                    }

                    Text(String(item.original_price))

                      .fontSize(12)

                      .fontColor('#999')

                      .decoration({

                        type: TextDecorationType.LineThrough,

                        color: Color.Gray

                      })

                      .margin({left:10})



                    Blank()

                    Column() {

                      Image($r('app.media.cart'))

                        .width(20)

                        .height(20)

                    }

                    .justifyContent(FlexAlign.Center)

                    .width(36)

                    .height(36)

                    .backgroundColor("#ff2bd2fa")

                    .borderRadius(18)

                  }

                  .margin({top:10})

                  .width('100%')

                  .justifyContent(FlexAlign.SpaceBetween)




                }

                .alignItems(HorizontalAlign.Start)

                .padding(12)

              }

              .backgroundColor(Color.White)

              .borderRadius(12)

              .onClick(() => {

                let product: ProductDetailModel = {

                  id: item.id

                };

                router.pushUrl({

                  url: 'pages/component/ProductDetailsPage',

                  params: product

                }, (err) => {

                  if (err) {

                    console.error(`Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`);

                    return;

                  }

                  console.info('Invoke pushUrl succeeded.');

                });

              })

            }

            .margin({ bottom: 12 })

          })

        }

        .padding(10)

        .columnsTemplate('1fr 1fr')

        .columnsGap(12)

        .onAreaChange((oldVal, newVal) => {

          this.columns = newVal.width > 600 ? 2 : 1

        })

        .layoutWeight(1)

        .visibility(this.isSearch?Visibility.Visible:Visibility.None)


    }

    .height('100%')

    .width('100%')

    .backgroundColor(Color.White)


  }




  addToStringList(list: string[], newItem: string): string {

  if (!list.includes(newItem)) {

    list.push(newItem);

  }

  return JSON.stringify(list);

}




}

```


请使用浏览器的分享功能分享到微信等