mondo.bongo
在线周易占卜

指尖卜乾坤,线上悟易理——88box周易算卦工具推荐

华夏文明五千年,《周易》作为群经之首,以其深邃的阴阳辩证思维和精妙的卦象体系,成为跨越时空的智慧瑰宝。从先秦的蓍草占卜到民间流传的三枚铜钱起卦,周易算卦始终是人们探索世事、审视自身的独特方式。而在数字时代,88box推出的在线周易算卦工具 https://88box.top/ai-tools/zhouyi 将传统铜钱卦的智慧与现代互联网技术融合,让千年易理走出古籍、走进指尖,成为普通人轻松接触周易、解惑寻思的优质选择。

这款工具的核心魅力,在于对传统三枚铜钱起卦法的精准还原,让经典占卜方式摆脱了实物限制。三枚铜钱定六爻,是民间流传最广、最适合入门的周易起卦法,其以铜钱的阴阳面定爻象,借六爻组合成卦象,暗合“天圆地方”的宇宙观与“天人感应”的易理核心。在88box的周易算卦工具中,无需准备古铜钱或硬币,只需点击操作,便能模拟传统抛卦的完整过程,从初爻到上爻层层生成,完美复刻“六爻定乾坤”的经典逻辑,让使用者在指尖操作中,感受传统占卜的仪式感与文化底蕴。

同时,工具还贴心设置了随机简化模式,兼顾不同用户的需求。对于周易初学者而言,复杂的起卦步骤往往是入门的阻碍,而简化模式省去了繁琐的手动记爻、定象环节,一键即可生成卦象,快速获得卦象指引;对于有一定易理基础的爱好者,三枚铜钱法的还原模式则能满足其沉浸式的起卦需求,让每一次占问都贴合传统流程,在操作中深化对“阴阳变幻,卦象知祸福”的理解。两种模式随心切换,既保留了周易文化的精髓,又适配了现代快节奏的生活方式,真正做到“雅俗共赏,老少皆宜”。

工具的界面设计简洁凝练,无多余冗杂信息,仅以核心的卦象生成功能和周易经典理念为核心,搭配“易有太极,是生两仪,两仪生四象,四象生八卦”的经典语句,既营造出契合周易文化的清雅氛围,又让操作路径一目了然。无论你是初次接触周易的新手,还是想要随时占问的易理爱好者,都能快速上手,无需花费时间学习复杂的操作流程,只需心怀诚敬,点击“掷卦/生成卦象”,便能揭开卦象之谜,开启与古老智慧的对话。

在快节奏的现代生活中,我们时常会面临选择的困惑、对未来的迷茫,而周易算卦并非单纯的“预测吉凶”,更多的是一种借卦象审视自身、分析处境的思维方式。它让我们在纷繁的世事中,静下心来梳理心念,从阴阳变化、卦象寓意中获得启发,从而更理性地看待问题、做出选择。88box的这款在线周易算卦工具,正是将这种古老的智慧转化为便捷的数字服务,让我们无需奔波寻师、无需钻研晦涩古籍,只需一部手机、一台电脑,便能随时随地借卦象解惑,在指尖的操作中,感受周易文化的博大精深,领悟“变易”“简易”“不易”的人生智慧。

从蓍草在手到指尖点触,变的是占卜的形式,不变的是周易中蕴含的东方智慧。88box周易算卦工具,以科技为桥,连接传统与现代,让千年易理触手可及。如果你也想探索周易的奥秘,在迷茫时寻求一份智慧的指引,不妨打开 https://88box.top/ai-tools/zhouyi 指尖掷卦,于卦象变幻中,寻得内心的答案,悟见生活的方向。

HD Superman
Typeless 真是个学英语的神器

宣传的主要功能是替代打字,用语音输出日常文档 google doc/email 之类。

但发现可以把 typeless 当成学英语的工具,日常 vibe 的时候可以顺便提高英语口语能力。把 translate 改成 to english,平时和 claude code 或者 codex 交流就可以直接用英语描述,如果遇到卡顿的地方,就快捷键切 translate 模式中文描述英文输出,这样可以 vibe 的同时看自己英语不流畅的地方,逐渐英文表达就好起来了。

admin
一个开源的简单易用的linux服务器监控平台

https://gitee.com/confman/linuxtm

支持docker一键部署,简单易用,大家可以试一下

mondo.bongo
从“死了么”看“死不了”:免费安全工具的差异化优势解析

从“死了么”看“死不了”:免费安全工具的差异化优势解析

在独居安全需求日益凸显的当下,“死了么”APP凭借“签到预警”的核心功能成为2026年初现象级产品,但付费门槛、名称争议等问题也让部分用户望而却步。而“死不了”(八八在线工具旗下签到工具,链接:https://88box.top/live-tools/dienotify )作为同类型免费工具,在功能逻辑、使用成本、情感体验上形成差异化优势,更贴合对“零成本安全保障”有需求的用户,以下从多维度对比分析其核心价值。

一、核心差异:免费模式打破“安全刚需”的付费门槛

“死了么”的核心痛点之一是付费买断制,初始定价1元后调整为8元,虽被宣传为“一杯奶茶钱”,但对追求“无成本基础保障”的用户(如学生、老年群体、低收入独居者)仍存在门槛;且其仅支持iOS平台,安卓用户无法使用,进一步限制覆盖范围。

而“死不了”的核心优势在于完全免费+全平台兼容

  1. 零付费门槛:无需任何费用,打开网页即可使用(无需下载APP,避免手机内存占用),无论是学生、退休老人还是临时有安全报备需求的用户,都能无压力接入,真正实现“安全保障不分消费能力”;

  2. 全设备覆盖:基于网页端开发,iOS、安卓、电脑端均可通过浏览器访问,解决“死了么”仅支持苹果系统的局限,尤其适配老年用户常用的安卓手机,扩大适用人群范围;

  3. 无订阅陷阱:不存在“死了么”潜在的后续功能付费(如未来短信通知可能附加成本),“死不了”所有核心功能(签到记录、邮件通知)均免费开放,用户无需担心隐性消费。

二、功能优化:更轻量化的操作,降低使用门槛

两款工具的核心逻辑均为“每日签到+逾期通知”,但“死不了”在操作细节上更贴合“零学习成本”需求,尤其适配老年用户或不熟悉APP操作的群体:

| 对比维度 | “死了么”APP | “死不了”网页工具 |

|----------|-------------|------------------|

| 使用前提 | 需下载、安装APP,仅支持iOS | 无需下载,浏览器打开网页即可使用,全平台兼容 |

| 注册流程 | 需填写姓名+紧急联系人邮箱(部分用户介意姓名填写) | 仅需填写“联系人邮箱”和“昵称”,无需实名,隐私感更强 |

| 核心操作 | 打开APP点击“今日平安”签到 | 网页端点击“活力满满 点击签到”,按钮更大、界面更简洁,老年用户易识别 |

| 状态反馈 | 需进入APP查看签到次数、上次签到时间 | 网页端实时显示“上次签到时间”“签到次数”,无需跳转,信息更直观 |

此外,“死不了”的“无强制授权”优势更明显:无需授权位置信息、通讯录权限,仅通过邮箱传递通知,避免用户对“隐私泄露”的担忧——这一点恰好解决了“死了么”部分用户反馈的“不愿提供过多个人信息”的痛点。

三、情感体验:弱化“死亡”焦虑,强化“生活仪式感”

“死了么”因名称直白引发争议,虽有年轻用户认可其“黑色幽默”,但老年用户或对“死亡”话题敏感的群体(如家属为独居老人设置工具时),易因名称产生心理不适;而“死不了”通过文案设计,弱化焦虑感,更贴合大众情感接受度:

  1. 名称传递正向情绪:“死不了”并非对“死亡”的直接提及,而是以“积极调侃”的语气传递“好好活着”的信念,搭配“记录生活的小确幸,确认一下,今天也好好活着呢 ✨”的文案,将“安全签到”转化为“生活仪式感”,减少用户对“风险预警”的负面联想;

  2. 界面设计更温暖:网页端采用“星光、爪印”等柔和元素,按钮颜色明亮不刺眼,对比“死了么”偏简洁的工具类界面,更易让用户产生“轻松、无压力”的使用感受,尤其适合作为“日常打卡”工具长期使用;

  3. 无“误报焦虑”暗示:“死不了”未强调“连续未签到即触发风险通知”,而是以“从未签到,快来打卡吧~”“主人,欢迎来到签到星球~”等引导式文案,降低用户对“忘记签到会引发麻烦”的心理负担,更易形成长期签到习惯。

四、场景适配:不止独居安全,更贴合“轻量社交”需求

“死了么”衍生出“异地好友互设联系人”的社交场景,但因付费+平台限制,难以快速扩散;而“死不了”的免费+网页属性,更适合低成本的“轻量社交”场景:

  • 异地亲友/情侣:无需对方下载APP,仅需共享网页链接,即可互设为“签到联系人”,每天签到即传递“平安信号”,尤其适合学生党、异地打工人,以“零成本”维系情感联结;

  • 家庭场景:子女可为独居父母收藏“死不了”网页,帮助设置好联系人邮箱(如子女自己的邮箱),父母每天只需打开网页点击签到,无需学习复杂操作,子女也能通过邮箱及时获取平安反馈,避免“老人不会用APP”的尴尬;

  • 自我提醒:用户可将网页设为浏览器书签,每天打开网页签到时,同步记录“小确幸”(如“今天吃了好吃的饭”“和朋友通了电话”),让“安全签到”附加“生活记录”功能,更易长期坚持。

总结:“死不了”的核心价值——免费、轻量、无压力的安全补充工具

对比“死了么”的“付费+APP模式”,“死不了”的核心竞争力在于**“零成本+零门槛+高包容度”** :它不追求“专业救援工具”的定位,而是以“网页轻工具”的形态,覆盖“死了么”未触达的用户群体(如安卓用户、老年用户、付费敏感群体),同时通过情感化设计,让“安全签到”从“风险预警”回归“生活日常”,成为更易被大众接受的“基础安全补充方案”。

对于追求“免费、简单、无隐私顾虑”的用户(如学生、老年独居者、异地亲友),“死不了”无需下载、无需付费、操作直观的特点,使其成为“轻量化安全保障”的优选;而对于需要“短信通知、多联系人设置”等进阶功能的用户,“死了么”(未来将上线短信功能)仍有其价值——两者分别适配“基础需求”与“进阶需求”,共同填补了独居安全工具的市场空白。

mondo.bongo
新人报道 自带产品

做了一个工具站 希望大家可以用起来

或者给点小建议 ,

https://88box.top/

li a
新人报到

无意刷到,新人报道

j2go
gemini 确实很强

把黑客说的界面截图给 gemini 让它用 vue 实现一下,一次就 ok 了,效果还相当可以啊

这是用 wails3 打包的桌面端的结果图

代码我开源在了 https://gitee.com/tiangao/openht

公司不能上传到 github , 回头再同步了

要是有人有兴趣,下一步打算用 github 和 gitee 的 issue api 来接上实现内容流,服务器都省了

Egbert Wang
需要离线部署的平台用什么方案

最近有需求在内网上做一个平台,做一些流程管理方面的工作,平台不大,用的人也不会很多,稳定,方便部署和维护就好。应该用什么样的方案呢?我有一些 Android 开发和 Python、Go 的背景,但是没做过前后端开发。

龙森
纯 C++ 开发 Telegram Bot 框架

各位大佬们 鄙人近期开发了一套 Telegram Bot 框架 我的目标是搞一套类似 QQ机器人框架的这种 目前实现了 TUI 界面 和 一部分的插件功能 现在遇到了一个困难 就是 如何实现 读取插件内的 Token 然后进行网络请求 把数据解析然后发送给该 Token 所属插件的对应事件函数

大概就像 QQ 机器人框架的 易语言 SDK一样 每个事件下方可以实现该事件的具体功能 例如:

// SDK

void OnSendMessageEvent(){
    这里来实现接收到文本消息后的具体操作
}

void OnSendStickerEvent(){
    这是实现接收到贴纸消息后的具体操作
}

本项目完全开源,开源地址: https://github.com/OasisPioneer/StyxTelegramBotFramework 如果可以请大佬们点个star

Dean Su
免费 AI 生图平台推荐:无需注册、无需付费的在线图像生成工具

你是否也在寻找一个真正免费的 AI 生图平台?市面上大多数 AI 图像生成工具如 Midjourney、Stable Diffusion 在线版、DALL-E、Flux1.ai 都需要注册账号或付费订阅,门槛太高。作为一个经常需要快速生成图片的设计师,我深知这种困扰。

  • 有没有免费的 AI 生图工具?

  • 哪个在线图像生成器不需要注册?

  • 无需注册 AI 绘图工具有哪些推荐?

  • 如何免费使用 AI 生成图片?

  • Flux Krea 免费使用是真的吗?

经过长期使用,也因为手上有几张闲着的显卡(如果生图很慢,可能是因为我在打游戏),我开发了一个完全免费的 AI 图像生成平台——Eye Dance,它彻底解决了传统 AI 生图工具的问题:无需注册、无需付费、无需复杂设置,直接输入文字描述就能生成高质量图片。

什么是 Eye Dance

EyeDance.net 是我打造的一个完全免费的 AI 生图平台,支持多种先进的 AI 模型。平台集成了 Flux Krea 免费使用、Flux Kontext 和 Stable Diffusion 在线版三大模型,为用户提供多样化的图像生成选择。

Flux Krea 免费使用版本擅长生成写实风格的高质量图片,细节表现力极强,特别适合产品展示、人像摄影等场景。Flux Kontext 在理解复杂场景描述方面表现出色,能够生成具有丰富背景和故事性的图像。Stable Diffusion 在线版则在艺术风格和创意表达上有着独特优势,支持多种艺术风格和创意概念。

恰好因为手上有几张闲着的显卡,我决定将这些计算资源投入到为更多人提供免费 AI 生图服务中。Eye Dance 的核心特性是:无需注册 AI 绘图、无需付费、无需复杂设置,直接输入文字描述就能生成高质量图片。

你不需要注册任何账号,不需要绑定支付方式,不需要学习复杂的参数设置,更不用担心使用次数限制。这种零门槛的设计让任何人都能立即开始使用这个在线图像生成器。

快速上手

  1. 打开网站 eyedance.net,直接进入生成页面

  2. 在输入框中描述你想要的图片

  3. 点击生成按钮,等待几秒钟

  4. 下载生成的图片

就是这么简单。没有注册流程,没有付费弹窗,没有复杂的参数调整。

实际使用场景

作为设计师,我经常需要快速生成概念图来展示想法。以前需要花时间注册平台、学习参数,现在只需要几秒钟就能得到结果。写文章、做视频需要配图时,Eye Dance 这个免费 AI 生图工具能快速生成符合主题的图片,不需要在版权图片库中大海捞针。

关于免费

很多人会问:为什么 Eye Dance 能完全免费?答案很简单:我恰好拥有几张显著的显卡,这些强大的计算资源让我能够为用户提供高质量的免费服务。我相信 AI 技术应该普惠大众,通过优化技术架构和运营成本,我们能够为用户提供高质量的免费 AI 生图服务。

后续可能会付费吗?可能会付费,但会一直提供免费的使用给大家。我们计划推出付费的高级功能,比如更高的并发限制、优先队列、更多模型选择等,但基础功能将始终保持免费。这样既能保证平台的可持续发展,又能让更多人享受到 AI 图像生成工具推荐的便利。

开始使用

访问 https://eyedance.net 即可开始使用。不需要注册,不需要付费,只需要你的创意。

立即体验免费 AI 生图:

  • 🎨 支持多种艺术风格:写实、动漫、油画、数字艺术

  • 🚀 快速生成:几秒钟即可获得高质量图片

  • 🔒 隐私保护:提示词和图片不会被存储

  • 💰 完全免费:无需注册 AI 绘图、无需付费、无使用限制

  • 🌟 Flux Krea 免费使用:体验最先进的图像生成技术

  • 📱 在线图像生成器:随时随地,打开即用

Eye Dance 将继续优化模型性能,增加更多风格选项,并计划支持更多语言。我的目标是成为最易用、最强大的 AI 图像生成工具推荐平台。

曾经困扰我的 AI 生图问题,在 Eye Dance 这里得到了完美解决。如果你也厌倦了复杂的注册流程和付费门槛,不妨试试这个真正为用户着想的免费 AI 生图产品。

Expector
感觉黑客说好冷清啊

还记得刚注册黑客说账号的时候,每天都回来签到,还经常发些小帖子,一两年过去,只剩偶尔几个月来一次了。但是即使隔几个月来一次,有时进站之后站顶的文章仍是我数十日前发布的,越发感觉荒凉了。

还记得最初我是以找一个功能丰富的技术分享与讨论网站的心态了解到的黑客说,当时还是非常小一个网站,但是ui风格一下就戳中了我。记得当时每日都有十数至数十个新用户到来,我当时以为这个站点会越来越活跃,可如今看到本站虽有数千用户却只有数月才有的寥寥几个帖子,不仅有些感叹。

还记得刚入站时,我在站内认识好几个志趣相投的朋友,可如今我们都已经不再黑客说上交流,转而通过在GitHub上相互欣赏、协助维护各自的开源项目,或微信、qq等方式交流。

哎,脑子想到哪里就说到那里,不知不觉也说了不少废话了。

总之,感谢站长开发并维护了这样一个给我印象颇深的优秀站点。祝愿黑客说越来越好!

Jingzhen
求推荐独立开发快速启动模板

免费付费都可以,感谢大家🫡

Change
《仿盒马》app开发技术分享-- 商品详情页(10)

技术栈

Appgallery connect

开发准备

上一节我们实现了自定义标题栏和商品详情的数据接收,我们已经拿到了想要的数据,这一节我们要丰富商品详情页的内容。商品详情页面我们需要展示的是商品的各个属性参数、商品的图片、商品规格、活动详情等

功能分析

商品详情页面的结构需要我们去用比较多的布局去处理,首先因为商品详情页面对的数据足够多,需要他能够实现滚动查看信息,然后我们需要在底部固定加入购物车和立即购买按钮,并且我们加购之后,我们也要在页面中即使响应购物车中的商品数。同时给到用户便捷跳转到购物车的按钮

代码实现

我们先进行数据的填充,因为上一节我们已经接收到数据,所以我们直接吧数据打印到text上,对着数据进行填充,同时还能帮我们暂时丰富一下页面内容,查看滑动的效果,页面完善之后我们再去删掉即可

Text(JSON.stringify(this.productParams))

          .fontColor(Color.Black)

然后我们根据设计的样式进行数据填充,要注意滚动和底部布局的固定,挑选合适的布局容器

Stack({alignContent:Alignment.Bottom}){

  Scroll(this.scroller){

      Column() {

        CommonTopBar({ title: "商品详情", alpha: 0, titleAlignment: TextAlign.Center ,backButton:true})

        Image(this.productParams.url)

          .width('100%')

          .height(300)

        Text(JSON.stringify(this.productParams))

          .fontColor(Color.Black)

        Column({space:10}){

          Row(){

            if (this.productParams.promotion_spread_price>0){

              Text(){

                Span("¥")

                  .fontSize(14)

                  .fontColor(Color.Red)

                Span(this.productParams.promotion_spread_price+"")

                  .fontSize(20)

                  .fontColor(Color.Red)

              }

            }else {

              Text(){

                Span("¥")

                  .fontSize(14)

                  .fontColor(Color.Red)

                Span(this.productParams.price+"")

                  .fontSize(20)

                  .fontColor(Color.Red)

              }

            }

            Text("¥"+this.productParams.original_price+"")

              .fontColor('#999')

              .decoration({

                type: TextDecorationType.LineThrough,

                color: Color.Gray

              })

              .fontSize(16)

              .margin({left:10})

            if (this.productParams.promotion_spread_price>0){

              Row(){

                Text("每件立减"+(this.productParams.price-this.productParams.promotion_spread_price)+"元")

                  .fontSize(14)

                  .borderRadius(20)

                  .backgroundColor("#FB424C")

                  .padding(3)

                  .textAlign(TextAlign.Center)

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

                  .margin({left:5})

                  .fontSize(14)

                  .borderRadius(20)

                  .backgroundColor("#FB424C")

                  .padding(3)

                  .textAlign(TextAlign.Center)

              }

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

            }

          }

          .padding(10)

          if (this.productParams.promotion_spread_price>0){

            Text(this.productParams.endTime)

              .fontSize(14)

              .borderRadius(20)

              .backgroundColor("#FB424C")

              .padding(3)

              .margin({left:10})

              .textAlign(TextAlign.Center)

          }

          Text(this.productParams.name)

            .fontSize(20)

            .fontColor(Color.Black)

            .margin({left:10})

            .fontWeight(FontWeight.Bold)

          Text(this.productParams.text_message)

            .fontSize(14)

            .fontColor(Color.Black)

            .margin({left:10})

          Row(){

            Text()

            Text("销量 "+this.productParams.sales_volume)

              .fontSize(14)

              .fontColor(Color.Black)

          }

          .padding(10)

          .width('100%')

          .justifyContent(FlexAlign.SpaceBetween)

          Divider().width('100%')

            .height(5).backgroundColor("#f7f7f7")

          Row(){

            Text("发货")

              .fontColor(Color.Gray)

              .fontSize(14)

            Text(this.productParams.delivery_time+"")

              .fontSize(14)

              .margin({left:20})

              .fontColor(Color.Black)

          }

          .padding(10)

          .width('100%')

          .justifyContent(FlexAlign.Start)

          Divider().width('100%')

            .height(5).backgroundColor("#f7f7f7")

          Row(){

            Text("参数")

              .fontColor(Color.Gray)

              .fontSize(14)

            Text("储藏条件:")

              .margin({left:20})

              .fontSize(14)

              .fontColor(Color.Black)

            Text(this.productParams.parameter)

              .fontSize(14)

              .fontColor(Color.Black)

          }

          .padding(10)

          .width('100%')

          .justifyContent(FlexAlign.Start)

          Divider().width('100%')

            .height(5).backgroundColor("#f7f7f7")

          Row(){

            Text("规格")

              .fontColor(Color.Gray)

              .fontSize(14)

            Column(){

              Text("请选择规格")

            }

          }

          .padding(10)

          .width('100%')

          .justifyContent(FlexAlign.Start)

          Divider().width('100%')

            .height(5).backgroundColor("#f7f7f7")

        }

        .alignItems(HorizontalAlign.Start)

      }

      .alignItems(HorizontalAlign.Start)

      .backgroundColor(Color.White)

  }

  .padding({bottom:80})

  .height('100%')

  .width('100%')

  Row(){

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

      .width(35)

      .height(35)

      .objectFit(ImageFit.Contain)

    Blank()

    Text("加入购物车")

      .padding(10)

      .width(100)

      .textAlign(TextAlign.Center)

      .backgroundColor("#FCDB29")

      .fontColor(Color.White)

      .borderRadius({topLeft:15,bottomLeft:15})

    Text(" 立即购买 ")

      .padding(10)

      .textAlign(TextAlign.Center)

      .width(100)

      .backgroundColor(Color.Red)

      .fontColor(Color.White)

      .borderRadius({topRight:15,bottomRight:15})

  }

  .padding(15)

  .justifyContent(FlexAlign.SpaceBetween)

  .width('100%')

  .backgroundColor(Color.White)

}

.backgroundColor(Color.White)

到这里我们的商品详情页面的内容已经比较完善了

Change
《仿盒马》app开发技术分享-- 自定义标题栏&商品详情初探(9)

技术栈

Appgallery connect

开发准备

上一节我们实现了顶部toolbar的地址选择,会员码展示,首页的静态页面就先告一段落,这节我们来实现商品列表item的点击传值、自定义标题栏。

功能分析

1.自定义标题栏

当我们进入二级三级页面的时候,就需要向用户介绍我们当前的页面信息,标题栏很好的实现了这个效果,并且进入的页面级别过多,也要给用户一个可点击的退出按钮。当然了,有些页面是不需要有返回按钮的,这里我们还要兼顾通用性。

2.页面间传值

页面之前的数据传递,是app中比较常见也是比较重要的知识点,这里我们通过点击列表的条目进行数据的传递,然后在详情页进行数据的接收

代码实现

自定义标题栏

import router from '@ohos.router'

@Component

export struct CommonTopBar {

@Prop title: string

@Prop alpha: number

private titleAlignment: TextAlign = TextAlign.Center

private backButton: boolean = true

private onBackClick?: () => void

build() {

Column() {

  Blank()

    .backgroundColor(Color.Red)

    .opacity(this.alpha)

  Stack({ alignContent: Alignment.Start }) {

    Stack()

      .height(50)

      .width("100%")

      .opacity(this.alpha)

      .backgroundColor(Color.Red)

    Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {

      Text(this.title)

        .flexGrow(1)

        .textAlign(this.titleAlignment)

        .fontSize(18)

        .fontColor(Color.Black)

        .align(Alignment.Center)

        .maxLines(1)

        .textOverflow({ overflow: TextOverflow.Ellipsis })

    }

    .height(50)

    .margin({ left: 50, right: 50 })

    .alignSelf(ItemAlign.Center)

    if (this.backButton) {

        Stack() {

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

            .height(16)

            .width(12)

            .objectFit(ImageFit.Contain)

            .align(Alignment.Center)

        }

        .onClick(() => {

          this.onBackClick?.()

          router.back();

        })

        .height(50)

        .width(50)

    }

  }

  .height(50)

  .width("100%")

  Divider().strokeWidth(0.5).color("#E6E6E6")

}.backgroundColor(Color.White)

.height(51)

}

}

在标题栏中我们使用了一些逻辑判断,并且设置标题是外部传入的,而且还预留了一个事件的回调,这能让我们的标题栏更加的灵活

页面间传值

首先我们需要创建一个商品详情页的页面,然后把我们的自定义标题栏引入进去

import { CommonTopBar } from '../widget/CommonTopBar';

@Entry

@Component

struct ProductDetailsPage {

build() {

Column() {

  CommonTopBar({ title: "商品详情", alpha: 0, titleAlignment: TextAlign.Center ,backButton:true})

}

.height('100%')

.width('100%')

}

}

然后在商品流的点击事件里使用router

.onClick(() => {

        router.pushUrl({

          url: 'pages/component/ProductDetailsPage',

          params: item

        }, (err) => {

          if (err) {

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

            return;

          }

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

        });

      })

      

这里我们把整个item的信息都传递过去,方便我们使用

接收

@State receivedParams: HomeProductList = {} as HomeProductList;

aboutToAppear(): void {

let order= router.getParams() as HomeProductList;

console.info('Received params:',order);

}

在页面上我们先展示出来

Text(JSON.stringify(this.receivedParams))

    .fontColor(Color.Black)

到这里我们就实现了本节的内容了,下一节我们将要丰富商品详情页的内容

Change
《仿盒马》app开发技术分享-- 首页地址选择&会员码(8)

技术栈

Appgallery connect

开发准备

上一节我们实现了商品流标的创建,数据的填充和展示,并且在商品信息表中添加了许多我们后去需要使用到的参数。让我们的首页功能更加的丰富,截至目前首页板块可以说是完成了百分之五十了,跟展示有关的基本都已完成,接下来就是我们对业务逻辑的完善,当然了我们的首页内容还缺少很多,这一节我们来把顶部toolbar的地址选择,会员码展示实现一下。

功能分析

1.地址选择

地址选择我们需要实现的是省市区街道的选择,当我们点击街道信息后,根据区域的不同,我们可能会调整首页相应的活动板块修改,以及不同模块的展示,比如我们的新人领券活动,我们仅在A区域开展活动,当我们切换的B区域就会关闭相应的功能展示。同时我们下次登陆需要加载上一次选中的地址,要实现这个功能我们还需要把地址信息存储到本地。

2.会员码

会员码这个就比较的简单,我们只需要把条形码跟二维码结合用户的id生成,(因为暂时没有登陆功能,所以我们要模拟一下)在进入页面的时候把条形码加载到页面上即可。

代码实现

地址选择

因为鸿蒙中是自带这个组建的,所以我们直接在点击事件中去调用即可

let districtSelectOptions: sceneMap.DistrictSelectOptions= {

      countryCode: "CN",

      subWindowEnabled: false

    };

    sceneMap.selectDistrict(getContext(this), districtSelectOptions).then((data) => {

      if (data.districts.length>5){

        this.locationName=data.districts[5].name!

      }else {

        this.locationName=data.districts[4].name!

      }

      console.info("SelectDistrict", "Succeeded in selecting district."+data);

    }).catch((err: BusinessError) => {

});

然后我们执行一下代码拉起地区选择的页面

然后我们实现会员码页面,这个页面就是一个一维码跟二维码的展示

因为系统不支持直接生成一维码,所以我们用到scankit ,二维码用原生

import { scanCore, generateBarcode } from '@kit.ScanKit';

import { BusinessError } from '@kit.BasicServicesKit';

import { image } from '@kit.ImageKit';

@Entry

@Component

struct QRCodePage {

@State content: string = '1122334455';

@State pixelMap: image.PixelMap | undefined = undefined

aboutToAppear(): void {

this.pixelMap = undefined;

let options: generateBarcode.CreateOptions = {

  scanType: scanCore.ScanType.CODE39_CODE,

  height:200,

  width: 400

}

try {

  generateBarcode.createBarcode(this.content, options).then((pixelMap: image.PixelMap) => {

    this.pixelMap = pixelMap;

  }).catch((error: BusinessError) => {

  })

} catch (error) {

}

}

build() {

Column() {

  Column(){

    if (this.pixelMap) {

      Image(this.pixelMap).width('90%').height(70).objectFit(ImageFit.Fill)

      QRCode(this.content).color(Color.Black).width('90%').height(140)

        .margin({top:20})

    }

  }

  .width('80%')

  .backgroundColor("#ffffff")

  .borderRadius(10)

  .padding(10)

  .alignItems(HorizontalAlign.Center)

  .justifyContent(FlexAlign.Center)

}

.backgroundColor("#ffeceaea")

.width('100%')

.height('100%')

}

}

这样就实现了对应的内容了

Change
《仿盒马》app开发技术分享-- 首页商品流(7)

技术栈

Appgallery connect

开发准备

上一节我们实现了首页banner模块的功能,现在我们的首页还需要添加商品列表,作为一个购物类应用,商品列表是非常重要的一个模块,所以我们尽量把它设计的足够完善,参数能更好的支持我们后期复杂的逻辑,它需要有图片的展示,适配的优惠券列表,限购,立减,划线价等,但他实际的参数还要更多,因为我们的列表是比较紧凑的,更多的数据需要从点击后的商品详情页展示出来。

代码实现

创建商品表

{

"objectTypeName": "home_product_list",

"fields": [

{"fieldName": "id", "fieldType": "Integer", "notNull": true, "belongPrimaryKey": true},

{"fieldName": "goods_list_id", "fieldType": "Integer", "notNull": true, "defaultValue": 0},

{"fieldName": "url", "fieldType": "String"},

{"fieldName": "name", "fieldType": "Text"},

{"fieldName": "price", "fieldType": "Double"},

{"fieldName": "original_price", "fieldType": "Double"},

{"fieldName": "amount", "fieldType": "Integer"},

{"fieldName": "text_message", "fieldType": "String"},

{"fieldName": "parameter", "fieldType": "String"},

{"fieldName": "delivery_time", "fieldType": "String"},

{"fieldName": "endTime", "fieldType": "String"},

{"fieldName": "sales_volume", "fieldType": "Integer"},

{"fieldName": "space_id", "fieldType": "Integer"},

{"fieldName": "max_loop_amount", "fieldType": "Integer"},

{"fieldName": "promotion_spread_price", "fieldType": "Double"},

{"fieldName": "coupon_id", "fieldType": "Integer"}

],

"indexes": [

{"indexName": "field1IndexId", "indexList": [{"fieldName":"id","sortType":"ASC"}]}

],

"permissions": [

{"role": "World", "rights": ["Read"]},

{"role": "Authenticated", "rights": ["Read", "Upsert"]},

{"role": "Creator", "rights": ["Read", "Upsert", "Delete"]},

{"role": "Administrator", "rights": ["Read", "Upsert", "Delete"]}

]

}

填充数据

{

"cloudDBZoneName": "default",

"objectTypeName": "home_product_list",

"objects": [

{

  "id": 10,

  "goods_list_id": 1,

  "url": "在线图片链接",

  "name": "红颜草莓",

  "price": 10.5,

  "original_price": 18.5,

  "amount": 10,

  "text_message": "特价",

  "parameter": "冷藏",

  "delivery_time": "付款后24小时内发货",

  "endTime": "直降 | 结束时间2025年5月18日 10:00",

  "sales_volume": 9812,

  "space_id": 10,

  "max_loop_amount": 10,

  "promotion_spread_price": 5,

  "coupon_id": 10

},

{

  "id": 20,

  "goods_list_id": 1,

  "url": "在线图片链接",

  "name": "麒麟瓜",

  "price": 2.8,

  "original_price": 5.9,

  "amount": 1,

  "text_message": "当季新品",

  "parameter": "冷藏",

  "delivery_time": "付款后24小时内发货",

  "endTime": "直降 | 结束时间2025年5月18日 10:00",

  "sales_volume": 9812,

  "space_id": 11,

  "max_loop_amount": 10,

  "promotion_spread_price": 0,

  "coupon_id": 10

}

]

}

我们接下来进行数据的查询

@State homeProduct:HomeProductList[]=[]//商品流数据

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

  let home_product=new cloudDatabase.DatabaseQuery(home_product_list);

  let list7 = await databaseZone.query(home_product);

  let json7 = JSON.stringify(list7)

  let data7:HomeProductList[]= JSON.parse(json7)

  this.homeProduct=data7

数据查出完成后,完善商品流的页面

import { HomeProductList } from "../entity/home_product_list"

@Component

@Preview

export struct WaterFlowGoods {

@Link goodsList: Array<HomeProductList>

@State columns: number = 2

build() {

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(() => {

      })

    }

    .margin({ bottom: 12 })

  })

}

.padding(10)

.columnsTemplate('1fr 1fr')

.columnsGap(12)

.onAreaChange((oldVal, newVal) => {

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

})

}

}

然后在首页调用,传入参数

WaterFlowGoods({goodsList:this.homeProduct})

到这里我们就实现了首页商品列表的内容

Change
《仿盒马》app开发技术分享-- 首页banner(6)

技术栈

Appgallery connect

开发准备

上一篇文章中我们实现了项目端云一体化首页商品活动入口列表,现在我们还差一个banner的模块,banner模块不仅可以用于展示一些信息,还可以在点击之后进行,跳转,弹窗,升级提示,信息提示等作用,我们直接坐的完善一些,因为我们事先在banner表中添加了action,我们通过这个action的值来进行对应的处理,同时通过islogin字段来判断是否需要登陆操作

代码实现

创建banner表

{

"objectTypeName": "home_banner",

"fields": [

{"fieldName": "id", "fieldType": "Integer", "notNull": true, "belongPrimaryKey": true},

{"fieldName": "banner_id", "fieldType": "Integer", "notNull": true, "defaultValue": 0},

{"fieldName": "url", "fieldType": "String"},

{"fieldName": "is_login", "fieldType": "Boolean"},

{"fieldName": "router", "fieldType": "Boolean"},

{"fieldName": "action_id", "fieldType": "Integer"},

{"fieldName": "action", "fieldType": "String"}

],

"indexes": [

{"indexName": "banner_id", "indexList": [{"fieldName":"banner_id","sortType":"ASC"}]}

],

"permissions": [

{"role": "World", "rights": ["Read"]},

{"role": "Authenticated", "rights": ["Read", "Upsert"]},

{"role": "Creator", "rights": ["Read", "Upsert", "Delete"]},

{"role": "Administrator", "rights": ["Read", "Upsert", "Delete"]}

]

}

填充数据

banner

{

"cloudDBZoneName": "default",

"objectTypeName": "home_banner",

"objects": [

{

  "id": 10,

  "banner_id": 1,

  "url": "在线图片链接",

  "is_login": true,

  "router": "",

  "action_id": 10,

  "action": "toast"

},

{

  "id": 20,

  "banner_id": 0,

  "url": "在线图片链接",

  "is_login": false,

  "router": "",

  "action_id": 20,

  "action": "dialog"

}

]

}

由于我们缺少banner相关的内容,所以我们还需要创建一个banner的页面

import { HomeBanner } from "../entity/HomeBanner"

import showToast from "../utils/ToastUtils"

@Component

@Preview

export struct HomeBannerPage {

//数据源

@Link bannerList:HomeBanner[]

//tabs 当前数据源的下标

@State swpIndex:number=1

build() {

Column() {

        Swiper(){

          ForEach(this.bannerList, (item: HomeBanner) => {

            Image(item.url)

              .width('100%')

              .height(130)

              .borderRadius(10)

              .onClick(()=>{

                if (item.action=='toast') {

                  showToast("1111")

                }

                if (item.action=='dialog') {

                }

              })

          })

        }

        .borderRadius(10)

        .loop(true)

        .indicator(true)

        .height(130)

        .onChange((index: number) => {

          this.swpIndex=index+1

        })

}

.padding(10)

.margin({top:10})

}

}

我们先判断是否需要is_login,然后根据action去判断,到这里我们就实现了banner的内容

Change
《仿盒马》app开发技术分享-- 首页活动配置(5)

技术栈

Appgallery connect

开发准备

上一篇文章中我们实现了项目端云一体化首页部分模块动态配置,实现了对模块模块的后端控制显示和隐藏,这能让我们的app更加的灵活,也能应对更多的情况。现在我们来对配置模块进行完善,除了已有的模块以外,我们还有一些banner ,活动入口等模块,这些模块的数据并不多,所以我们也归纳到配置中去实现。并且我们在配置表中添加了一些不同的id,我们只需要根据相对应的id 去查询对应的表就可以了

代码实现

实现横幅海报,商品活动入口

创建海报横幅表

{

"objectTypeName": "home_poster",

"fields": [

{"fieldName": "id", "fieldType": "Integer", "notNull": true, "belongPrimaryKey": true},

{"fieldName": "poster_id", "fieldType": "Integer", "notNull": true, "defaultValue": 0},

{"fieldName": "url", "fieldType": "String"},

{"fieldName": "router", "fieldType": "String"}

],

"indexes": [

{"indexName": "posterIdIndex", "indexList": [{"fieldName":"poster_id","sortType":"ASC"}]}

],

"permissions": [

{"role": "World", "rights": ["Read"]},

{"role": "Authenticated", "rights": ["Read", "Upsert"]},

{"role": "Creator", "rights": ["Read", "Upsert", "Delete"]},

{"role": "Administrator", "rights": ["Read", "Upsert", "Delete"]}

]

}

创建商品活动入口表

{

"objectTypeName": "home_good_center",

"fields": [

{"fieldName": "id", "fieldType": "Integer", "notNull": true, "belongPrimaryKey": true},

{"fieldName": "good_left_id", "fieldType": "Integer", "notNull": true, "defaultValue": 0},

{"fieldName": "title", "fieldType": "String"},

{"fieldName": "url", "fieldType": "String"}

],

"indexes": [

{"indexName": "goodLeftIdIndex", "indexList": [{"fieldName":"good_left_id","sortType":"ASC"}]}

],

"permissions": [

{"role": "World", "rights": ["Read"]},

{"role": "Authenticated", "rights": ["Read", "Upsert"]},

{"role": "Creator", "rights": ["Read", "Upsert", "Delete"]},

{"role": "Administrator", "rights": ["Read", "Upsert", "Delete"]}

]

}

分别填充数据

海报

{

"cloudDBZoneName": "default",

"objectTypeName": "home_poster",

"objects": [

{

  "id": 10,

  "poster_id": 1,

  "url": "在线图片链接",

  "router": "string1"

}

]

}

商品活动入口

{

"cloudDBZoneName": "default",

"objectTypeName": "home_good_center",

"objects": [

{

  "id": 10,

  "good_left_id": 1,

  "title": "生鲜严选",

  "url": "在线图片链接"

},

{

  "id": 20,

  "good_left_id": 1,

  "title": "西购新品",

  "url": "在线图片链接"

},

{

  "id": 30,

  "good_left_id": 1,

  "title": "今日推荐",

  "url": "在线图片链接"

}

]

}

都填充完成后,我们把数据提交到云端,然后进行配置类的同步

接下来我们进行数据查询,因为我们在配置表中添加了id ,所以我们要查询出对应id的活动入口。

@State homeActivity:HomeActivitySetting[]=[]//首页活动配置

@State homeGoodCenter:HomeGoodCenter[]=[]//商品活动入口

let listData3 = await databaseZone.query(condition3);

  let json3 = JSON.stringify(listData3)

  let data3:HomeActivitySetting[]= JSON.parse(json3)

  this.homeActivity=data3

  hilog.error(0x0000, 'testTag', `Failed to query data, code: ${this.homeActivity}`);

  let list5 = await databaseZone.query(home_good);

  home_good.equalTo("good_left_id",data3[0].good_left_id);

  let json5 = JSON.stringify(list5)

  let data5:HomeGoodCenter[]= JSON.parse(json5)

  this.homeGoodCenter=data5

  hilog.error(0x0000, 'testTag', `Failed to query data, code: ${this.homeGoodCenter}`);

然后我们修改一下商品活动入口的内容

import { HomeGoodCenter } from "../entity/HomeGoodCenter"

@Component

@Preview

export struct SpecialColumn {

@Link goodInfo: HomeGoodCenter[]

build() {

Column(){

  List({space:10}){

    ForEach(this.goodInfo,(data:HomeGoodCenter)=>{

      ListItem(){

        Column(){

          Text(data.title)

            .fontSize(16)

            .fontWeight(FontWeight.Bold)

            .fontColor(Color.Black)

          Blank()

          Image(data.url)

            .width('28%')

            .height(90)

            .margin({ bottom: 8 })

            .objectFit(ImageFit.Cover)

        }

        .borderRadius(5)

        .backgroundColor("#ffeedeb8")

        .padding(5)

      }

    })

  }

  .listDirection(Axis.Horizontal)

}

.margin({top:10})

}

}

在首页进行调用

SpecialColumn({goodInfo:this.homeGoodCenter})

到这里我们就实现了活动配置相关的内容

Change
《仿盒马》app开发技术分享-- 首页模块配置(4)

技术栈

Appgallery connect

开发准备

上一篇文章中我们实现了项目端云一体化金刚区活动模块,数据也成功的从云端获取,并且我们跟ScrollBar进行关联,能够让用户直观的查看当前滑动的位置。现在我们开始继续向下,随着我们首页的内容越来越多,我们如果因为某些业务需要进行调整和下线,想隐藏和关掉某些模块,就需要每次在打包的时候进行处理,这很明显会非常的麻烦,现在我们通过一张表来对首页的整个模块进行控制,这样每次做展示的时候去进行查询当前状态来实现想要的效果。

代码实现

首先我们先创建一个表把所有的关联id 以及模块的状态字段定义一下

{

"objectTypeName": "home_activity_setting",

"fields": [

{"fieldName": "id", "fieldType": "Integer", "notNull": true, "belongPrimaryKey": true},

{"fieldName": "poster_id", "fieldType": "Integer"},

{"fieldName": "banner_id", "fieldType": "Integer"},

{"fieldName": "good_left_id", "fieldType": "Integer"},

{"fieldName": "good_right_id", "fieldType": "Integer"},

{"fieldName": "goods_list_id", "fieldType": "Integer"},

{"fieldName": "new_people_status", "fieldType": "Boolean"},

{"fieldName": "split_layout_status", "fieldType": "Boolean"},

{"fieldName": "banner_status", "fieldType": "Boolean"},

{"fieldName": "goods_list_status", "fieldType": "Boolean"}

],

"indexes": [

{"indexName": "field1IndexId", "indexList": [{"fieldName":"id","sortType":"ASC"}]}

],

"permissions": [

{"role": "World", "rights": ["Read"]},

{"role": "Authenticated", "rights": ["Read", "Upsert"]},

{"role": "Creator", "rights": ["Read", "Upsert", "Delete"]},

{"role": "Administrator", "rights": ["Read", "Upsert", "Delete"]}

]

}

然后我们创建数据

{

"cloudDBZoneName": "default",

"objectTypeName": "home_activity_setting",

"objects": [

{

  "id": 1,

  "poster_id": 1,

  "banner_id": 1,

  "good_left_id": 1,

  "good_right_id": 1,

  "goods_list_id": 1,

  "new_people_status": true,

  "split_layout_status": true,

  "banner_status": true,

  "goods_list_status": true

}

]

}

现在进行实体类的创建

export class HomeActivitySetting {

id: number;

poster_id: number;

banner_id: number;

good_left_id: number;

good_right_id: number;

goods_list_id: number;

new_people_status: boolean;

split_layout_status: boolean;

banner_status: boolean;

goods_list_status: boolean;

constructor() {

}

getFieldTypeMap():  Map<string, string> {

    let fieldTypeMap = new Map<string, string>();

    fieldTypeMap.set('id', 'Integer');

    fieldTypeMap.set('poster_id', 'Integer');

    fieldTypeMap.set('banner_id', 'Integer');

    fieldTypeMap.set('good_left_id', 'Integer');

    fieldTypeMap.set('good_right_id', 'Integer');

    fieldTypeMap.set('goods_list_id', 'Integer');

    fieldTypeMap.set('new_people_status', 'Boolean');

    fieldTypeMap.set('split_layout_status', 'Boolean');

    fieldTypeMap.set('banner_status', 'Boolean');

    fieldTypeMap.set('goods_list_status', 'Boolean');

    return fieldTypeMap;

}

getClassName(): string {

    return 'home_activity_setting';

}

getPrimaryKeyList(): string[] {

    let primaryKeyList: string[] = [];

    primaryKeyList.push('id');

    return primaryKeyList;

}

getIndexList(): string[] {

    let indexList: string[] = [];

    indexList.push('id');

    return indexList;

}

getEncryptedFieldList(): string[] {

    let encryptedFieldList: string[] = [];

    return encryptedFieldList;

}

setId(id: number): void {

    this.id = id;

}

getId(): number  {

    return this.id;

}

setPoster_id(poster_id: number): void {

    this.poster_id = poster_id;

}

getPoster_id(): number  {

    return this.poster_id;

}

setBanner_id(banner_id: number): void {

    this.banner_id = banner_id;

}

getBanner_id(): number  {

    return this.banner_id;

}

setGood_left_id(good_left_id: number): void {

    this.good_left_id = good_left_id;

}

getGood_left_id(): number  {

    return this.good_left_id;

}

setGood_right_id(good_right_id: number): void {

    this.good_right_id = good_right_id;

}

getGood_right_id(): number  {

    return this.good_right_id;

}

setGoods_list_id(goods_list_id: number): void {

    this.goods_list_id = goods_list_id;

}

getGoods_list_id(): number  {

    return this.goods_list_id;

}

setNew_people_status(new_people_status: boolean): void {

    this.new_people_status = new_people_status;

}

getNew_people_status(): boolean  {

    return this.new_people_status;

}

setSplit_layout_status(split_layout_status: boolean): void {

    this.split_layout_status = split_layout_status;

}

getSplit_layout_status(): boolean  {

    return this.split_layout_status;

}

setBanner_status(banner_status: boolean): void {

    this.banner_status = banner_status;

}

getBanner_status(): boolean  {

    return this.banner_status;

}

setGoods_list_status(goods_list_status: boolean): void {

    this.goods_list_status = goods_list_status;

}

getGoods_list_status(): boolean  {

    return this.goods_list_status;

}

static parseFrom(inputObject: any): HomeActivitySetting {

    let result = new HomeActivitySetting();

    if (!inputObject) {

        return result;

    }

    if (inputObject.id) {

        result.id = inputObject.id;

    }

    if (inputObject.poster_id) {

        result.poster_id = inputObject.poster_id;

    }

    if (inputObject.banner_id) {

        result.banner_id = inputObject.banner_id;

    }

    if (inputObject.good_left_id) {

        result.good_left_id = inputObject.good_left_id;

    }

    if (inputObject.good_right_id) {

        result.good_right_id = inputObject.good_right_id;

    }

    if (inputObject.goods_list_id) {

        result.goods_list_id = inputObject.goods_list_id;

    }

    if (inputObject.new_people_status) {

        result.new_people_status = inputObject.new_people_status;

    }

    if (inputObject.split_layout_status) {

        result.split_layout_status = inputObject.split_layout_status;

    }

    if (inputObject.banner_status) {

        result.banner_status = inputObject.banner_status;

    }

    if (inputObject.goods_list_status) {

        result.goods_list_status = inputObject.goods_list_status;

    }

    return result;

}

}

db类

import { cloudDatabase } from '@kit.CloudFoundationKit';

class home_activity_setting extends cloudDatabase.DatabaseObject {

public id: number;

public poster_id: number;

public banner_id: number;

public good_left_id: number;

public good_right_id: number;

public goods_list_id: number;

public new_people_status: boolean;

public split_layout_status: boolean;

public banner_status: boolean;

public goods_list_status: boolean;

public naturalbase_ClassName(): string {

return 'home_activity_setting';

}

}

export { home_activity_setting };

创建完成之后,记得要在开发工具中把数据提交到云端,我们把数据提交到云端后,可以看到数据提交成功并且表也已经创建完成

进行数据查询

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

  let condition3 = new cloudDatabase.DatabaseQuery(home_activity_setting);

let listData3 = await databaseZone.query(condition3);

let json3 = JSON.stringify(listData3)

let data3:HomeActivitySetting[]= JSON.parse(json3)

this.homeActivity=data3

} catch (err) {

hilog.error(0x0000, 'testTag', Failed to query data, code: ${err.code}, message: ${err.message});

}

数据也已经查询成功,接下来我们直接运行程序,可以看到金刚区是在显示中的,接下来我们修改金刚区的状态,再执行一下,可以看到金刚区已经不见了,到这里我们首页模块的显示隐藏配置已经实现了。

Change
《仿盒马》app开发技术分享-- 金刚区(3)

技术栈

Appgallery connect

开发准备

上一篇文章中我们实现了项目端云一体化新人专享券活动模块,数据也成功的从云端获取,现在我们开始继续向下,实现金刚区模块

功能分析

金刚区的实现我们之前已经完成了,但是数据的获取都是本地的静态数据,现在我们要获取云端的数据,实现数据的展示,同时要把滚动跟bar 关联起来,让用户能看到当前滑动到什么位置

代码实现

首先我们进行表、数据、实体、db类的创建

{

"objectTypeName": "split_layout",

"fields": [

{"fieldName": "split_id", "fieldType": "Integer", "notNull": true, "belongPrimaryKey": true},

{"fieldName": "txt", "fieldType": "String"},

{"fieldName": "url", "fieldType": "String"},

{"fieldName": "router", "fieldType": "String"},

{"fieldName": "is_login", "fieldType": "Boolean"},

{"fieldName": "bt_state", "fieldType": "Integer"}

],

"indexes": [

{"indexName": "splitId_Index", "indexList": [{"fieldName":"split_id","sortType":"ASC"}]}

],

"permissions": [

{"role": "World", "rights": ["Read"]},

{"role": "Authenticated", "rights": ["Read", "Upsert"]},

{"role": "Creator", "rights": ["Read", "Upsert", "Delete"]},

{"role": "Administrator", "rights": ["Read", "Upsert", "Delete"]}

]

}

数据

{

"cloudDBZoneName": "default",

"objectTypeName": "split_layout",

"objects": [

{

  "split_id": 10,

  "txt": "果蔬肉禽",

  "url": "在线图片链接",

  "router": "string1",

  "is_login": false,

  "bt_state": 0

},

{

  "split_id": 20,

  "txt": "冷冻水产",

  "url": "在线图片链接",

  "router": "string2",

  "is_login": false,

  "bt_state": 0

},

{

  "split_id": 30,

  "txt": "乳品烘焙",

  "url": "在线图片链接",

  "router": "string2",

  "is_login": false,

  "bt_state": 0

},

{

  "split_id": 40,

  "txt": "粮油面点",

  "url": "在线图片链接",

  "router": "string2",

  "is_login": false,

  "bt_state": 0

},

{

  "split_id": 50,

  "txt": "酒水饮料",

  "url": "在线图片链接",

  "router": "string2",

  "is_login": false,

  "bt_state": 0

},

{

  "split_id": 60,

  "txt": "休闲零食",

  "url": "在线图片链接",

  "router": "string2",

  "is_login": false,

  "bt_state": 0

},

{

  "split_id": 70,

  "txt": "婴宠保健",

  "url": "在线图片链接",

  "router": "string2",

  "is_login": false,

  "bt_state": 0

},

{

  "split_id": 80,

  "txt": "美妆个护",

  "url": "在线图片链接",

  "router": "string2",

  "is_login": false,

  "bt_state": 0

},

{

  "split_id": 90,

  "txt": "纸品清洁",

  "url": "在线图片链接",

  "router": "string2",

  "is_login": false,

  "bt_state": 0

},

{

  "split_id": 101,

  "txt": "百货家电",

  "url": "在线图片链接",

  "router": "string2",

  "is_login": false,

  "bt_state": 0

},

{

  "split_id": 102,

  "txt": "家纺服饰",

  "url": "在线图片链接",

  "router": "string2",

  "is_login": false,

  "bt_state": 0

},

{

  "split_id": 201,

  "txt": "跨境免税",

  "url": "在线图片链接",

  "router": "string2",

  "is_login": false,

  "bt_state": 0

}

]

}

db类

import { cloudDatabase } from '@kit.CloudFoundationKit';

class split_layout extends cloudDatabase.DatabaseObject {

public split_id: number;

public txt: string;

public url: string;

public router: string;

public is_login: boolean;

public bt_state: number;

public naturalbase_ClassName(): string {

return 'split_layout';

}

}

export { split_layout };

实体类

/*

  • Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved.

  • Generated by the CloudDB ObjectType compiler. DO NOT EDIT!

*/

export class SplitLayoutModel {

split_id: number;

txt: string;

url: string;

router: string;

is_login: boolean;

bt_state: number;

constructor() {

}

getFieldTypeMap():  Map<string, string> {

    let fieldTypeMap = new Map<string, string>();

    fieldTypeMap.set('split_id', 'Integer');

    fieldTypeMap.set('txt', 'String');

    fieldTypeMap.set('url', 'String');

    fieldTypeMap.set('router', 'String');

    fieldTypeMap.set('is_login', 'Boolean');

    fieldTypeMap.set('bt_state', 'Integer');

    return fieldTypeMap;

}

getClassName(): string {

    return 'split_layout';

}

getPrimaryKeyList(): string[] {

    let primaryKeyList: string[] = [];

    primaryKeyList.push('split_id');

    return primaryKeyList;

}

getIndexList(): string[] {

    let indexList: string[] = [];

    return indexList;

}

getEncryptedFieldList(): string[] {

    let encryptedFieldList: string[] = [];

    return encryptedFieldList;

}

setSplit_id(split_id: number): void {

    this.split_id = split_id;

}

getSplit_id(): number  {

    return this.split_id;

}

setTxt(txt: string): void {

    this.txt = txt;

}

getTxt(): string  {

    return this.txt;

}

setUrl(url: string): void {

    this.url = url;

}

getUrl(): string  {

    return this.url;

}

setRouter(router: string): void {

    this.router = router;

}

getRouter(): string  {

    return this.router;

}

setIs_login(is_login: boolean): void {

    this.is_login = is_login;

}

getIs_login(): boolean  {

    return this.is_login;

}

setBt_state(bt_state: number): void {

    this.bt_state = bt_state;

}

getBt_state(): number  {

    return this.bt_state;

}

static parseFrom(inputObject: any): SplitLayoutModel {

    let result = new SplitLayoutModel();

    if (!inputObject) {

        return result;

    }

    if (inputObject.split_id) {

        result.split_id = inputObject.split_id;

    }

    if (inputObject.txt) {

        result.txt = inputObject.txt;

    }

    if (inputObject.url) {

        result.url = inputObject.url;

    }

    if (inputObject.router) {

        result.router = inputObject.router;

    }

    if (inputObject.is_login) {

        result.is_login = inputObject.is_login;

    }

    if (inputObject.bt_state) {

        result.bt_state = inputObject.bt_state;

    }

    return result;

}

}

然后把这些内容同步到云端

一切都完成之后,我们进行页面逻辑的修改

import { SplitLayoutModel } from "../entity/SplitLayoutModel"

@Preview

@Component

export struct SplitLayout {

@Link listData: SplitLayoutModel[]

private scroller: Scroller = new Scroller()

build() {

Column() {

  Grid(this.scroller){

    ForEach(this.listData, (item:SplitLayoutModel) => {

      GridItem(){

        Column() {

          Image(item.url)

            .width(45)

            .height(45)

            .borderRadius(24)

            .margin({ top: 5 })

          Text(item.txt)

            .padding(2)

            .fontSize(16)

            .fontColor(Color.Black)

            .textAlign(TextAlign.Center)

        }

      }

    })

  }

  .scrollBar(BarState.Off)

  .rowsTemplate('1fr 1fr')

  .rowsGap(15)

  .columnsGap(10)

  .height(150)

    ScrollBar({ scroller: this.scroller, direction: ScrollBarDirection.Horizontal,state: BarState.Auto }) {

      Text()

        .width(40)

        .height(10)

        .borderRadius(10)

        .backgroundColor('#ff34a8e5')

    }

    .borderRadius(5)

    .margin({top:10})

    .width(100)

    .backgroundColor('#ededed')

}

.alignItems(HorizontalAlign.Center)

.height(190)

.width('95%')

.margin({top:20})

.backgroundColor('#ffeedeb8')

.padding(16)

.borderRadius(20)

}

}

然后在主页调用组件

先创建一个接收数据变量

@State splitList:SplitLayoutModel[]=[]

                SplitLayout({listData:this.splitList})

进行数据查询和赋值

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

 let listData2 = await databaseZone.query(condition2);

  let json2 = JSON.stringify(listData2)

  let data2:SplitLayoutModel[]= JSON.parse(json2)

  this.splitList=data2

到这里我们的金刚区就实现了