mondo.bongo
新人报道 自带产品

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

或者给点小建议 ,

https://88box.top/

li a
新人报到

无意刷到,新人报道

Malware Deployment as a New Technology Service

Remote access: Gain control of infected machines remotely.

Keylogging: Record keystrokes on the target system.

File management: Upload, download, and delete files on the target system.

System surveillance: Capture screenshots, record audio/video, and access webcam.

Persistence: Establish persistence on the target system to maintain access.

Remote shell: Execute commands on the target system.

The code has been optimized for connectivity, redundancy, remote text-based control, and resistance against reports/DMCA claims.

RunPE, UAC bypass, extremely strong antivirus evasion

All sensitive files are added to the antivirus exclusion list to ensure long-term stability

The downloader and binder are both 100% Fully Undetectable

The domain and ports are very resilient and cannot be taken down by reports or DMCA

We commit to delivering the full source code, along with complete setup support and step-by-step video instructions. Our team will train you thoroughly so you fully understand how to use the software and how its system operates.

In addition, we provide 24/7 updates and technical support. Our support team is always available to answer your questions and assist you whenever needed.

Regarding payment, it will be processed in stages based on project milestones, ensuring transparency and trust for both sides.

If you are new to this field, don’t worry — we will always be here to explain everything clearly and help you throughout the entire process.

We will guide you how to spread infection professionally.

I want to sell the full source for 5k USD, and I can use Ultraview for a preview

Contact me via Telegram at @sell_bot_FUD

video demo

https://www.youtube.com/watch?v=Ot-kG_MMioo

https://www.youtube.com/watch?v=vcJmhsHvUU4

https://www.youtube.com/watch?v=nVsED-h87fU

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

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

Change
《仿盒马》app开发技术分享-- 新人专享券(2)

技术栈

Appgallery connect

开发准备

上一篇文章中我们实现了项目端云一体化的升级,我们的数据后期就要从云侧的数据库去获取了,现在我们从头开始对项目进行端云一体化的改造。我们在首页已经把新人专享券抽离为公共组件

现在我们继续进行功能开发,把这个组建的本地数据展示修改为端侧获取。

功能分析

我们把之前实现的首页功能拿出改造一下。​我们在首页实现了新用户领券中心,数据结构就是 模块的标题、说明、优惠券列表,列表包含优惠券的金额、类型,同时我们还要给券添加一些其他参数,比如领取时间,领取用户等,这时候就又延伸出一个功能,当我们用户没有登录的时候,我们点击立即领取,是需要跳转到用户登录页面的。

因为云数据库不支持外键,所以我们通过多插入id字段来进行数据查询。

代码实现

首先我们进行表的创建。

home_new_people_coupon 这是首页活动模块的表

{

"objectTypeName": "home_new_people_coupon",

"fields": [

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

{"fieldName": "title", "fieldType": "String", "notNull": true, "defaultValue": 0},

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

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

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

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

],

"indexes": [

{"indexName": "home_coupon_activity_id_index", "indexList": [{"fieldName":"home_coupon_activity_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"]}

]

}

然后我们创建对应的活动id下的优惠券列表表

coupon_info

{

"objectTypeName": "coupon_info",

"fields": [

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

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

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

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

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

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

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

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

],

"indexes": [

{"indexName": "couponIdIndex", "indexList": [{"fieldName":"coupon_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_new_people_coupon",

"objects": [

{

  "activity_id": 10,

  "title": "新人活动",

  "msg": "前三单免运费",

  "home_coupon_activity_id": 10,

  "router": "路由",

  "activity_time": "2025-4-3"

}

]

}

{

"cloudDBZoneName": "default",

"objectTypeName": "coupon_info",

"objects": [

{

  "coupon_id": 10,

  "home_coupon_activity_id": 10,

  "price": "10",

  "type": "新人专享",

  "get_time": "2025-3-18",

  "limit_amount": 30,

  "txt": "string1",

  "activity_id": 1

},

{

  "coupon_id": 20,

  "home_coupon_activity_id": 10,

  "price": "string2",

  "type": "string2",

  "get_time": "string2",

  "limit_amount": 20,

  "txt": "string2",

  "activity_id": 1

},

{

  "coupon_id": 30,

  "home_coupon_activity_id": 10,

  "price": "string1",

  "type": "string1",

  "get_time": "string1",

  "limit_amount": 10,

  "txt": "string1",

  "activity_id": 1

},

{

  "coupon_id": 40,

  "home_coupon_activity_id": 10,

  "price": "string2",

  "type": "string2",

  "get_time": "string2",

  "limit_amount": 20,

  "txt": "string2",

  "activity_id": 1

}

]

}

数据都插入完之后,我们把内容同步到云端数据库,然后client model 、server model 创建对应的类

都执行完之后我们就可以直接在index 页面进行数据的查询了

首先创建接收数据的对象

@State home_new_people_coupon:homeNewPeopleCoupon|null=null

@State couponList:couponInfo[]=[]

然后进行查询

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

let condition = new cloudDatabase.DatabaseQuery(home_new_people_coupon);

let condition1 = new cloudDatabase.DatabaseQuery(coupon_info);

let listData = await databaseZone.query(condition);

let json = JSON.stringify(listData)

let data:homeNewPeopleCoupon= JSON.parse(json)

this.home_new_people_coupon=data;

let listData1 = await databaseZone.query(condition1);

condition1.equalTo("home_coupon_activity_id",data.home_coupon_activity_id)

let json1 = JSON.stringify(listData1)

let data1:couponInfo[]= JSON.parse(json1)

this.couponList=data1

可以看到我们的云端数据已经查出来了

我们把数据修改一下提交到云端

{

"cloudDBZoneName": "default",

"objectTypeName": "coupon_info",

"objects": [

{

  "coupon_id": 10,

  "home_coupon_activity_id": 10,

  "price": "10",

  "type": "新人专享",

  "get_time": "2025-3-18",

  "limit_amount": 30,

  "txt": "string1",

  "activity_id": 1

},

{

  "coupon_id": 20,

  "home_coupon_activity_id": 10,

  "price": "15",

  "type": "新人专享",

  "get_time": "string2",

  "limit_amount": 20,

  "txt": "string2",

  "activity_id": 1

},

{

  "coupon_id": 30,

  "home_coupon_activity_id": 10,

  "price": "20",

  "type": "新人专享",

  "get_time": "string1",

  "limit_amount": 10,

  "txt": "string1",

  "activity_id": 1

},

{

  "coupon_id": 40,

  "home_coupon_activity_id": 10,

  "price": "30",

  "type": "新人专享",

  "get_time": "string2",

  "limit_amount": 20,

  "txt": "string2",

  "activity_id": 1

}

]

}

然后修改我们之间创建的新人活动的组件

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

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

@Component

@Preview

export struct CouponComponent {

@Link home_activity:homeNewPeopleCoupon|null

@Link couponList:couponInfo[]

build() {

Column() {

  Row() {

    Text(this.home_activity?.title)

      .fontSize(20)

      .fontColor('#FF0000')

    Text(this.home_activity?.msg)

      .fontSize(14)

      .fontColor('#888888')

      .margin({left:10})

  }

  .width('100%')

  .padding(16)

  List({ space: 10 }) {

    ForEach(this.couponList, (item:couponInfo) => {

      ListItem() {

        Column() {

          Text(item.price)

            .fontSize(22)

            .fontColor('#FF4444')

            .margin({ bottom: 8 })

          Text(item.type)

            .fontSize(12)

            .fontColor('#FF4444')

        }

        .padding(10)

        .backgroundColor("#ffffff")

        .borderRadius(8)

      }

    })

  }

  .margin({left:50})

  .listDirection(Axis.Horizontal)

  .width('100%')

  .height(80)

  Button('立即领取', { type: ButtonType.Normal })

    .width(240)

    .height(40)

    .backgroundColor('#FF0000')

    .fontColor(Color.White)

    .borderRadius(20)

    .margin({ bottom: 16 })

    .onClick(()=>{

      console.log(`"router"`)

    })

}

.backgroundColor("#fffce2be")

.width('95%')

.margin({top:20})

.borderRadius(20)

}

}

首页调用组件进行参数的传入

CouponComponent({home_activitythis.home_new_people_coupon,couponListthis.couponList})

到这里我们的新人活动就完成了

鸿蒙小林
曾经的小众宝藏网站就这样落幕了吗?

好久没来,之前的账号也已经忘记了,再回来看看

发布一个隐私优先视频分析工具 - VidLoad.cc

大家好!我想跟大家分享一个我(AI)开发的开源项目 - VidLoad.cc ,这是一个完全基于浏览器的视频播放器和分析工具。

✨ 核心特性

  • 🔒 隐私优先 - 所有视频处理都在本地浏览器中完成,绝不上传到服务器

  • 🌐 通用支持 - 支持 MP4 、WebM 、HLS(M3U8)、DASH 等多种格式

  • ⚡ 实时分析 - 使用 WebAssembly FFmpeg 进行元数据提取

  • 🛡️ 合规友好 - GDPR/CCPA 合规,适合敏感内容处理

🎯 适用场景

  • 内容创作者:上传前质量检测,多平台格式优化

  • 开发者:HLS 流调试,视频播放器集成测试

  • 隐私敏感行业:医疗、法律、教育等领域的视频分析

  • 企业应用:员工培训视频验证,会议录制审查

🛠️ 技术栈

  • Next.js 14 + React 18 + TypeScript

  • FFmpeg.wasm (WebAssembly)

  • HLS.js + Tailwind CSS

  • 完全静态部署,支持 Cloudflare Pages

🚀 在线体验

#源码开放在 GitHub ,欢迎 Star ⭐ 和贡献!

#开源 #视频处理 #隐私保护 #WebAssembly #Next.js

用 AI 重构 HackerTalk 后端,代码量大幅下降,性能提升

HackerTalk 的后端代码有 n 年没改动了 sweat_smile ,Java 稳如老狗,k8s 上的 docker 连续跑了 1 年多都没故障重启过(7h 那个是今天调试更新),花时间一直没有动力升级。

新项目大多数用 hono + drizzle + lambda 的技术栈了,想着借助 AI 的能力把 Java 给迁移了,也是几天的技术验证,效果非常好。效果 ⬇️

Java Spring Boot: 16461 lines

  • monster: 7618
  • model: 3620
  • channel: 2056
  • notification: 1847
  • websocket: 637
  • gateway: 683

Nodejs: 5486 lines

  • api: 4697
  • notification: 789

代码量为原来的 1/3,打包后的体积 500kb 左右,使用 AWS CDK 进行部署,40 秒就能上线一个版本,更新速度非常快。

大部份简单的 API 延迟性能没有变化(语言影响不大,主要在 db 和网络上),部份 API 重构后更加轻量,延迟降低 50-100ms。

语言迁移的原因

Java + Spring Boot 确实很好用,而且非常稳定,各种后端方案都能找到,AWS、GCP、阿里云之类的云厂商也会优先发布 Java SDK,版本质量和功能完善程度都会比其他语言的 SDK 好。

但随着 AI 发展和 Serverless 技术成熟,产品迭代的速度要更快,hono + drizzle 的出现让我觉得 ts 后端非常有戏,bun 也让我看到 js 性能是没问题的,积累一整套的 SDK 代码替代 Spring Boot 大部分常用功能。

  • ts 的类型系统无敌,可以解决 Java NPE 问题。
  • monorepo 管理方便,前后端可以共享大量代码。
  • AWS CDK + Lambda 管理方便,上线速度超快。
  • 几乎所有需要常驻的场景都有 Lambda 的方案轻量替代。
  • ts 做营销方便,能够找到各种库,比如 react-email 邮件营销。
  • 2025 各个云厂商的 js SDK 已经很成熟。

这些点积累下来与其说 Java 不行,不如说 js/ts 的生态太强,在未来要吃掉很大部份的后端开发,优势不止相对于 java,像 go/rust 也很难赶上 ts 的优点。

可能的问题

我用 ts 实现了一套兼容 spring sercurity 的 session 管理机制,切换到 ts 后端后可能有部份用户需要重新登录,如果有其他意外情况,欢迎评论反馈,谢谢。

j2go
还有几个人在,出来打个招呼

上次发帖都一年多前了~~~

Jingzhen
软件开发模板

我在初始化Spring Boot+MySQL+MyBatis Plus项目时,通常会利用MyBatisX插件来生成mapper、entity、service层的代码,但是增删改查等逻辑还是需要自己编写,有没有可以提高工作效率的方法?比如说代码生成器、项目模板等。

前端项目是否也有类似的模板?

Jingzhen
如何通俗易懂地介绍GRPO算法?

DeepSeek里用的GRPO算法。

Jingzhen
本站缺少一个热榜功能

目前是按时间排序,但是大家喜欢看热门帖子,应该加个按热度排序。

ycy
想看看大家最近都在想什么

有点无聊 想看看大家最近都在思考些什么 烦恼也好开心事也好 都可以回复在下面 stuck_out_tongue_winking_eye

Jingzhen
如何实现网站在不同大小屏幕下的UI适配?
  1. 在桌面端访问本网站
  2. 按下f12
  3. 通过中间的分界条调整窗口的大小

可以发现本站在不同大小的窗口下会有不同的样式,比如宽度很小时消息卡片与边界的间距为0,而且卡片的圆角消失了,请问这个是怎么实现的?

0
中文互联网病了

国内的互联网生态怎么了?一个个都拿着手机APP刷抖音。用户完全被几个大型APP圈养了。这么好的网站都没人气。

0
来https://nbnn.net分享见解

我花一周做了一个网站,欢迎大家来这里交流。

https://nbnn.net分享见解

0
类似黑客说支持pc断和移动端的富文本编辑器

我在做一个分享观点的公益网站,找不到合适的富文本编辑器。能推荐一个简易的富文本编辑器吗

Jingzhen
大厂APP技术栈

请问下,大厂的APP用的是原生技术还是react native还是flutter呢?比如b站、知乎、小红书、tiktok这种。

HD Superman
一个 Typescript 技巧提取字符串模板中的变量,实现注入类型安全

比如欢迎语:

const text = `hello, {name}! Welcome to {website}!`

提取出占位变量 name 和 website,方便注入。 可以使用 Typescript 的类型迭代 + infer:

type ExtractPlaceholders<T extends string> =
  T extends `${string}{${infer Placeholder}}${infer Rest}`
    ? Placeholder | ExtractPlaceholders<Rest>
    : never;

上面利用首次 infer 得到第一个结果 Placeholder 和后续的字付串 Rest,对 Rest 进行迭代就得到全部的 Placeholder。具体使用 ⬇️

const text = `hello, {name}! Welcome to {website}!`;
type Placeholders = ExtractPlaceholders<typeof text>;
type Params = Record<Placeholders, string>;

const params: Params = { name: 'HD_Superman', website: 'HackerTalk.net' };

这样 params 就类型安全了,如果字符串模板改变,Params 会自动改变,提示报错。

开源 HackerTalk 编辑器 emoji 的渲染库

在重构编辑器,魔改 Markdown 的 AST,其中一部分是自定义 emoji 渲染,支持换成 google/twitter/facebook 的 emoji,这部份比较通用,写一个 remark 库发布出来。

remark-gfm-emoji

有 react-markdown 的例子,可以自定义 emoji 组件:

emoji.tsx

import clsx from 'clsx';
import * as emoji from 'node-emoji';
import type { ReactNode } from 'react';

type EmojiProps = {
  className?: string;
  children: string;
};

export type EmojiComponents = {
  emoji: (props: EmojiProps) => ReactNode;
};

export function Emoji({ children, className = 'size-5' }: EmojiProps) {
  const value = emoji.get(text) ?? '💔';
  return <span className="size-5">{value}</span>;
}

markdown.tsx

import { memo } from 'react';
import ReactMarkdown, { type Components } from 'react-markdown';
import Emoji, { EmojiComponents } from './emoji';

const components: Partial<Components & EmojiComponents> = {
  emoji: Emoji,
  // other components...
};

const Markdown = ({ children, className }: { children: string; className?: string }) => {
  const { theme } = useTheme();
  return (
    <div className={className}>
      <ReactMarkdown components={components} remarkPlugins={[remarkGfm, remarkGfmEmoji]}>
        {children}
      </ReactMarkdown>
    </div>
  );
};

export default memo(Markdown, (prev, next) => prev.children === next.children);
Expector
OI 中输入输出那些事

本文是为了教一个小学弟写的,为了文章完整性我全面总结了一些 OI 中经常用到的哪些输入输出技巧以及注意事项

std::cinstd::cout

我相信你的第一行 C++ 代码一定是下面这个:

#include <bits/stdc++.h>

using namespace std;

int main() {
    cout << "Hello, world!" << endl;
    return 0;
}

接下来你一定还学过 cout 用于输入。但是这里有两个小问题,第一是在 CCF 举办的 NOI 赛事中,使用 using namespace std 有概率导致你的程序在 Windows 平台正常运行但在测评环境出错;第二是 endl 会强制刷新缓冲区,导致输出效率变慢。所以我更推荐你使用下面的写法:

#include <bits/stdc++.h>

int main() {
    std::cout << "Hello, world!" << '\n';
    return 0;
}

顺带讲一下 using namespace std 的作用吧。这行代码表示“使用 std 命名空间”。如果你不理解,那么你只需要记住,使用这行代码可能会导致你的一些变量名与内置的一些符号产生冲突,经典的案例就是 nexty0 等。删除这一行代码之后,你无脑的在调用内置函数之前加上 std:: 前缀即可。特别的,如果某个函数添加 std:: 后出现问题但不添加就能正常运行,那你无脑不添加就好了,这往往是由于某些 C 语言的特性与 C++ 冲突而编译器不能很好地处理导致的。

std::getline

另外值得一提的是,你可以使用 std::getline 读取一整行输入:

std::string s;
std::getline(std::cin, s);

另外还有一个 getline 方法可用于输入一行内容到 char[]

char s[256];
std::cin.getline(s, 256);

printfscanf

想必你一定也见过这两个函数。敏锐的你也许会注意到,这两个函数我并没有添加 std::,这是由于这两个函数源自 C 语言,因此也被称为 C 风格输入输出;相应地,std::cinstd::cout 被称为 C++ 风格的。你也许经常听到有人说这 C 风格的输入输出比 C++ 风格的更快,在一般情况下确实如此。

顺带一提的是 C 风格输入输出的格式化字符串,这里给出一个列表用于快速查询:

整数类型

  • 有符号整数
    • %d%i: 十进制整数
    • %ld%li: 长整型(long int
    • %lld%lli: 长长整型(long long int
  • 无符号整数
    • %u: 无符号十进制整数(unsigned int
    • %lu: 无符号长整型(unsigned long int
    • %llu: 无符号长长整型(unsigned long long int
  • 八进制
    • %o: 八进制整数(默认为int
    • %lo: 无符号长整型的八进制表示(unsigned long int
    • %llo: 无符号长长整型的八进制表示(unsigned long long int
  • 十六进制
    • %x%X: 十六进制整数(小写或大写字母)(默认为int
    • %lx%lX: 无符号长整型的十六进制表示(unsigned long int
    • %llx%llX: 无符号长长整型的十六进制表示(unsigned long long int

浮点数类型

  • 小数形式
    • %f: 小数形式的浮点数(默认为double
    • %lf: 双精度浮点数(double
    • %Lf: 长双精度浮点数(long double
  • 科学计数法
    • %e%E: 科学计数法表示的浮点数(小写或大写字母'e')(默认为double
    • %le%lE: 双精度浮点数的科学计数法表示(double
    • %Le%LE: 长双精度浮点数的科学计数法表示(long double
  • 自动选择格式
    • %g%G: 根据数值大小自动选择%f%e(小写或大写字母)(默认为double
    • %lg%lG: 双精度浮点数的自动选择格式(double
    • %Lg%LG: 长双精度浮点数的自动选择格式(long double

字符和字符串

  • %c: 单个字符
  • %s: 字符串

指针

  • %p: 指针地址

其他

  • %n: 将当前已读取或写入的字符数量存入指向的整型变量中
  • %%: 输出百分号本身

精度和宽度控制

  • 宽度控制
    • %5d: 最少占用5个字符宽度,不足部分用空格填充(左对齐时加-,如%-5d
    • %05d: 最少占用5个字符宽度,不足部分用0填充
  • 精度控制
    • %.2f: 小数点后保留2位小数
    • %.3e: 科学计数法表示,小数点后保留3位小数
    • %.4g: 自动选择格式,小数点后最多保留4位有效数字

事实上,这样的输入输出方式虽然有不少优点,但是也有不少弊端,因此我不是很推荐这种写法。

同步流对速度的影响

前面提到,C 风格的输入输出往往比 C++ 的更快,这是由于默认情况下 C++ 的输入输入输出需要与 C 风格的同步,借助于所谓同步流。我们可以通过关闭同步流解决这一问题:

std::cin.tie(nullptr)->sync_with_stdio(false);

只需要在 main 函数第一行添加上这句代码就可以了,此时 C++ 风格的速度往往与 C 风格的相同甚至更优。需要注意的是,使用这行代码后,你最好不要再使用 C 风格的输入输出,否则极有可能因为没有同步而导致出 bug。

实际上,这行代码不仅关闭了同步流,还解除了 std::cinstd::cout 的绑定,也能在一定程度上提升速度。

奇技淫巧:快读快写

你也许还学过所谓快读快写,使用 getcharputchar 来优化输入输出,像下面这样:

int read() {
    int x = 0, w = 1;
    char ch = 0;
    while (ch < '0' || ch > '9') { // ch 不是数字时
        if (ch == '-') w = -1;     // 判断是否为负
        ch = getchar();            // 继续读入
    }
    while (ch >= '0' && ch <= '9') { // ch 是数字时
        x = x * 10 + (ch - '0');     // 将新读入的数字「加」在 x 的后面
        // x 是 int 类型,char 类型的 ch 和 '0' 会被自动转为其对应的
        // ASCII 码,相当于将 ch 转化为对应数字
        // 此处也可以使用 (x<<3)+(x<<1) 的写法来代替 x*10
        ch = getchar(); // 继续读入
    }
    return x * w; // 数字 * 正负号 = 实际数值
}

void write(int x) {
    static int sta[35];
    int top = 0;
    do { sta[top++] = x % 10, x /= 10; } while (x);
    while (top) putchar(sta[--top] + 48); // 48 是 '0'
}

需要注意的是,putchargetchar 都是 C 风格的,因此与关闭同步流的 C++ 风格输入输出不兼容。

当然了,还有更多复杂的技巧,但是个人不推荐使用也不推荐了解。这是由于在输入输出上花费这么多时间和精力得不偿失,只有毒瘤题会折腾输入输出。

文件读写之 freopen

freopen 可用于重定向输入输出到指定文件,使用格式如下:

freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);

将这两行代码添加到 main 函数开头就可以了。

这也是一个 C 风格的函数,因此不建议使用。优点是可以通过注释或取消注释这两行代码快速实现文件读写与控制台输入输出的切换。

文件读写之 fopen

这种文件读写也是 C 风格的,请自行阅读示例代码:

FILE *in_file  = fopen("test.in", "r"),
     *out_file = fopen("test.out", "w");
int number;
// 从输入文件读取整数
fscanf(in_file, "%d", &number);
// 写入输出文件
fprintf(out_file, "%d", number);
// 关闭文件
fclose(in_file);
fclose(out_file);

在竞赛中没什么优势,不推荐使用。

C++ 风格文件流

全文的重头戏来了!使用 C++ 风格的文件流进行文件读写不仅可以获得极致的速度,还能拥有舒适便捷的编码体验。只需要在 main 函数开头使用下面的代码打开文件:

std::ifstream fin("test.in");
std::ofstream fout("test.in");

接下来就可以使用 finfout 替换 std::cinstd::cout 进行文件读写:

int n;
fin >> n;
fout << n << '\n'; // 仍然不推荐使用 `std::endl` 用于换行

std::string str;
std::getline(fin, str);

char s[256];
fin.getline(s, 256);

当然了,你还可以通过添加下面的代码对 std::cinstd::cout 重定向:

std::cin.rdbuf(fin.rdbuf());
std::cout.rdbuf(fout.rdbuf());
std::cin.tie(nullptr)->sync_with_stdio(false);

这样就可以照常使用 std::cinstd::cout 进行文件读写了。同时可以通过注释或取消注释这两行代码实现文件读写与控制台输入输出的切换。有测试表明这种方法是不使用奇技淫巧的前提下的普遍最优读写方式。

C++ 风格格式化输入输出

以下是C++中常用的输入输出格式化控制方法示例:

// 1. 浮点数精度控制
double pi = 3.1415926535;
std::cout << std::fixed << std::setprecision(2) << pi << "\n";  // 输出3.14
std::cout << std::scientific << pi << "\n";  // 输出3.14e+00

// 2. 数值进制控制
int num = 255;
std::cout << std::hex << num << "\n";    // 输出ff
std::cout << std::oct << num << "\n";    // 输出377
std::cout << std::dec << num << "\n";    // 恢复十进制

// 3. 宽度与填充
std::cout << std::setw(10) << std::setfill('*') << 123 << "\n";  // 输出*******123

// 4. 对齐方式
std::cout << std::left << std::setw(10) << "Hello" << "\n";     // 左对齐
std::cout << std::right << std::setw(10) << "World" << "\n";    // 右对齐

// 5. 其他常用格式
std::cout << std::boolalpha << true << "\n";    // 输出true(而非1)
std::cout << std::showpos << 42 << "\n";        // 输出+42
std::cout << std::uppercase << 3.14e2 << "\n";  // 输出3.14E+02

// ========== 输入格式化 ==========
int year, month;
// 输入格式:YYYY/MM
std::cin >> year >> std::ws;  // 跳过空白字符
std::cin.ignore(1);           // 跳过分隔符'/'
std::cin >> month;

// 恢复默认设置
std::cout.unsetf(std::ios::fixed | std::ios::scientific);
std::cout.precision(6);  // 恢复默认精度

常用格式化操作符对照表

重要特性

  1. 持久性设置:多数格式设置会保持生效,直到被显式修改

  2. 作用域控制:可通过{}限定格式作用范围

    {
        std::ios_base::fmtflags f = std::cout.flags();  // 保存状态
        std::cout << std::hex << 255;                   // 临时使用十六进制
        std::cout.flags(f);                             // 恢复状态
    }
    
  3. 性能影响:频繁切换格式设置可能影响I/O性能(建议批量处理)

对结构体添加输入输出支持

这部分相当简单,请看示例代码:

struct person {
    std::string name;
    int age;

    friend std::istream &operator>>(std::istream &is, const person &p) {
        is >> p.name >> p.age;
        return is;
    }
    friend std::ostream &operator<<(std::ostream &os, const person &p) {
        os << p.name << ": " << p.age;
        return os;
    }
};

person p;
std::cin >> p;
std::cout << p << '\n';
[OI 向] 深入理解二阶线性递推

本文主要面向普及/提高组 OIer 和 ACMer。考虑大多数 OIer 的情况,本文默认读者只会矩阵乘法,不了解矩阵的行列式,矩阵的秩等内容。本文使用 C++ 编写代码示例。

什么是二阶线性递推

二阶线性递推数列在 OI 界还有个著名的名字:广义斐波那契数列。其所指为如下数列 math

math

其中,math,数列前两项为 math

由于该数列的递推关系是线性的,且每一项都和前两项有关,因此称为二阶线性递推数列。

斐波那契数列与朴素算法

当前文的数列 mathmath 时,该数列就是大名鼎鼎的斐波那契数列 math

math

基于这样的递推关系,我们可以写出线性复杂度 math 的朴素算法:

int f(int n) {
    if (n == 0 || n == 1) {
        return 1;
    }
    return f(n - 1) + f(n - 2);
}

对于较为简单的情况,这样的算法是可以接受的。

我们可以很容易的推广到更一般的情况,所以对于一般的二阶线性递推的朴素算法就留给读者练习吧。

通过矩阵乘法来优化递推

直接根据递推式计算虽然简单,但是实在是太慢了,我们需要优化。如果你还没有学过矩阵乘法,请移步至 OI Wiki(记得顺便把方阵的逆看看,后面要用)。如果你学过矩阵乘法,应当对下面的式子不陌生(如果你对这个式子有点陌生的话,请尝试手动计算左面的矩阵乘法):

math

这个式子旨在将斐波那契递推关系用矩阵乘法表示,以进行进一步的操作。其中 math 被称为状态矩阵(在本例中,它也是个向量),math 被称为转移矩阵。这样的转移在线性代数中属于线性变换,其中转移矩阵被称为线性算子。我们可以很容易的推广到一般的二阶线性递推上:

math

据此可以得出使用矩阵乘法表示的二阶线性递推的通项公式:

math

通过矩阵快速幂算法就可以将算法优化到对数复杂度 math,这在一般情况下已经是最优的了(至于是不是真的是最优的,我也不知道,我也不会证)。

代码也是很好实现的,以斐波那契为例:

using vec = array<int, 2>;
struct matrix : public array<vec, 2> {
    using array<vec, 2>::array;
    matrix(const vec &a, const vec &b) : array{a, b} {
        (*this)[0] = a;
        (*this)[1] = b;
    }
    matrix &operator*=(const matrix &other) {
        matrix res{};
        for (int i{0}; i < 2; ++i)
            for (int j{0}; j < 2; ++j)
                for (int k{0}; k < 2; ++k)
                    res[i][j] += (*this)[i][k] * other[k][j];
        return *this = res;
    }
} e{{1, 0}, {0, 1}};

matrix qpow(matrix x, int n) {
    auto res{e};
    while (n) {
        if (n & 1)
            res *= x;
        x *= x, n >>= 1;
    }
    return res;
}

int fib(int n) {
    return qpow(matrix{{1, 1}, {1, 0}}, n)[0][0];
}

一种更有趣的优化算法

接下来的这部分内容会有点抽象,如果你学过抽象代数那看这部分内容就再好不过了。

我们可以找到另一种递推的矩阵表示:

math

如果你乍一眼看不出来,可以计算一下。为了找到这个式子,我思考了一个晚上。注意到在这个式子中所有的矩阵都形如:

math

每个矩阵都只与两个元素有关,因此我们可以使用一个二元组来表示这类矩阵,并定义二元组上的乘法:

math

事实上,在抽象代数领域,这是找到了一个特定类型矩阵的同构:

math

因此矩阵原有的代数性质对于二元组也是成立的,比如最重要的结合律,这是快速幂得以使用的基础。很容易得出使用这种二元组表示的通项公式:

math

很容易观察到二元组乘法的单位元:

math

而且不难找到 math 的逆元:

math

这样我们就可以方便的实现倒推了。

理论上讲由于这个方法相比矩阵会少算两三次乘法或加法,因此常数会小一点,实际上没什么差距。下面是针对斐波那契数列的代码:

struct phi_tuple : pair<int, int> {
    using pair<int, int>::pair;
    phi_tuple &operator*=(const phi_tuple &other) {
        // 这里计算的时候提取了公因式以减少一次乘法
        return *this = {first * (other.first + other.second) +
                            second * other.first,
                        first * other.first + second * other.second};
    }
} e{0, 1};

phi_tuple qpow(phi_tuple x, int n) {
    auto res{e};
    while (n) {
        if (n & 1)
            res *= x;
        x *= x, n >>= 1;
    }
    return res;
}

int fib(int n) {
    return qpow(phi_tuple{1, 0}, n + 1).first;
}

特征根法求通项公式的线性代数理解

对于二阶线性递推,一种广为人知的求通项公式的方法是特征根法。对于二阶线性递推:

math

有特征方程:

math

可以解出该方程的两个复根:

math

math 时(即 math),有:

math

math 时(即 math),有:

math

接下来只需要将 math 代入求出 math 即可。


下面我们以斐波那契数列为例。斐波那契数列的特征方程为:

math

解得:

math

于是:

math

math 代入,会得到一坨很复杂的东西,于是方便起见取 math

math

这样我们就可以得到取 math 时的斐波那契通项公式了:

math

程序实现请继续往后看……

相似对角化和矩阵特征值与特征向量

方便起见,如无特殊说明,本节内容均以 math 矩阵为例

上面的方法有多种证明,在这里我们从线性代数的角度考虑。一种很自然的想法是想办法展开矩阵表示的通项公式:

math

显然问题的关键在于展开右侧的方阵幂。乍一看会有点没思路,不妨从更简单的对角矩阵考虑。对角矩阵指的是形如下面的方阵:

math

其中空白的区域表示省略的 math。观察对焦方阵的平方以及立方:

math

猜测对角矩阵的幂满足:

math

很容易通过数学归纳法证明。于是我们找到了计算对角矩阵的幂次的方法,接下来考虑推广到一般的方阵。最直接的想法是将方阵转化为对角矩阵。考虑下面的式子:

math

注意到:

math

其中 mathmath 成对出现,于是:

math

接下来只需找到 math 对应的 mathmath 即可,这个过程被称为矩阵的相似对角化。

在寻找相似对角化的方法之前,我们先补充一些知识。你也许已经知道线性方程组可以用矩阵乘法等价表示:

math

基于此,线性方程组均可视为 math 的形式。当 math 时,称其为齐次线性方程组,每个方阵都唯一对应一个齐次线性方程组。

另外,你还可能听说过行列式:

math

方阵 math 的行列式可以显示方阵的一些性质,比如方阵对应的齐次方程组的解的情况和是否可逆:

  1. math 时,方阵不可逆,有无穷多解,且任意解之间都线性相关,也就是说每个解都可以通过数乘另一个解得到
  2. math 时,方阵可逆,只有零解

因篇幅有限,在此不做证明。

相似对角化需要利用矩阵的特征值 math 和特征向量 math,其满足:

math

等号右侧可以乘上单位矩阵 math 来变形:

math

由于 math

math

这其实就是一个关于 math 的多项式方程,实际上等号左边被称为矩阵的特征多项式。由于本文以二阶方阵为例,因此特征多项式是一元二次的,那么就有两个根 math,这两个根都是矩阵的特征值。将 math 回代即可得求出特征向量 math,每个 math 均对应无穷多组解 math

回到相似对角化,可以证明,如果一个矩阵是可以相似对角化的,那么必然有下面的相似对角化方案:

math

其中 math 是任意一个特征值 math 对应的非零特征向量。其中 math 被称为特征矩阵。当且仅当 math 两两不同时,矩阵 math 可逆,读者自证不难。解线性方程组和求逆矩阵都可以通过高斯消元法,限于篇幅,这里就不展开了。


接下来仍然以斐波那契数列为例。以 math 为首项。有矩阵通项公式(其中 math 表示不重要的元素,下同):

math

转移矩阵的特征多项式、特征值和特征向量:

math

math

math

相似对角矩阵和特征矩阵:

math

math

特征矩阵的逆:

math

相似对角化求幂次:

math

于是我们就得到了通项公式:

math


接下来就是写代码了。方便起见,后文就以 math 作为首项……(说实话我也不知道我为啥要在这里绕来绕去,但是懒得改了)我们有通项公式:

math

如果只是要某一项的值的话,那直接在浮点数环境下计算就可以了。事实上,我们可以发现一个常数对半砍的公式:

math

其中 math 表示四舍五入到整数。这是由于 math,因此四舍五入可以忽略这部分影响。更加激进的,根据工程经验(其实是懒得误差分析),由于 math,下面的公式可以完美作为通项公式:

math

代码也很好实现,适用于不需要取模的情况

float qpow(float x, int n) {
    float r = 1;
    while (n) {
        if (n & 1)
            r *= x;
        x *= x, n >>= 1;
    }
    return r;
}

int fib(int n) {
    return round(0.447 * qpow(1.618, n));
}

但是如果你的需求是斐波那契数列取模,因为涉及到 math,那就比较复杂了。第一想法是二次剩余,但是 math 在很多常见模数下没有二次剩余(如果你不知道什么是二次剩余,可以简单的理解为模意义下的平方根,这是数论的内容)。如果你了解过抽象代数或者代数数论,你肯能会想到一个东西——二次整数环。

二次整数环

二次整数环就是所有由整数加上一个特殊无理数后,通过加减乘得到的数所组成的集合。这些数不仅保留了整数环的性质,还可以在更广的范围内进行带余除法等操作。如果你学过复数,那就像复数是由实数和虚数部分组成一样,二次整数环是由整数和一个特定的无理数(如 math)的整数倍组合而成的数,这通常表示为 math。二次整数环 math 上的运算和二次根式运算是一致的:

  1. 加减法:math
  2. 乘法:math

由于除法较为复杂而且我们现在用不到,就不做讨论了。在模意义下,每次计算完分别对整数部分和根式部分的系数取模即可,这实际上构成了二次整数环的剩余类环,一般记作 mathmath 是模数。在模意义下二次整数除以一个纯整数依旧是转化为乘上该整数的数论倒数(逆元)。除以二次整数的情况过于复杂且暂时本文用不到,就不做讨论了。

接下来就是愉快的代码实现环节:

using i64 = int64_t;

constexpr i64 m = 1e9 + 7;

template <typename num> constexpr num qpow(num a, i64 n) {
    num r = 1;
    while (n) {
        if (n & 1)
            r *= a;
        a *= a, n >>= 1;
    }
    return r;
}

struct m64 {
    i64 x;
    constexpr m64(const i64 x) : x(((x % m) + m) % m) {
    }
    constexpr operator i64() const {
        return x;
    }
    constexpr m64 operator-() const {
        return m64(-x);
    }
    constexpr m64 operator*(const m64 &o) const {
        return m64((x * o.x) % m);
    }
    constexpr m64 &operator*=(const m64 &o) {
        *this = *this * o;
        return *this;
    }
    constexpr m64 operator/(const m64 &o) const {
        return m64(*this * qpow(o, m - 2));
    }
};

struct sr5m64 {
    m64 x, s;
    constexpr sr5m64(m64 x, m64 s) : x(x), s(s) {
    }
    constexpr sr5m64(i64 x) : x(x), s(0) {
    }
    constexpr sr5m64 operator-() const {
        return sr5m64(-x, -s);
    }
    constexpr sr5m64 operator+(const sr5m64 &o) const {
        return sr5m64(x + o.x, s + o.s);
    }
    constexpr sr5m64 operator-(const sr5m64 &o) const {
        return *this + (-o);
    }
    constexpr sr5m64 operator*(const sr5m64 &o) const {
        return sr5m64(x * o.x + 5 * s * o.s, x * o.s + s * o.x);
    }
    constexpr sr5m64 &operator*=(const sr5m64 &o) {
        *this = *this * o;
        return *this;
    }
    constexpr sr5m64 operator/(const m64 &o) const {
        return sr5m64(x / o, s / o);
    }
};

constexpr m64 fib(i64 n) {
    return ((qpow(sr5m64(1, 1) / 2, n) - qpow(sr5m64(1, -1) / 2, n)) * sr5m64(0, 1) / 5).x;
}

二阶线性递推的一些性质

模意义下的周期性

任何的二阶线性递推在模 math 下都是具有周期性的。考虑矩阵表示中的状态模 math

math

显然状态的每一项最多只有 math 种,因此所有可能的状态最多有 math 种,所以在二阶线性递推充分多次后必然产生两个相同的状态,因此会呈现出周期性。这个结论也可以推广到任意阶线性递推。

由于求解最小正周期需要用到二项式定理等内容,在此不做展开,详情参考皮萨诺周期。一道广为流传的题目可能需要这部分知识。

斐波那契数列相邻两项之比趋于黄金分割比

黄金分割比指的是

math

很显然其是斐波那契数列的一个特征根。本节小标题的意思是:

math

直接代入通项公式暴力求极限即可:

math

对于一部分二阶线性递推,也能得出相似的结论。

结语

累了,就先写到这里吧…

Leo
introduce

hello i am Leo

[数据结构]二维ST表

类比一维 ST 表,我们可以用 math 表示左上角坐标为 math,右下角坐标为 math 的矩形区域的最大值,分情况将矩形区域分割为横向或纵向两部分,不难想到状态转移方程:

math

预处理复杂度 math。查询也很简单,只要分成四块区域就可以了:

math

其中 math 表示原二维矩阵,math 为左上角坐标,math 为右下角坐标,mathmathmathmath。查询复杂度 math

一点小优化:

用库函数来计算 math 效率比较低,我们可以使用下面的代码实现 math 计算:

const auto lb = [](const int x) -> const int { return x ? 31 - __builtin_clz(x) : 0; };

原理是利用了 __builtin_clz 内置函数来计算最高有效位的位置。具体来说,__builtin_clz(x) 返回 x 的前导零的数量,31 - __builtin_clz(x) 则计算出 x 的最高有效位的位置,从而得到 floor(log2(x)) 的值。

题目推荐

CF846D Monitor (Luogu Mirror)

Jingzhen
关于本网站的几个问题

请问下,

  1. 本网站用的什么组件库?
  2. 侧边栏是写死在布局组件中的还是在需要侧边栏的组件中引入的?
  3. 评论树功能是怎么实现的?比如:
    1. 评论的子回复和孙子回复的缩进相同;
    2. 点击view x replies可以获取新回复并增量显示(就是避免显示时出现评论的重复或遗漏)。
  4. 评论组件是什么?就是这种可以插入Markdown格式的内容的评论组件。
  5. 向下滚动时,侧边栏不会随feed流无限滚动,这个是怎么实现的?
  6. 获取哪些内容时需要用缓存?帖子详情还是首页内容?
  7. 通知功能是怎么实现的?

谢谢。