1. 购物车
1. 购物车
咱们购物车基于 V2 装饰器进行开发,底气来源于
自定义组件混用场景指导
1.1. 素材整合
observedv2和Trace
- 数据模型和页面
// 其他略
// 购物车
export interface CartGoods {count: number;id: string;name: string;picture: string;price: number;selected: boolean;skuId: string;stock: number;attrsText: string;
}@ObservedV2
export class CartGoodsModel implements CartGoods {@Tracecount: number = 0id: string = ''name: string = ''picture: string = ''price: number = 0@Traceselected: boolean = falseskuId: string = ''stock: number = 0attrsText: string = ''constructor(model: CartGoods) {this.count = model.countthis.id = model.idthis.name = model.namethis.picture = model.picturethis.price = model.pricethis.selected = model.selectedthis.skuId = model.skuIdthis.stock = model.stockthis.attrsText = model.attrsText}
}
- 静态页面
import { auth, MkGuess, MkNavbar, MkUser, CartGoodsModel, CartGoods } from 'basic'@Component
export struct CartView {// 用户信息@StorageProp(auth.KEY) user: MkUser = {} as MkUser// 顶部安全区域@StorageProp('safeTop') safeTop: number = 0// ObserveV2 无法直接和@State 使用 会报错 但是包到数组中是可以使用的// @State cartData: CartGoodsModel = new CartGoodsModel({} as CartGoods)// 商品信息@State cartList: CartGoodsModel[] = []aboutToAppear(): void {}onCheckOrder() {AlertDialog.show({message: '去结算'})}@BuilderDeleteBuilder(onDelete: () => void) {Text('删除').fontSize(14).width(60).height(100).backgroundColor($r('[basic].color.red')).fontColor($r('[basic].color.white')).textAlign(TextAlign.Center).onClick(() => {onDelete()})}build() {Column() {MkNavbar({ title: '购物袋', showLeftIcon: false, showRightIcon: true }).border({width: { bottom: 0.5 },color: '#e4e4e4'})List() {if (this.user.token) {if (this.cartList.length) {ForEach(this.cartList, (cart: CartGoodsModel) => {ListItem() {CartItemComp({cart})}.backgroundColor($r('[basic].color.under')).padding({ left: 8, right: 8 }).transition({ type: TransitionType.Delete, opacity: 0 }).swipeAction({end: this.DeleteBuilder(async () => {AlertDialog.show({message: 'clickDel'})})})})} else {ListItem() {Text('无商品')}}} else {// 未登录ListItem() {Text('未登录')}}ListItem() {MkGuess().margin({ top: 8, bottom: 8 })}}.contentStartOffset(8).width('100%').layoutWeight(1).scrollBar(BarState.Off)if (this.cartList.length) {Row() {// TODO 添加自定义 CheckBoxText('全选').fontSize(14).fontColor($r('[basic].color.black')).margin({ right: 20 })Text('合计:').fontSize(14).fontColor($r('[basic].color.black')).margin({ right: 2 })Text('998').fontSize(16).fontWeight(500).fontColor($r('[basic].color.red')).layoutWeight(1)Button('去结算').fontSize(14).height(36).backgroundColor($r('[basic].color.red')).onClick(() => {})}.height(50).width('100%').backgroundColor($r('[basic].color.white')).border({width: { top: 0.5, bottom: 0.5 },color: '#e4e4e4'}).padding({ left: 16, right: 16 })}}.height('100%').width('100%').backgroundColor($r('[basic].color.under')).padding({ top: this.safeTop })}
}@ComponentV2
struct CartItemComp {@Param // v1 Prop 自己的状态 @Localcart: CartGoodsModel = new CartGoodsModel({} as CartGoods)build() {Row({ space: 10 }) {// TODO 添加自定义 CheckBoxImage(this.cart.picture).width(80).height(80)Column({ space: 8 }) {Text(this.cart.name).textOverflow({ overflow: TextOverflow.Ellipsis }).maxLines(1).fontSize(14).fontColor($r('[basic].color.black')).width('100%')Row() {Text(this.cart.attrsText).textOverflow({ overflow: TextOverflow.Ellipsis }).maxLines(1).fontSize(12).fontColor($r('[basic].color.text')).layoutWeight(1)Image($r('sys.media.ohos_ic_public_arrow_down')).fillColor($r('[basic].color.gray')).width(16).height(16)}.padding({ left: 6, right: 4 }).width(100).height(24).backgroundColor($r('[basic].color.under')).borderRadius(2)Row() {Text(`¥${this.cart.price}`).fontSize(14).fontWeight(500)Counter()}.width('100%').justifyContent(FlexAlign.SpaceBetween)}.layoutWeight(1).alignItems(HorizontalAlign.Start)}.width('100%').height(100).padding(10).border({ width: { bottom: 0.5 }, color: '#e4e4e4' }).backgroundColor($r('[basic].color.white'))}
}
- 在 oh-package.json5 文件中导入 common/basic
{"name": "cart","version": "1.0.0","description": "Please describe the basic information.","main": "Index.ets","author": "","license": "Apache-2.0","packageType": "InterfaceHar","dependencies": {"basic": "file:../../commons/basic"}
}
git 记录
Cart-素材整合
1.2. MkEmpty 组件封装(V2实现)
V2装饰器
先来搞定购物车中间部分的显示效果,通过组件来完成
核心步骤:
- 创建组件并暴露
- 根据文档实现功能
- 页面中导入并测试使用
-
- 已登录,无商品,点击去第一个 Tab(下一节做)
- 未登录,点击去登录页
基础模版
@ComponentV2export struct MkEmpty {build() {Column({ space: 20 }) {Image($r('app.media.ic_public_empty')).width(120).aspectRatio(1)Text('信息').fontSize(12).fontColor($r('app.color.gray'))Button('去逛逛').fontSize(12).height(26).padding({ left: 20, right: 20 }).linearGradient({angle: 90,colors: [['#FD3F8F', 0], ['#FF773C', 1]]}).margin({ bottom: 20 })}.padding(40).width('100%')}}
属性名 | 类型 | 说明 | 默认值 |
tip | string | 提示信息 | 空字符串 |
buttonText | string | 按钮文本 | 空字符串 |
icon | ResourceStr | 中间图标 | ic_public_empty |
onClickButton | ()=>void | 点击按钮的逻辑 | 空方法 |
参考代码
@ComponentV2
export struct MkEmpty {@Paramtip: string = '空空如也'@ParambuttonText: string = '去逛逛'@Paramicon: ResourceStr = $r('app.media.ic_public_empty')@EventonClickButton: () => void = () => {}build() {Column({ space: 20 }) {Image(this.icon).width(120).aspectRatio(1)Text(this.tip).fontSize(12).fontColor($r('app.color.gray'))Button(this.buttonText).fontSize(12).height(26).padding({ left: 20, right: 20 }).linearGradient({angle: 90,colors: [['#FD3F8F', 0], ['#FF773C', 1]]}).margin({ bottom: 20 }).onClick(() => {this.onClickButton()})}.padding(40).width('100%')}
}
List() {if (this.user.token) {if (this.cartList.length) {// 略} else {ListItem() {MkEmpty({tip: '购物袋是空的~',buttonText: '去逛逛',onClickButton: () => {// 去第一个 Tab}})}}} else {// 未登录ListItem() {MkEmpty({tip: '您还未登陆~',buttonText: '请先登录',onClickButton: () => {// 去登录页}})}}ListItem() {MkGuess().margin({ top: 8, bottom: 8 })}
}
git记录
Cart-MkEmpty组件封装
1.3. 去首页
使用Emitter进行线程间通信
需求及分析:
- 已登录,购物车没有商品的时候,点击切换 tab 到第一个【首页】
- feature/cart -> products/phone 可以通过??? 来实现通讯
- products/phone 如何控制 tab 切换
核心步骤:
- 增加 emmit 的事件 id 常量,并导出 (方便管理)
- phone/index 注册事件,接收参数
- feature/cart 触发事件,传递数据
- phone/index 控制 tab 切换
// 其他略
export class EmitterKey {// 切换到指定的 tabstatic CHANGE_TAB: emitter.InnerEvent = { eventId: 10001 }
}
aboutToAppear(): void {this.registerEvent()
}// 注册 emmiter
registerEvent() {// 订阅事件emitter.on(EmitterKey.CHANGE_TAB, (eventData) => {this.activeIndex = eventData.data?.index || 0 // 不传值也能回首页})
}Tabs({ barPosition: BarPosition.End, index: this.activeIndex }){// 其他略
}
MkEmpty({tip: '购物袋是空的',buttonText: '去逛逛',onClickButton: () => {// 发送事件emitter.emit(EmitterKey.CHANGE_TAB, {data: {index: 0}})}
})
git 记录
Cart-去首页
1.4. 加入购物车
接口文档
需求:
- 详情页点击添加到购物车
- 更新服务器数据
核心步骤:
- 抽取 api,丢到 common
- 页面实现交互逻辑
-
- token 校验-》sku 校验-》提交-》api调用-》
- 通过一个类管理所有购物车接口,因为有一整套的 CRUD
import { RequestAxios } from '../utils/Request'interface AddParams {skuId: stringcount: number
}class Cart {// 添加add(data: AddParams) {return RequestAxios.post<null>('/member/cart', data)}
}export const cart = new Cart()
- 导出
// 导出购物车封装购物车请求 API 的类,这个类只需导出一次即可
export { cart } from './src/main/ets/apis/cart'
- 整合业务,在商品详情页调用 加入购物车 接口
// 加入购物车async addToCart() {// 1. 判断用户是否登录,未登录需要登录const token = auth.getUser().tokenif (!token) {promptAction.showToast({ message: '请登录后下单' })this.pageStack.pushPath({ name: 'LoginView' })return}// 2. 判断用户是否有选择商品规则(是否完整)if (!this.sku.id) {promptAction.showToast({ message: '请选择要购买的商品规格' })return}// 3. 加入购物车this.loading = true// await 等待请求成功await cart.add({ skuId: this.sku.id, count: this.count })this.loading = falsepromptAction.showToast({ message: '加入购物袋成功' })// 4. 关闭半模态弹窗this.showSheet = false}// 其他略
Button(this.loading ? '加入中...' : '加入购物袋').buyButton($r('[basic].color.black'), true).onClick(async () => {this.addToCart()})
git 记录
Cart-加入购物车
1.5. 个数及页面跳转
多个地方需要用到个数,并且在很多时候都需要刷新
1.5.1. 详情页面个数更新
需求:
- 获取最新个数(通过接口)
- 更新购物车个数(考虑其他界面获取)
核心步骤:
- 抽取 api:
-
- 有 Token
-
-
- 获取数据,具体的值
-
-
- 没 Token
-
-
- 格式为 0
-
-
- 更新到AppStorage
- 组件中获取并渲染
import { RequestAxios } from '../utils/Request'interface AddParams {skuId: stringcount: number
}interface CountResult {count: number
}export class Cart {// AppStorage 的 KeyCartKey: string = 'cartCount'// 添加add(data: AddParams) {return RequestAxios.post<null>('/member/cart', data)}// 获取购物车数量async count() {let count = 0// 用户已登录才发请求获取const userInfo = auth.getUser()if (userInfo.token) {// 获取购物车数量const res = await RequestAxios.get<CountResult>('/member/cart/count')// 更新变量,用于全局存储count = res.count}// 全局存储购物车数量AppStorage.setOrCreate(this.CartKey, count)}
}export const cart = new Cart()
// 购物车商品数量
@StorageProp(cart.CartKey) cartCount: number = 0Badge({count: this.cartCount,style: {},position: { x: 30, y: 4 }
}) /*** 加入购物车* */
async addToCart() {// 登录判断if (!auth.getUser().token) {promptAction.showToast({message: '请登录后下单'})return pathStack.pushPathByName('LoginView', null)}// 是否选择商品if (!this.sku.id) {return promptAction.showToast({message: '请选择商品规格'})}this.loading = true// 加入购物车await cart.add({skuId: this.sku.id,count: this.count})promptAction.showToast({message: '加入购物袋成功'})// 关闭半模态this.showSheet = falsethis.loading = false// 更新数量cart.count()
}Button(this.loading ? '加入中...' : '加入购物袋').buyButton($r('app.color.black'), true).onClick(async () => {this.addToCart()})
git 记录
Cart-详情页个数更新
1.5.2. 首页更新数量
需求:
- 首页增加角标
- 获取个数并渲染
- 如果是首次打开页面,获取个数(已登录的情况)
核心步骤:
- 增加 Badge 组件
- 通过 AppStorage 获取数据并渲染
- 生命周期钩子中获取数据
- 更新首页购物车数量
@StorageProp(cart.CartKey) cartCount: number = 0aboutToAppear(): void {this.breakpointSystem.register()// 获取购物车数量cart.count()
}@BuilderTabItemBuilder(item: TabItem, index: number) {Badge({count: index === 2 ? this.cartCount : 0,style: {},}) {Column() {Image(this.activeIndex === index ? item.active : item.normal).width(24).aspectRatio(1)Text(item.text).fontColor($r('app.color.black')).fontSize(12)}.justifyContent(FlexAlign.SpaceEvenly).height(50)}
}
git记录
Cart-首页个数更新
1.5.3. 登录和登出更新数量
登录和登出:
- 登录更新:
-
- 账号密码登录,华为登录之后,保存用户信息,同时更新购物车数量
- 登出更新:
-
- 设置页,退出登录时,删除用户信息,同时更新购物车数量
- cart.count() --> 0
- 保存用户信息、删除用户信息时,都要更新购物车数量
import { cart } from "../apis/cart"export interface MkUser {token: stringnickname: stringavatar: stringaccount: string
}class Auth {KEY: string = 'user'initUser() {// 把 AppStorage 的用户信息进行持久化 UI 状态存储,状态改变自动同步到本地磁盘PersistentStorage.persistProp(this.KEY, {})}getUser() {return AppStorage.get<MkUser>(this.KEY) || {} as MkUser}saveUser(user: MkUser) {AppStorage.setOrCreate<MkUser>(this.KEY, user)// 登录时,更新购物车数量cart.count()}removeUser() {AppStorage.setOrCreate<MkUser>(this.KEY, {} as MkUser)// 登出时,更新购物车数量cart.count()}
}// 导出实例化对象,单例
export const auth = new Auth()
git记录
Cart-登录登出更新购物车数量
1.6. 跳转购物车页
需求:
- 点击购物车图标,打开购物袋页面
- tabs打开时,没有返回按钮
- 跳转打开时,显示返回按钮
注意:
- 之前购物袋只是 Tabs 渲染的组件,并不是页面,没有注册页面路由。
核心步骤:
- feature/CartView
-
- 增加 Builder 入口函数 和 NavDestination 组件
- 配置路由表 route_map.json
- 配置 module.json5
- 详情页,点击跳转即可
-
- 优化:详情页跳转的时候【显示返回按钮】,并可以点击【返回上一页】
- 通过携带参数实现效果
- 增加 Builder 入口函数 和 NavDestination 组件
// 1.1 路由入口函数
@Builder
function CartViewBuilder() {// 1.2 子路由组件NavDestination() {CartView()}.hideTitleBar(true)
}@Component
export struct CartView {}
- 新建路由表文件,并配置路由表
{"routerMap": [{"name": "CartView","pageSourceFile": "src/main/ets/views/CartView.ets","buildFunction": "CartViewBuilder"}]
}
- 配置 module.json5,让模块的路由表生效
{"module": {"name": "cart","type": "shared","description": "$string:shared_desc","deviceTypes": ["phone","tablet","2in1"],"deliveryWithInstall": true,"pages": "$profile:main_pages","routerMap": "$profile:route_map"}
}
- 页面跳转,并传参
// 购物袋角标
Badge({count: this.cartCount,style: {},position: { x: 30, y: 4 }
}) {Image($r('[basic].media.ic_public_cart')).iconButton().onClick(() => {pathStack.pushPathByName('CartView', true)})
}
- 获取参数
@State isShowLeftIcon: boolean = falseaboutToAppear(): void {const params = pathStack.getParamByName('CartView') as boolean[]if (params.length > 0) {this.isShowLeftIcon = params.pop() as boolean}
}// 其他内容略
MkNavbar({title: '购物袋',showRightIcon: true,showLeftIcon: this.isShowLeftIcon,leftClickHandler: () => {pathStack.pop()}
})
git 记录
Cart-去购物车页
1.7. 购物车列表渲染
接口文档
需求:
- 获取购物车数据并渲染
核心步骤:
- 抽取接口
- 获取数据之后通过 new 转对象(嵌套数据更新)
- 这个过程需要在 2 种情况下执行(分两步完成)
-
- 首次进入,有 token 就获取(生命周期钩子)
- 添加商品到购物车中(详情页--》购物车页)
1.7.1. 默认渲染
import { RequestAxios } from '../utils/Request'
import { CartGoods } from '../viewmodel'interface AddParams {skuId: stringcount: number
}interface CountResult {count: number
}class Cart {CartKey: string = 'cartCount'// 添加add(data: AddParams) {return RequestAxios.post<null>('/member/cart', data)}// 获取个数async count() {const res = await RequestAxios.get<CountResult>('/member/cart/count')AppStorage.setOrCreate(this.CartKey, res.data.result.count)return res.data.result.count}// 获取列表async list() {return RequestAxios.get<CartGoods[]>('/member/cart')}}export const cart = new Cart()
aboutToAppear(): void {const params = pathStack.getParamByName('CartView') as boolean[]if (params.length > 0) {this.isShowLeftIcon = params.pop() as boolean}this.getData()
}async getData() {if (!auth.getUser().token) {return}const res = await cart.list()this.cartList = res.map((item) => new CartGoodsModel(item))
}
git 记录
Cart-默认渲染
1.7.2. 更新购物车列表
组件可见区域变化事件
问题分析:
- 购物车,每次打开重新获取列表数据
-
- 不是页面,只有 aboutToAppear 钩子
- 第一次打开之后就不会被销毁,不会再次触发
- 详情页
-
- 跳转去购物车是新页面,会触发 aboutToAppear 可以刷新
- tab 页
-
- 切换到 购物车 tab 时需要获取列表(不会再次触发aboutToAppear)
思考:如何实现 购物车页面数据刷新?
- 通过 emitter 触发刷新
- 组件可视区域改变时刷新 onVisibleAreaChange
@Component
export struct CartView {// ...省略aboutToAppear(): void {const params = pathStack.getParamByName('CartView') as boolean[]if (params.length > 0) {this.isShowLeftIcon = params.pop() as boolean}// this.getData() 拿掉}// 获取购物袋列表async getData() {// 用户已登录的情况下,才获取购物袋列表const userInfo = auth.getUser()if (userInfo.token) {// 显示加载框this.dialog.open()const res = await cart.list()// map 把每个普通对象转换成 CartGoodsModel 对象,更新对象时图片不会闪动this.cartList = res.data.result.map(item => new CartGoodsModel(item))// 隐藏加载框this.dialog.close()}}// ...省略build() {Column() {// ...}.height('100%').width('100%').backgroundColor($r('[basic].color.under')).onVisibleAreaChange([0, 1], (isVisible) => {if (isVisible) {this.getData()}})}
}
git 记录
Cart-更新购物车列表
1.8. 个数更新
接口文档
需求:
- 修改【个数】时
- 更新服务器,更新本地
- 更新购物车个数
核心步骤:
- 实现Count组件的个数渲染及禁用外观
- 累加及递减逻辑:
-
- 修改服务器数据(*)
- 更新本地数据
- 更新个数
- 伴随 loading 效果
- 选中:
-
- 修改服务器数据(*)
- 更新本地数据
import { auth } from '../../../../Index'
import { RequestAxios } from '../utils/Request'
import { CartGoods } from '../viewmodel'// 导出 页面中 会用到
export interface UpdateParams {selected?: booleancount?: number
}interface AddParams {skuId: stringcount: number
}interface CountResult {count: number
}export const CartKey: string = 'cartCount'export class Cart {// 添加add(data: AddParams) {return RequestAxios.post<null>('/member/cart', data)}// 获取个数async count() {let count = 0if (auth.getUser().token) {const res = await RequestAxios.get<CountResult>('/member/cart/count')count = res.data.result.count}AppStorage.setOrCreate(CartKey, count)}// 获取列表async list() {return RequestAxios.get<CartGoods[]>('/member/cart')}// 更新商品async update(skuId: string, data: UpdateParams) {return RequestAxios.put<CartGoods>(`/member/cart/${skuId}`, data)}
}export const cart = new Cart()
// 其他略
async updateCart(params: UpdateParams) {await cart.update(this.cart.skuId, params)// 如果是更新个数 不传递时为 undefinedif (params.count) {this.cart.count = params.countcart.count()}
}// 计数器
Counter() {Text(this.cart.count.toString())
}
.enableDec(this.cart.count > 1) // 控制减号是否可用
.enableInc(this.cart.count < this.cart.stock) // 控制加号是否可用
.onInc(() => {this.updateCart({ count: this.cart.count + 1 })
})
.onDec(() => {this.updateCart({ count: this.cart.count - 1 })
})
git 记录
Cart-个数及选中状态更新
1.9. MkCheckBox组件
checkBox
原生的 CheckBox 使用起来有一些不便,咱们来:
- 试试这个不便之处
- 然后自己实现个组件,替换到 购物车页面的 【列表项】和【全选】
如果不用双向绑定那么 选中状态 和 绑定的状态变量就没有关联
@Entry
@Component
struct Page06 {@State isChecked: boolean = falsebuild() {Column() {Text(JSON.stringify(this.isChecked))Checkbox().select(this.isChecked).onClick(() => {this.isChecked = true})}.height('100%').width('100%').backgroundColor(Color.Orange).padding(40)}
}
基础模版
@ComponentV2
export struct MkCheckBox {build() {// $r('app.media.ic_public_check_filled')选中 $r('app.media.ic_public_check') 未选中Image($r('app.media.ic_public_check_filled')).width(18)// $r('app.color.red') 选中 $r('app.color.gray') 默认.fillColor($r('app.color.red')).aspectRatio(1).margin(2)}
}
// 其他略 记得导出
export { MkCheckBox } from './src/main/ets/components/MkCheckBox'
// 添加全选 其他略
MkCheckBox({
})
Text('全选').fontSize(14).fontColor($r('[basic].color.black')).margin({ right: 20 })@ComponentV2
struct CartItemComp {@Paramcart: CartGoodsModel = new CartGoodsModel({} as CartGoods)async updateCart(params: UpdateParams) {await cart.update(this.cart.skuId, params)if (params.selected !== undefined) {this.cart.selected = params.selected}if (params.count) {this.cart.count = params.countcart.count()}}build() {Row({ space: 10 }) {// 添加单选 其他略MkCheckBox({})Image(this.cart.picture).width(80).height(80)}.width('100%').height(100).padding(10).border({ width: { bottom: 0.5 }, color: '#e4e4e4' }).backgroundColor($r('[basic].color.white'))}
}
属性名 | 类型 | 说明 | 默认值 |
checked | boolean | 是否选中 | false |
boxSize | number | 大小 | 18 |
onClickBox | ()=>void | 点击组件 | ()=>{} |
@ComponentV2
export struct MkCheckBox {@Param checked: boolean = false@Param boxSize: number = 18@EventonClickBox: () => void = () => {}build() {Image(this.checked ? $r('app.media.ic_public_check_filled') : $r('app.media.ic_public_check')).width(this.boxSize).fillColor(this.checked ? $r('app.color.red') : $r('app.color.gray')).aspectRatio(1).margin(2).onClick(() => this.onClickBox())}
}
git记录
Cart-MkCheckBox组件封装
1.10. 选中状态更新
接口文档
需求:
- 修改【选中状态】时
- 更新服务器,更新本地
核心步骤:
- 点击选中:
-
- 修改服务器数据(*)
- 更新本地数据
@ComponentV2
struct CartItemComp {@Paramcart: CartGoodsModel = new CartGoodsModel({} as CartGoods)async updateCart(params: UpdateParams) {await cart.update(this.cart.skuId, params)if (params.selected !== undefined) {this.cart.selected = params.selected}if (params.count) {this.cart.count = params.countcart.count()}}build() {Row({ space: 10 }) {MkCheckBox({checked: this.cart.selected, onClickBox: () => {this.updateCart({ selected: !this.cart.selected })}})Image(this.cart.picture).width(80).height(80)Column({ space: 8 }) {Text(this.cart.name).textOverflow({ overflow: TextOverflow.Ellipsis }).maxLines(1).fontSize(14).fontColor($r('[basic].color.black')).width('100%')Row() {Text(this.cart.attrsText).textOverflow({ overflow: TextOverflow.Ellipsis }).maxLines(1).fontSize(12).fontColor($r('[basic].color.text')).layoutWeight(1)Image($r('sys.media.ohos_ic_public_arrow_down')).fillColor($r('[basic].color.gray')).width(16).height(16)}.padding({ left: 6, right: 4 }).width(100).height(24).backgroundColor($r('[basic].color.under')).borderRadius(2)Row() {Text(`¥${this.cart.price}`).fontSize(14).fontWeight(500)Counter() {Text(this.cart.count.toString())}.enableDec(this.cart.count > 0).onInc(() => {this.updateCart({ count: this.cart.count + 1 })}).onDec(() => {this.updateCart({ count: this.cart.count - 1 })})}.width('100%').justifyContent(FlexAlign.SpaceBetween)}.layoutWeight(1).alignItems(HorizontalAlign.Start)}.width('100%').height(100).padding(10).border({ width: { bottom: 0.5 }, color: '#e4e4e4' }).backgroundColor($r('[basic].color.white'))}
}
git记录
Cart-选中状态更新
1.11. 全选
接口文档
Array.prototype.every() - JavaScript | MDN
需求:
- 点击列表项目,同步更新全选状态
- 点击全选,批量设置每一项的选中状态
核心步骤:
- 计算全选状态并绑定(方法):
-
- 返回每一项是否被选中(selected==true)(every)
- 点击子组件(CheckBox)通过回调函数通知父组件(强制刷新页面)
- 抽取 api
- 点击全选:
-
- 获取本地选中状态
- 调用接口更新服务器
- 设置列表项的选中状态
import { auth } from '../../../../Index'
import { RequestAxios } from '../utils/Request'
import { CartGoods } from '../viewmodel'interface UpdateParams {selected?: booleancount?: number
}interface AddParams {skuId: stringcount: number
}interface CountResult {count: number
}interface CheckAllParams {selected: boolean
}export const CartKey: string = 'cartCount'export class Cart {// 添加add(data: AddParams) {return RequestAxios.post<null>('/member/cart', data)}// 获取个数async count() {let count = 0if (auth.getUser().token) {const res = await RequestAxios.get<CountResult>('/member/cart/count')count = res.data.result.count}AppStorage.setOrCreate(CartKey, count)}// 获取列表list() {return RequestAxios.get<CartGoods[]>('/member/cart')}// 更新商品update(skuid: string, data: UpdateParams) {return RequestAxios.put<CartGoods>(`/member/cart/${skuid}`, data)}// 全选checkAll(data: CheckAllParams) {return RequestAxios.put<null>('/member/cart/selected', data)}
}export const cart = new Cart()
// 全选
MkCheckBox({checked: this.cartList.every(v => v.selected),onClickBox: async () => {const isCheckedAll = this.cartList.every(v => v.selected)await cart.checkAll({ selected: !isCheckedAll })this.cartList.forEach(v => v.selected = !isCheckedAll)}})
git 记录
Cart-全选
1.12. 总价格
链接
需求:
- 根据选中状态,个数,单价计算总价格
核心步骤:
- 实现方法:
-
- 筛选-》累加-》小数点 2 位-》转字符串
- 组件中使用该方法
// 定义计算总价的函数
/*** 总价格* */totalCount() {return this.cartList.filter(v => v.selected).reduce((acc, cur) => acc + cur.price * cur.count, 0).toFixed().toString()}Text('合计:').fontSize(14).fontColor($r('[basic].color.black')).margin({ right: 2 })
Text(this.totalCount()).fontSize(16).fontWeight(500).fontColor($r('[basic].color.red')).layoutWeight(1)
git 记录
Cart-总价格
1.13. 删除
接口文档
需求:
- 点击删除,删除购物车数据
核心步骤:
- 抽取接口(坑,delete 请求方法第二个参数和之前的不同)
- 点击删除调用接口,删除服务器数据
- 更新个数
- 删除本地
- 伴随 loading 效果
export class RequestAxios {// get -> params -> { params: {} }// T 响应的内容 的类型!!static get<T>(url: string, params?: object): Promise<T> {return axiosInstance.get<null, T>(url, { params })}static getPlus<T>(url: string, config?: AxiosRequestConfig): Promise<T> {return axiosInstance.get<null, T>(url, config)}// post -> data -> { data: {} }static post<T>(url: string, data?: object): Promise<T> {return axiosInstance.post<null, T>(url, data)}static delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {return axiosInstance.delete<null, T>(url, config)}static put<T>(url: string, data?: object): Promise<T> {return axiosInstance.put<null, T>(url, data)}
}
import { Log } from '@abner/log'
import { auth, Logger } from '../../../../Index'
import { RequestAxios } from '../utils/Request'
import { CartGoods } from '../viewmodel'interface UpdateParams {selected?: booleancount?: number
}interface AddParams {skuId: stringcount: number
}interface CountResult {count: number
}interface CheckAllParams {selected: boolean
}// 删除的参数
interface DeleteParams {ids: string[]
}export const CartKey: string = 'cartCount'export class Cart {// 添加add(data: AddParams) {return RequestAxios.post<null>('/member/cart', data)}// 获取个数async count() {let count = 0if (auth.getUser().token) {const res = await RequestAxios.get<CountResult>('/member/cart/count')count = res.data.result.count}AppStorage.setOrCreate(CartKey, count)}// 获取列表async list() {return RequestAxios.get<CartGoods[]>('/member/cart')}// 更新商品async update(skuid: string, data: UpdateParams) {return RequestAxios.put<CartGoods>(`/member/cart/${skuid}`, data)}// 全选async checkAll(data: CheckAllParams) {return RequestAxios.put<null>('/member/cart/selected', data)}// 删除async remove(data: DeleteParams) {return RequestAxios.delete<null>('/member/cart', { data: data })}
}export const cart = new Cart()
// 其他略
ForEach(this.cartList,(item: CartGoodsModel, index: number) => {ListItem() {CartItemComp({cart: item})}.backgroundColor($r('[basic].color.under')).padding({ left: 8, right: 8 }).transition({ type: TransitionType.Delete, opacity: 0 }).swipeAction({end: this.DeleteBuilder(async () => {AlertDialog.show({title: '温馨提示',message: '确定删除该商品吗?',buttons: [{value: '取消', fontColor: $r('[basic].color.gray'), action: () => {}},{value: '确定', fontColor: $r('[basic].color.black'), action: async () => {// 服务器端删除,根据 skuId 删除商品await cart.remove({ ids: [item.skuId] })// 本地删除this.cartList.splice(index, 1)// 更新购物袋数量await cart.count()}},]})})})})
git 记录
Cart-删除购物袋商品
2. bug-ForEach 的keyGenerator参数
ForEach 键值规则
ForEach的第三个参数如果不给,那么默认值
bug 复现
- 保证购物车有数据,测试交互效果
- 切换到其他tab,再切换购物车,测试交互效果
- 修改选中状态,修改个数,无法触发【全选】 和 【价格】的重新计算
import { promptAction } from '@kit.ArkUI'
import { JSON } from '@kit.ArkTS'@ObservedV2
class Dog {id: number@Tracename: string@Tracechecked: booleanconstructor(id: number, name: string) {this.id = idthis.name = namethis.checked = false}
}@Entry
@Component
struct Parent {@StatesimpleList: Array<Dog> = [new Dog(1, '1狗'),new Dog(2, '2狗'),new Dog(3, '3狗'),];isCheckedAll() {console.log('计算是否全部选中')return this.simpleList.every(v => v.checked)}build() {Row() {Column() {Button('打印数据').margin(10).onClick(() => {promptAction.showToast({message: JSON.stringify(this.simpleList)})})Text('点击替换成相同的数据').fontSize(24).fontColor(Color.Red).onClick(() => {this.simpleList = this.simpleList.map(v => {return new Dog(v.id, v.name)})})Text(this.isCheckedAll() ? '全部选中' : '未全选').fontSize(24).fontColor(Color.Blue).margin(10)ForEach(this.simpleList, (item: Dog, index: number) => {ChildItem({ item: item }).margin({ top: 20 })}, (item: Dog, index: number) => {return JSON.stringify(item) + ' ' + index})}.justifyContent(FlexAlign.Center).width('100%').height('100%')}.height('100%').backgroundColor(0xF1F3F5)}
}@ComponentV2
struct ChildItem {@Param item: Dog = new Dog(0, '')aboutToAppear(): void {console.log('aboutToAppear' + this.item.id)}build() {Row() {Text(this.item.checked ? '√' : 'x').fontSize(20).margin({ right: 10 }).onClick(() => {this.item.checked = !this.item.checked})Text(this.item.id + '|' + this.item.name).fontSize(20).onClick(() => {this.item.name += '1'})}}
}
结论:
- 默认的键值生成逻辑,在使用相同数据覆盖的时候不会触发子组件的重新生成
- 子组件交互时修改的依旧是上一份数据,但是数据源已经更改所以无法触发 UI 更新
解决方案:
- 自定义 keyGenerator 函数,希望子组件全部重新创建时,使生成结果和上一次不同
- 可以使用时间戳,随机数,累加的数。。核心就是保证 循环的 key 发生改变
优化代码
import { promptAction } from '@kit.ArkUI'
import { JSON } from '@kit.ArkTS'
import { data } from '@kit.TelephonyKit'@ObservedV2
class Dog {id: number@Tracename: string@Tracechecked: booleanconstructor(id: number, name: string) {this.id = idthis.name = namethis.checked = false}
}@Entry
@Component
struct Parent {@StatesimpleList: Array<Dog> = [new Dog(1, '1狗'),new Dog(2, '2狗'),new Dog(3, '3狗'),];num: number = 0isCheckedAll() {console.log('计算是否全部选中')return this.simpleList.every(v => v.checked)}build() {Row() {Column() {Button('打印数据').margin(10).onClick(() => {promptAction.showToast({message: JSON.stringify(this.simpleList)})})Text('点击替换成相同的数据').fontSize(24).fontColor(Color.Red).onClick(() => {this.num = Date.now()// 基于原始数据 生成新数据this.simpleList = this.simpleList.map(v => {return new Dog(v.id, v.name)})})Text(this.isCheckedAll() ? '全部选中' : '未全选').fontSize(24).fontColor(Color.Blue).margin(10)ForEach(this.simpleList, (item: Dog, index: number) => {ChildItem({ item: item }).margin({ top: 20 })}, (item: Dog, index: number) => {console.log('ForEach' + item.id)return JSON.stringify(item)+this.num})}.justifyContent(FlexAlign.Center).width('100%').height('100%')}.height('100%').backgroundColor(0xF1F3F5)}
}@ComponentV2
struct ChildItem {@Param item: Dog = new Dog(0, '')aboutToAppear(): void {console.log('aboutToAppear' + this.item.id)}build() {Row() {Text(this.item.checked ? '√' : 'x').fontSize(20).margin({ right: 10 }).onClick(() => {this.item.checked = !this.item.checked})Text(this.item.id + '|' + this.item.name).fontSize(20).onClick(() => {this.item.name += '1'})}}
}
// 额外添加到随机数中
refreshNum: number = 0/*** 获取数据* */
async getData() {if (!auth.getUser().token) {return}const res = await cart.list()this.cartList = res.map((item) => new CartGoodsModel(item))this.refreshNum = Date.now()
}ForEach(this.cartList,(item: CartGoodsModel, index: number) => {// 略},(item: CartGoodsModel, index: number) => {return JSON.stringify(item) + this.refreshNum})
3. 自定义弹窗优化
3.1. 弹窗错误优化
错误信息
目前使用的弹窗会有错误提示,错误原因是:
- 103302: 内容节点对应自定义弹窗已存在
- 103303:无法找到内容节点对应的自定义弹窗
咱们来优化一下弹框的逻辑
- 多次请求只有第一次弹框
- 多次请求最后一次结束的时候关闭弹框
// 其他略let count =0// 添加请求拦截器
axiosInstance.interceptors.request.use((config: InternalAxiosRequestConfig) => {// 如果用户信息中有 token 就在请求头中携带 tokenconst user = auth.getUser()if (user.token) {config.headers.Authorization = `Bearer ${user.token}`}// 对请求数据做点什么if (count === 0) {PromptActionClass.openDialog()}count++return config;
}, (error: AxiosError) => {// 对请求错误做些什么return Promise.reject(error);
});interface ErrorType {message: stringmsg: stringcode: string
}// 添加响应拦截器
axiosInstance.interceptors.response.use((response: AxiosResponse) => {// 对响应数据做点什么count--if (count <= 0) {PromptActionClass.closeDialog()}return response.data.result
}, (error: AxiosError<ErrorType>) => {// 只要 http 状态码 不在 200-299 以内 就会进到这个异常// 400 参数错误 (用户名密码登录 输入错误密码)// Logger.info(error.response?.status)if (error.response?.status === 400) {// Logger.info( error.response.data as object)promptAction.showToast({message: error.response.data.message})} else if (error.response?.status === 401) {// 删除 tokenauth.removeUser()// 提示用户promptAction.showToast({message: error.response.data.message})// 去登陆pathStack.pushPathByName('LoginView', null)}count--if (count <= 0) {PromptActionClass.closeDialog()}// 401 登录过期 (给错误的 token)// 对响应错误做点什么return Promise.reject(error);
});
git 记录
fix-优化全局弹框错误提示
相关文章:
1. 购物车
1. 购物车 咱们购物车基于 V2 装饰器进行开发,底气来源于 自定义组件混用场景指导 1.1. 素材整合 observedv2和Trace 数据模型和页面 // 其他略 // 购物车 export interface CartGoods {count: number;id: string;name: string;picture: string;price: number;…...
frp 让服务器远程调用本地的服务(比如你的java 8080项目)
1、服务器上安装frp 2、本地安装frp 服务器上 frps.toml 配置信息: bindPort 30000auth.token "密码" # 客户端连接密码vhostHTTPPort 8082 本地 frpc.toml serverAddr "服务器ip" serverPort 30000 auth.token "服务器上设置的…...
《AI大模型应知应会100篇》第56篇:LangChain快速入门与应用示例
第56篇:LangChain快速入门与应用示例 前言 最近最火的肯定非Manus和OpenManus莫属,因为与传统AI工具仅提供信息不同,Manus能完成端到端的任务闭环。例如用户发送“筛选本月抖音爆款视频”,它会自动完成: 爬取平台数据…...
大模型——如何在本地部署微软的OmniParser V2
微软的 OmniParser V2 是一款尖端的人工智能屏幕解析器,可通过分析屏幕截图从图形用户界面中提取结构化数据,使人工智能代理能够与屏幕元素进行无缝交互。该工具是构建自主图形用户界面代理的完美选择,它改变了自动化和工作流程优化的游戏规则。在本指南中,我们将介绍如何在…...
Oracle触发器使用(一):DML触发器
Oracle触发器使用(一):DML触发器 DML触发器条件谓词触发器INSTEAD OF DML触发器复合DML触发器Oracle数据库中的触发器(Trigger)本质上也是PL/SQL代码,触发器可以被Enable或者Disable,但是不能像存储过程那样被直接调用执行。 触发器不能独立存在,而是定义在表、视图、…...
智慧园区大屏如何实现全局监测:监测意义、内容、方式
智慧园区的价值不容小觑呀,可以说园区的大部分数据都在这个大屏上,监测数据越多,那么大屏的价值就越大。很多小伙伴拿到需求后感觉无从下手,本文在这里智慧园区大屏可以监测哪些内容、监测的意义、监测的方式等,欢迎点…...
LeetCode 解题思路 31(Hot 100)
解题思路: 递归参数: 字符串 s、结果集 result、当前路径 path、回文子串数组 dp、开始位置 start。递归过程: 当当前路径 path 的长度等于 s.length() 时,说明已经分割完成,加入结果集。若当前起止位置满足回文条件…...
fastAPI详细介绍以及使用方法
FastAPI是一个现代的Python web框架,它提供快速构建API的能力。它具有高性能、易用性和文档自动生成的特点,使得开发者能够快速开发高效的API服务。 以下是一些FastAPI的主要特点和优势: 快速:FastAPI基于Python 3.6的异步框架St…...
数字人训练数据修正和查看 不需要GPU也能运行的DH_live-加载自己训练-
自己训练模pth报错 le "D:\ai\dh_live\app.py", line 42, in demo_mini interface_mini(asset_path, wav_path, output_video_name) File "D:\ai\dh_live\demo_mini.py", line 21, in interface_mini renderModel_mini.loadModel("checkpoi…...
WGAN-GP 原理及实现(pytorch版)
WGAN-GP 原理及实现 一、WGAN-GP 原理1.1 WGAN-GP 核心原理1.2 WGAN-GP 实现步骤1.3 总结二、WGAN-GP 实现2.1 导包2.2 数据加载和处理2.3 构建生成器2.4 构建判别器2.5 训练和保存模型2.6 图片转GIF一、WGAN-GP 原理 Wasserstein GAN with Gradient Penalty (WGAN-GP) 是对原…...
chromium魔改——navigator.webdriver 检测
chromium源码官网 https://source.chromium.org/chromium/chromium/src 说下修改的chromium源码思路: 首先在修改源码过检测之前,我们要知道它是怎么检测的,找到他通过哪个JS的API来做的检测,只有知道了如何检测,我们…...
Sentinel[超详细讲解]-7 -之 -熔断降级[异常比例阈值]
📖 主要讲解熔断降级之 --- 异常比例阈值 🚀 1️⃣ 背景 Sentinel 以流量作为切入点,提供了很多的丰富的功能,例如🤗: 流量控制,熔断降级等,它能够有效的适用各个复杂的业务场景&am…...
程序化广告行业(56/89):S2S对接与第三方广告监测全解析
程序化广告行业(56/89):S2S对接与第三方广告监测全解析 大家好!在前面的博客中,我们一起深入学习了程序化广告的人群标签、用户标签生成、Look Alike原理,以及DMP与DSP对接和数据统计原理等内容。今天&…...
C++进阶知识复习 31~38
目的 写这一系列文章的目的主要是为了秋招时候应对计算机基础问题能够流畅的回答出来 (如果不整理下 磕磕绊绊的回答会被认为是不熟悉) 本文章题目的主要来源来自于 面试鸭 部分面试鸭上没有而牛客网上有的博主会进行查缺补漏 题目编号按照面试鸭官网…...
Spring Boot 整合mybatis
2025/4/3 向全栈工程师迈进!!! 配置文件在实际开发中如何使用呢,接下去将通过Spring Boot整合mybatis来看配置文件如何在实际开发中被使用。 一、引入mybatis起步依赖 在pom.xml中引入mybatis的起步依赖,如下。 在这…...
新能源汽车测试中的信号调理模块:从原理到实战应用
摘要 信号调理模块(Signal Conditioning Module, SCM)是新能源汽车(NEV)测试系统中的关键环节,直接影响数据采集的精度与可靠性。本文面向HIL测试工程师、电机测试工程师及整车动力经济性测试工程师,系统性…...
一篇关于Netty相关的梳理总结
一篇关于Netty的梳理总结 一、Netty1.1 什么是netty?为什么要用netty1.2 Netty是什么? 二、Netty关于网络基础2.1 线程池2.2 线程池线程的生命周期和状态2.3 为什么要使用线程池2.4 简述线程池原理,FixedThreadPool用的阻塞队列是什么2.5 并发…...
纺织车间数字化转型:降本增效新路径
在纺织行业竞争日益激烈的当下,如何提升生产效率、降低成本成为企业关注的焦点。某纺织车间通过创新引入明达技术MBox20网关并部署IOT 平台,成功接入千台设备,实现了生产模式的重大变革。 以往,纺织车间生产犹如黑箱,…...
递归典例---汉诺塔
https://ybt.ssoier.cn/problem_show.php?pid1205 #include<bits/stdc.h> #define endl \n #define pii pair<int,int>using namespace std; using ll long long;void move(int n,char a,char b,char c) // n 个盘子,通过 b,从 a 移动到 …...
Unity2D:从零开始制作一款跑酷游戏!
目录 成品展示 美术资源 制作步骤 场景预布设: 实现人物基础功能: 移动背景——横向卷轴: 生成障碍物: 生成敌人与攻击逻辑: UI制作与重新开始: 导出游戏: 小结 大家小时候都玩过《…...
维拉工时自定义字段:赋能项目数据的深度洞察 | 上新预告
原文链接:维拉工时自定义字段:赋能项目数据的深度洞察 | 上新预告 在项目管理实践中,每个企业都有独特的数据统计与分析需求。为了帮助用户实现个性化数据建模,从而更精准地衡量项目进度和预算投入,维拉工时全新升级的…...
C++ | 文件读写(ofstream/ifstream/fstream)
一、C文件操作核心类 C标准库通过<fstream>提供了强大的文件操作支持,主要包含三个关键类: 类名描述典型用途ofstream输出文件流(Output File Stream)文件写入操作ifstream输入文件流(Input File Stream&#…...
flux文生图部署笔记
目录 依赖库: 文生图推理代码cpu: cuda版推理: 依赖库: tensorrt安装: pip install nvidia-pyindex # 添加NVIDIA仓库索引 pip install tensorrt 文生图推理代码cpu: import torch from diffusers import FluxPipelinemodel_id = "black-forest-labs/FLUX.1-s…...
二语习得理论(Second Language Acquisition, SLA)如何学习英语
二语习得理论(Second Language Acquisition, SLA)是研究学习者如何在成人或青少年阶段学习第二语言(L2)的理论框架。该理论主要关注语言习得过程中的认知、社会和文化因素,解释了学习者如何从初学者逐渐变得流利并能够…...
策略模式实际用处,改吧改吧直接用,两种方式
controller RestController RequestMapping("admin/test") RequiredArgsConstructor(onConstructor __(Autowired)) public class TestController {Autowiredprivate VideoFactory VideoFactory;GetMapping("getList")public R getList(){// 第一种方式T…...
计算机网络-TCP的流量控制
内容来源:小林coding 本文是对小林coding的TPC流量控制的精简总结 什么是流量控制 发送方不能无脑的发数据给接收方,要考虑接收方处理能力 如果一直无脑的发数据给对方,但对方处理不过来,那么就会导致触发重发机制 从而导致网…...
搬砖--贪心+排序的背包
a在上面b在下面->a.v-M-b.m>b.v-M-a.m->剩余率大 所以我先遍历a,让a在上面 这就是要考虑贪心排序的01背包 因为它有放的限制条件 #include<bits/stdc.h> using namespace std; #define N 100011 typedef long long ll; typedef pair<ll,int>…...
git克隆数据失败
场景:当新到一家公司,然后接手了上一个同时的电脑,使用git克隆代码一直提示无法访问,如图 原因:即使配置的新的用户信息。但是window记录了上一个同事的登录信息,上一个同事已经被剔除权限,再拉…...
Dart 语法
1. 级联操作符 … var paint Paint()..color Colors.black..strokeCap StrokeCap.round..strokeWidth 5.0;2. firstWhereOrNull 3. 隐藏或导入部分组件 // Import only foo. import package:lib1/lib1.dart show foo;// Import all names EXCEPT foo. import package:lib…...
34、web前端开发之JavaScript(三)
十. DOM操作详解 1、DOM简介 文档对象模型(DOM,Document Object Model)是JavaScript与网页内容交互的接口。它将HTML文档表示为一种树状结构(DOM树),其中每个节点代表文档的一部分(例如元素、…...
自适应卡尔曼滤波
目录 自适应卡尔曼滤波 自适应卡尔曼滤波 主要作用,去抖动 AdaptiveKalmanFilter.py import cv2 import numpy as np from collections import deque# ------------------ 核心去抖算法 ------------------ import cv2 import numpy as np from collections import deque#…...
EIP-712:类型化结构化数据的哈希与签名
1. 引言 以太坊 EIP-712: 类型化结构化数据的哈希与签名,是一种用于对类型化结构化数据(而不仅仅是字节串)进行哈希和签名 的标准。 其包括: 编码函数正确性的理论框架,类似于 Solidity 结构体并兼容的结构化数据规…...
Day 3:Leetcode 比特位计数+只出现一次的数字 II
比特位计数 本质是一个递推,时间复杂度O(n),空间复杂度O(n)。 class Solution { public:vector<int> countBits(int n) {vector<int> ans(n 1);ans[0] 0;//ans[1] 1;for(int i 1; i < n; i){if(i & 1){ans[i] ans[i/2] 1;}else…...
CentOS7安装conda
root用户登录虚拟机后更新yum yum update 下载Miniconda,路径就在/root下 wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh conda分为Miniconda和Anaconda, Miniconda是Conda的轻量级版本,只包含conda和Python…...
[Linux]从零开始的vs code交叉调试arm Linux程序教程
一、前言 最近的项目中需要集成rknn的视觉识别,在这之前我并且没有将rknn集成到自己项目的经验。这里我需要在rknn原本demo的基础上我还需要集成自己的业务代码。但是又有一个问题,原本rknn我们都是使用交叉编译编译到开发板上的,并且我们还要…...
【顶刊级科研绘图】AI支持下Nature级数据可视化(如何画图、如何标注、如何改图、如何美化、如何组合、如何排序)
技术点目录 第一章、绘图原则与概念:规范清晰简洁自明第二章、DeepSeek、ChatGPT、R绘图系统:八仙过海各显神通第三章、美学设计与细节:完美图表华丽呈现第四章、数据类型与图表:宝典在手各个击破第五章、统计分析与可视化&#x…...
CSRF跨站请求伪造——入门篇【DVWA靶场low级别writeup】
CSRF跨站请求伪造——入门篇 0. 前言1. 什么是CSRF2. 一次完整的CSRF攻击 0. 前言 本文将带你实现一次完整的CSRF攻击,内容较为基础。需要你掌握的基础知识有: 了解cookie;已经安装了DVWA的靶场环境(本地的或云的)&am…...
Spring Boot应用中实现Jar包热更新的实践指南
Spring Boot应用中实现Jar包热更新的实践指南 一、引言 在现代软件开发中,快速迭代和持续交付是至关重要的。对于基于Spring Boot的应用程序,一旦部署到生产环境,传统的更新方式通常是重新打包并重启应用,这不仅耗时,…...
JVM深入原理(七)(一):运行时数据区
目录 7. JVM运行时数据区 7.1. 运行时数据区-总览 7.2. 运行时数据区-查看内存对象 7.3. 运行时数据区-程序计数器 7.3.1. 程序计数器-作用 7.3.2. 字节码指令执行流程 7.4. 运行时数据区-Java虚拟机栈 7.4.1. 栈-概述 7.4.2. 栈帧-组成 7.4.2.1. 栈帧-帧数据 7.4.2…...
约瑟夫环的四种(数组,链表,递归,迭代)解决方案,与空间、时间复杂度分析
以下方法均没有考虑结果集的空间与时间复杂度 1.数组解法 实现代码(未优化) class Main {public static void main(String[] args){Scanner read new Scanner(System.in);int n read.nextInt();int m read.nextInt();int[] people new int[n]; //…...
skynet.start 的作用详细解析
目录 skynet.start 的作用详细解析1. 功能概述2. 基本用法3. 关键作用(1) 注册消息处理函数(2) 启动事件循环(3) 服务生命周期管理 4. 与其他函数的协作5. 未调用 skynet.start 的后果6. 高级场景:何时不需要 skynet.start7. 总结 skynet.start 的作用详细解析 在 …...
Apache Doris 2025 Roadmap:构建 GenAI 时代实时高效统一的数据底座
在全球 290 位开发者的协作下,Apache Doris 在 2024 年完成了 7000 次代码提交,并发布了 22 个版本,实现在实时分析、湖仓一体和半结构化数据分析等核心场景的技术突破及创新。 2025 年,Apache Doris 社区将秉承“以场景驱动创新…...
springboot+easyexcel实现下载excels模板下拉选择
定义下拉注解 Target(ElementType.FIELD) Retention(RetentionPolicy.RUNTIME) public interface ExcelDropDown {/*** 固定下拉选项*/String[] source() default {};/*** 动态数据源key(从上下文中获取)*/String sourceMethod() default "";…...
vue3+ts+element-plus 开发一个页面模块的详细过程
目录、文件名均使用kebab-case(短横线分隔式)命名规范 子组件目录:./progress-ctrl/comps 1、新建页面文件 progress-ctrl.vue <script setup lang"ts" name"progress-ctrl"></script><template>&l…...
软考《信息系统运行管理员》- 7.1 物联网运维
物联网的概念及特征 物联网是在计算机互联网的基础上,通过射频识别 (RFID) 、 无线传感器、红外感应器、 全球定位系统、激光扫描器等信息传感设备,按约定的协议,把物与物之间通过网络连接起来, 进行信息交换和通信,以…...
【GPT入门】第33 课 一文吃透 LangChain:chain 结合 with_fallbacks ([]) 的实战指南
[TOC](【GPT入门】第33课 一文吃透 LangChain:chain 结合 with_fallbacks ([]) 的实战指南) 1. fallback概述 模型回退,可以设置在llm上,也可以设置在chain上,都带有with_fallbacks([])函数 2. llm的回退 2.1 代码 核心代码&…...
裴蜀定理:整数解的奥秘
裴蜀定理:整数解的奥秘 在数学的世界里,裴蜀定理(Bzout’s Theorem)是数论中一个非常重要的定理,它揭示了二次方程和整数解之间的关系。它不仅仅是纯粹的理论知识,还在计算机科学、密码学、算法优化等多个…...
Table as Thought论文精读
标题:Table as Thought: Exploring Structured Thoughts in LLM Reasoning 作者:Zhenjie Sun, Naihao Deng, Haofei Yu, Jiaxuan You 单位:University of Illinois Urbana-Champaign, University of Michigan 摘要: llm的推理…...
PyQt6实例_A股日数据维护工具_使用
目录 前置: 下载预备更新的数据 使用工具更新 用工具下载未复权、前复权、权息数据 在PostgreSQL添加两个数据表 工具&视频 前置: 1 本系列将以 “PyQt6实例_A股日数据维护工具” 开头放置在“PyQt6实例”专栏 2 日数据可在“数据库”专栏&…...
MySQL客户端工具-图形化工具-DataGrip 安装与使用
一. 常见的图形化工具 二. DataGrip 安装 官网:DataGrip:由 JetBrains 开发的数据库和 SQL 跨平台 IDE 二. DataGrip 使用...