鸿蒙开发笔记(七):应用状态管理,LocalStorage及AppStorage的使用

开发者要实现应用级的,或者多个页面的状态数据共享,就需要用到应用级别的状态管理的概念。ArkTS根据不同特性,提供了多种应用状态管理的能力:

  • LocalStorage:页面级UI状态存储,通常用于UIAbility内、页面间的状态共享。

  • AppStorage:特殊的单例LocalStorage对象,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储;

  • PersistentStorage:持久化存储UI状态,通常和AppStorage配合使用,选择AppStorage存储的数据写入磁盘,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同;

  • Environment:应用程序运行的设备的环境参数,环境参数会同步到AppStorage中,可以和AppStorage搭配使用。

1. LocalStorage:页面级UI状态存储

LocalStorage是页面级的UI状态存储,通过@Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例。LocalStorage也可以在UIAbility内,页面间共享状态。

LocalStorage使用场景和相关的装饰器:@LocalStorageProp和@LocalStorageLink。

LocalStorage是ArkTS为构建页面级别状态变量提供存储的内存内“数据库”。

  • 应用程序可以创建多个LocalStorage实例,LocalStorage实例可以在页面内共享,也可以通过GetShared接口,获取在UIAbility里创建的GetShared,实现跨页面、UIAbility内共享。

  • 组件树的根节点,即被@Entry装饰的@Component,可以被分配一个LocalStorage实例,此组件的所有子组件实例将自动获得对该LocalStorage实例的访问权限;

  • 被@Component装饰的组件最多可以访问一个LocalStorage实例和AppStorage,未被@Entry装饰的组件不可被独立分配LocalStorage实例,只能接受父组件通过@Entry传递来的LocalStorage实例。一个LocalStorage实例在组件树上可以被分配给多个组件。

  • LocalStorage中的所有属性都是可变的。

应用程序决定LocalStorage对象的生命周期。当应用释放最后一个指向LocalStorage的引用时,比如销毁最后一个自定义组件,LocalStorage将被JS Engine垃圾回收。

LocalStorage根据与@Component装饰的组件的同步类型不同,提供了两个装饰器:

  • @LocalStorageProp:@LocalStorageProp装饰的变量和与LocalStorage中给定属性建立单向同步关系。

  • @LocalStorageLink:@LocalStorageLink装饰的变量和在@Component中创建与LocalStorage中给定属性建立双向同步关系。

1.1 限制条件

  • LocalStorage创建后,命名属性的类型不可更改。后续调用Set时必须使用相同类型的值。
  • LocalStorage是页面级存储,GetShared接口仅能获取当前Stage通过windowStage.loadContent传入的LocalStorage实例,否则返回undefined。

1.2 @LocalStorageProp

如果要建立LocalStorage和自定义组件的联系,需要使用@LocalStorageProp和@LocalStorageLink装饰器。使用@LocalStorageProp(key)/@LocalStorageLink(key)装饰组件内的变量,key标识了LocalStorage的属性。

当自定义组件初始化的时候,@LocalStorageProp(key)/@LocalStorageLink(key)装饰的变量会通过给定的key,绑定LocalStorage对应的属性,完成初始化。本地初始化是必要的,因为无法保证LocalStorage一定存在给定的key(这取决于应用逻辑是否在组件初始化之前在LocalStorage实例中存入对应的属性)。

@LocalStorageProp(key)是和LocalStorage中key对应的属性建立单向数据同步,我们允许本地改变的发生,但是对于@LocalStorageProp,本地的修改永远不会同步回LocalStorage中,相反,如果LocalStorage给定key的属性发生改变,改变会被同步给@LocalStorageProp,并覆盖掉本地的修改。

1.2.1 装饰器使用规则说明

在这里插入图片描述

1.2.2 变量的传递/访问规则说明

在这里插入图片描述
在这里插入图片描述

1.2.3 观察变化和行为表现

观察变化

  • 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。

  • 当装饰的数据类型为class或者Object时,可以观察到赋值和属性赋值的变化,即Object.keys(observedObject)返回的所有属性。

  • 当装饰的对象是array时,可以观察到数组添加、删除、更新数组单元的变化。

框架行为

  • 当@LocalStorageProp(key)装饰的数值改变被观察到时,修改不会被同步回LocalStorage对应属性键值key的属性中。

  • 当前@LocalStorageProp(key)单向绑定的数据会被修改,即仅限于当前组件的私有成员变量改变,其他的绑定该key的数据不会同步改变。

  • 当@LocalStorageProp(key)装饰的数据本身是状态变量,它的改变虽然不会同步回LocalStorage中,但是会引起所属的自定义组件的重新渲染。

  • 当LocalStorage中key对应的属性发生改变时,会同步给所有@LocalStorageProp(key)装饰的数据,@LocalStorageProp(key)本地的修改将被覆盖。

1.3 @LocalStorageLink

如果我们需要将自定义组件的状态变量的更新同步回LocalStorage,就需要用到@LocalStorageLink。其可以实现双向的同步. 其他使用与@LocalStorageProp保持一致.

1.4 使用场景

1.4.1 应用逻辑使用LocalStorage

let storage = new LocalStorage({ 'PropA': 47 }); // 创建新实例并使用给定对象初始化
let propA = storage.get('PropA') // propA == 47
let link1 = storage.link('PropA'); // link1.get() == 47
let link2 = storage.link('PropA'); // link2.get() == 47
let prop = storage.prop('PropA'); // prop.get() = 47
link1.set(48); // two-way sync: link1.get() == link2.get() == prop.get() == 48
prop.set(1); // one-way sync: prop.get()=1; but link1.get() == link2.get() == 48
link1.set(49); // two-way sync: link1.get() == link2.get() == prop.get() == 49

1.4.2 从UI内部使用LocalStorage

除了应用程序逻辑使用LocalStorage,还可以借助LocalStorage相关的两个装饰器@LocalStorageProp和@LocalStorageLink,在UI组件内部获取到LocalStorage实例中存储的状态变量。

本示例以@LocalStorage为例,展示了:

  • 使用构造函数创建LocalStorage实例storage;

  • 使用@Entry装饰器将storage添加到CompA顶层组件中;

  • @LocalStorageLink绑定LocalStorage对给定的属性,建立双向数据同步。

// 创建新实例并使用给定对象初始化
let storage = new LocalStorage({ 'PropA': 47 });

@Component
struct Child {
  // @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定
  @LocalStorageLink('PropA') storLink2: number = 1;

  build() {
    Button(`Child from LocalStorage ${this.storLink2}`)
      // 更改将同步至LocalStorage中的'PropA'以及Parent.storLink1
      .onClick(() => this.storLink2 += 1)
  }
}
// 使LocalStorage可从@Component组件访问
@Entry(storage)
@Component
struct CompA {
  // @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定
  @LocalStorageLink('PropA') storLink1: number = 1;

  build() {
    Column({ space: 15 }) {
      Button(`Parent from LocalStorage ${this.storLink1}`) // initial value from LocalStorage will be 47, because 'PropA' initialized already
        .onClick(() => this.storLink1 += 1)
      // @Component子组件自动获得对CompA LocalStorage实例的访问权限。
      Child()
    }
  }
}

1.4.3 @LocalStorageProp和LocalStorage单向同步的简单场景

在下面的示例中,CompA 组件和Child组件分别在本地创建了与storage的’PropA’对应属性的单向同步的数据,我们可以看到:

  • CompA中对this.storProp1的修改,只会在CompA中生效,并没有同步回storage;

  • Child组件中,Text绑定的storProp2 依旧显示47。

// 创建新实例并使用给定对象初始化
let storage = new LocalStorage({ 'PropA': 47 });
// 使LocalStorage可从@Component组件访问
@Entry(storage)
@Component
struct CompA {
  // @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定
  @LocalStorageProp('PropA') storProp1: number = 1;

  build() {
    Column({ space: 15 }) {
      // 点击后从47开始加1,只改变当前组件显示的storProp1,不会同步到LocalStorage中
      Button(`Parent from LocalStorage ${this.storProp1}`)
        .onClick(() => this.storProp1 += 1)
      Child()
    }
  }
}

@Component
struct Child {
  // @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定
  @LocalStorageProp('PropA') storProp2: number = 2;

  build() {
    Column({ space: 15 }) {
      // 当CompA改变时,当前storProp2不会改变,显示47
      Text(`Parent from LocalStorage ${this.storProp2}`)
    }
  }
}

1.4.4 @LocalStorageLink和LocalStorage双向同步的简单场景

下面的示例展示了@LocalStorageLink装饰的数据和LocalStorage双向同步的场景

// 构造LocalStorage实例
let storage = new LocalStorage({ 'PropA': 47 });
// 调用link9+接口构造'PropA'的双向同步数据,linkToPropA 是全局变量
let linkToPropA = storage.link('PropA');

@Entry(storage)
@Component
struct CompA {

  // @LocalStorageLink('PropA')在CompA自定义组件中创建'PropA'的双向同步数据,初始值为47,因为在构造LocalStorage已经给“PropA”设置47
  @LocalStorageLink('PropA') storLink: number = 1;

  build() {
    Column() {
      Text(`incr @LocalStorageLink variable`)
        // 点击“incr @LocalStorageLink variable”,this.storLink加1,改变同步回storage,全局变量linkToPropA也会同步改变 

        .onClick(() => this.storLink += 1)

      // 并不建议在组件内使用全局变量linkToPropA.get(),因为可能会有生命周期不同引起的错误。
      Text(`@LocalStorageLink: ${this.storLink} - linkToPropA: ${linkToPropA.get()}`)
    }
  }
}

1.4.5 兄弟节点之间同步状态变量

下面的示例展示了通过@LocalStorageLink双向同步兄弟节点之间的状态。

先看Parent自定义组件中发生的变化:

  1. 点击“playCount ${this.playCount} dec by 1”,this.playCount减1,修改同步回LocalStorage中,Child组件中的playCountLink绑定的组件会同步刷新;

  2. 点击“countStorage ${this.playCount} incr by 1”,调用LocalStorage的set接口,更新LocalStorage中“countStorage”对应的属性,Child组件中的playCountLink绑定的组件会同步刷新;

  3. Text组件“playCount in LocalStorage for debug ${storage.get(‘countStorage’)}”没有同步刷新,因为storage.get(‘countStorage’)返回的是常规变量,常规变量的更新并不会引起Text组件的重新渲染。

Child自定义组件中的变化:

  1. playCountLink的刷新会同步回LocalStorage,并且引起兄弟组件和父组件相应的刷新。
let storage = new LocalStorage({ countStorage: 1 });

@Component
struct Child {
  // 子组件实例的名字
  label: string = 'no name';
  // 和LocalStorage中“countStorage”的双向绑定数据
  @LocalStorageLink('countStorage') playCountLink: number = 0;

  build() {
    Row() {
      Text(this.label)
        .width(50).height(60).fontSize(12)
      Text(`playCountLink ${this.playCountLink}: inc by 1`)
        .onClick(() => {
          this.playCountLink += 1;
        })
        .width(200).height(60).fontSize(12)
    }.width(300).height(60)
  }
}

@Entry(storage)
@Component
struct Parent {
  @LocalStorageLink('countStorage') playCount: number = 0;

  build() {
    Column() {
      Row() {
        Text('Parent')
          .width(50).height(60).fontSize(12)
        Text(`playCount ${this.playCount} dec by 1`)
          .onClick(() => {
            this.playCount -= 1;
          })
          .width(250).height(60).fontSize(12)
      }.width(300).height(60)

      Row() {
        Text('LocalStorage')
          .width(50).height(60).fontSize(12)
        Text(`countStorage ${this.playCount} incr by 1`)
          .onClick(() => {
            storage.set<number>('countStorage', 1 + storage.get<number>('countStorage'));
          })
          .width(250).height(60).fontSize(12)
      }.width(300).height(60)

      Child({ label: 'ChildA' })
      Child({ label: 'ChildB' })

      Text(`playCount in LocalStorage for debug ${storage.get<number>('countStorage')}`)
        .width(300).height(60).fontSize(12)
    }
  }
}

1.4.6 将LocalStorage实例从UIAbility共享到一个或多个视图

上面的实例中,LocalStorage的实例仅仅在一个@Entry装饰的组件和其所属的子组件(一个页面)中共享,如果希望其在多个视图中共享,可以在所属UIAbility中创建LocalStorage实例,并调用windowStage.loadContent。

// EntryAbility.ts
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';

export default class EntryAbility extends UIAbility {
  storage: LocalStorage = new LocalStorage({
    'PropA': 47
  });

  onWindowStageCreate(windowStage: window.WindowStage) {
    windowStage.loadContent('pages/Index', this.storage);
  }
}

在UI页面通过GetShared接口获取在通过loadContent共享的LocalStorage实例。

// 通过GetShared接口获取stage共享的LocalStorage实例
let storage = LocalStorage.GetShared()

@Entry(storage)
@Component
struct CompA {
  // can access LocalStorage instance using 
  // @LocalStorageLink/Prop decorated variables
  @LocalStorageLink('PropA') varA: number = 1;

  build() {
    Column() {
      Text(`${this.varA}`).fontSize(50)
    }
  }
}

对于开发者更建议使用这个方式来构建LocalStorage的实例,并且在创建LocalStorage实例的时候就写入默认值,因为默认值可以作为运行异常的备份,也可以用作页面的单元测试。

2. AppStorage:应用全局的UI状态存储

AppStorage是应用全局的UI状态存储,是和应用的进程绑定的,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储。

和AppStorage不同的是,LocalStorage是页面级的,通常应用于页面内的数据共享。而AppStorage是应用级的全局状态共享,还相当于整个应用的“中枢”,持久化数据PersistentStorage和环境变量Environment都是通过和AppStorage中转,才可以和UI交互。

本文介绍AppStorage使用场景和相关的装饰器:@StorageProp和@StorageLink。

AppStorage是在应用启动的时候会被创建的单例。它的目的是为了提供应用状态数据的中心存储,这些状态数据在应用级别都是可访问的。AppStorage将在应用运行过程保留其属性。属性通过唯一的键字符串值访问。

AppStorage可以和UI组件同步,且可以在应用业务逻辑中被访问。

AppStorage中的属性可以被双向同步,数据可以是存在于本地或远程设备上,并具有不同的功能,比如数据持久化(详见PersistentStorage)。这些数据是通过业务逻辑中实现,与UI解耦,如果希望这些数据在UI中使用,需要用到@StorageProp和@StorageLink。

2.1 @StorageProp

要建立AppStorage和自定义组件的联系,需要使用@StorageProp和@StorageLink装饰器。使用@StorageProp(key)/@StorageLink(key)装饰组件内的变量,key标识了AppStorage的属性。

当自定义组件初始化的时候,@StorageProp(key)/@StorageLink(key)装饰的变量会通过给定的key,绑定在AppStorage对应的属性,完成初始化。本地初始化是必要的,因为无法保证AppStorage一定存在给定的key,这取决于应用逻辑,是否在组件初始化之前在AppStorage实例中存入对应的属性。

@StorageProp(key)是和AppStorage中key对应的属性建立单向数据同步,我们允许本地改变的发生,但是对于@StorageProp,本地的修改永远不会同步回AppStorage中,相反,如果AppStorage给定key的属性发生改变,改变会被同步给@StorageProp,并覆盖掉本地的修改。

类似LocalStorageProp,只是范围覆盖了整个应用.

2.2 @StorageLink

@StorageLink(key)是和AppStorage中key对应的属性建立双向数据同步:

  1. 本地修改发生,该修改会被写回AppStorage中;

  2. AppStorage中的修改发生后,该修改会被同步到所有绑定AppStorage对应key的属性上,包括单向(@StorageProp和通过Prop创建的单向绑定变量)、双向(@StorageLink和通过Link创建的双向绑定变量)变量和其他实例(比如PersistentStorage)。

类似LocalStorageLink,只是范围覆盖了整个应用.

2.3 使用场景

2.3.1 从应用逻辑使用AppStorage和LocalStorage

AppStorage是单例,它的所有API都是静态的,使用方法类似于LocalStorage对应的非静态方法。

AppStorage.SetOrCreate('PropA', 47);

let storage: LocalStorage = new LocalStorage({ 'PropA': 17 });
let propA: number = AppStorage.Get('PropA') // propA in AppStorage == 47, propA in LocalStorage == 17
var link1: SubscribedAbstractProperty<number> = AppStorage.Link('PropA'); // link1.get() == 47
var link2: SubscribedAbstractProperty<number> = AppStorage.Link('PropA'); // link2.get() == 47
var prop: SubscribedAbstractProperty<number> = AppStorage.Prop('PropA'); // prop.get() == 47

link1.set(48); // two-way sync: link1.get() == link2.get() == prop.get() == 48
prop.set(1); // one-way sync: prop.get() == 1; but link1.get() == link2.get() == 48
link1.set(49); // two-way sync: link1.get() == link2.get() == prop.get() == 49

storage.get('PropA') // == 17 
storage.set('PropA', 101);
storage.get('PropA') // == 101

AppStorage.Get('PropA') // == 49
link1.get() // == 49
link2.get() // == 49
prop.get() // == 49

2.3.2 从UI内部使用AppStorage和LocalStorage

@StorageLink变量装饰器与AppStorage配合使用,正如@LocalStorageLink与LocalStorage配合使用一样。此装饰器使用AppStorage中的属性创建双向数据同步。

AppStorage.SetOrCreate('PropA', 47);
let storage = new LocalStorage({ 'PropA': 48 });

@Entry(storage)
@Component
struct CompA {
  @StorageLink('PropA') storLink: number = 1;
  @LocalStorageLink('PropA') localStorLink: number = 1;

  build() {
    Column({ space: 20 }) {
      Text(`From AppStorage ${this.storLink}`)
        .onClick(() => this.storLink += 1)

      Text(`From LocalStorage ${this.localStorLink}`)
        .onClick(() => this.localStorLink += 1)
    }
  }
}

2.3.3 以持久化方式订阅某个事件并接收事件回调

推荐使用持久化方式订阅某个事件并接收事件回调,可以减少开销,增强代码的可读性。

// xxx.ets
import emitter from '@ohos.events.emitter';

let NextID: number = 0;

class ViewData {
  title: string;
  uri: Resource;
  color: Color = Color.Black;
  id: number;

  constructor(title: string, uri: Resource) {
    this.title = title;
    this.uri = uri
    this.id = NextID++;
  }
}

@Entry
@Component
struct Gallery2 {
  dataList: Array<ViewData> = [new ViewData('flower', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon'))]
  scroller: Scroller = new Scroller()
  private preIndex: number = -1

  build() {
    Column() {
      Grid(this.scroller) {
        ForEach(this.dataList, (item: ViewData) => {
          GridItem() {
            TapImage({
              uri: item.uri,
              index: item.id
            })
          }.aspectRatio(1)
          .onClick(() => {
            if (this.preIndex === item.id) {
              return
            }
            var innerEvent = { eventId: item.id }
            // 选中态:黑变红
            var eventData = {
              data: {
                "colorTag": 1
              }
            }
            emitter.emit(innerEvent, eventData)

            if (this.preIndex != -1) {
              console.info(`preIndex: ${this.preIndex}, index: ${item.id}, black`)
              var innerEvent = { eventId: this.preIndex }
              // 取消选中态:红变黑
              var eventData = {
                data: {
                  "colorTag": 0
                }
              }
              emitter.emit(innerEvent, eventData)
            }
            this.preIndex = item.id
          })

        }, (item: ViewData) => JSON.stringify(item))
      }.columnsTemplate('1fr 1fr')
    }

  }
}

@Component
export struct TapImage {
  @State tapColor: Color = Color.Black;
  private index: number;
  private uri: Resource;

  onTapIndexChange(colorTag: emitter.EventData) {
    this.tapColor = colorTag.data.colorTag ? Color.Red : Color.Black
  }

  aboutToAppear() {
    //定义事件ID
    var innerEvent = { eventId: this.index }
    emitter.on(innerEvent, this.onTapIndexChange.bind(this))
  }

  build() {
    Column() {
      Image(this.uri)
        .objectFit(ImageFit.Cover)
        .border({ width: 5, style: BorderStyle.Dotted, color: this.tapColor })
    }
  }
}

以下示例为消息机制方式订阅事件,会导致回调监听的节点数较多,非常耗时,不推荐以此来实现应用代码。

// xxx.ets
class ViewData {
  title: string;
  uri: Resource;
  color: Color = Color.Black;

  constructor(title: string, uri: Resource) {
    this.title = title;
    this.uri = uri
  }
}

@Entry
@Component
struct Gallery2 {
  dataList: Array<ViewData> = [new ViewData('flower', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon')), new ViewData('OMG', $r('app.media.icon'))]
  scroller: Scroller = new Scroller()

  build() {
    Column() {
      Grid(this.scroller) {
        ForEach(this.dataList, (item: ViewData, index?: number) => {
          GridItem() {
            TapImage({
              uri: item.uri,
              index: index
            })
          }.aspectRatio(1)

        }, (item: ViewData, index?: number) => {
          return JSON.stringify(item) + index;
        })
      }.columnsTemplate('1fr 1fr')
    }

  }
}

@Component
export struct TapImage {
  @StorageLink('tapIndex') @Watch('onTapIndexChange') tapIndex: number = -1;
  @State tapColor: Color = Color.Black;
  private index: number;
  private uri: Resource;

  // 判断是否被选中
  onTapIndexChange() {
    if (this.tapIndex >= 0 && this.index === this.tapIndex) {
      console.info(`tapindex: ${this.tapIndex}, index: ${this.index}, red`)
      this.tapColor = Color.Red;
    } else {
      console.info(`tapindex: ${this.tapIndex}, index: ${this.index}, black`)
      this.tapColor = Color.Black;
    }
  }

  build() {
    Column() {
      Image(this.uri)
        .objectFit(ImageFit.Cover)
        .onClick(() => {
          this.tapIndex = this.index;
        })
        .border({ width: 5, style: BorderStyle.Dotted, color: this.tapColor })
    }

  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/325836.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FPGA 高端项目:基于 SGMII 接口的 UDP 协议栈,提供2套工程源码和技术支持

目录 1、前言给读者的一封信免责声明 2、相关方案推荐我这里已有的以太网方案本协议栈的 1G-UDP版本本协议栈的 10G-UDP版本本协议栈的 25G-UDP版本1G 千兆网 TCP-->服务器 方案1G 千兆网 TCP-->客户端 方案10G 万兆网 TCP-->服务器客户端 方案 3、该UDP协议栈性能4、…

JRT核心竞争力

如果说JRT业务脚本化和发部署简单和打印导出客户端都不足以抵挡Spring用的人多的优势的话。那么这一篇让DolerGet给你一个选择JRT的理由&#xff0c;借助JRT自我实现的ORM&#xff0c;JRT有能力完全把控更新数据和删除数据的口径&#xff0c;和能够准确知道哪些是热点数据&…

细说JavaScript内置对象(JavaScript内置对象详解)

一、String对象 1、简单上手 2、构造方法 3、其他方法 3.1、charAt() 3.2、indexOf() 3.3、split() 3.4、substring() 3.5、substr() 4、实际操作 二、Math对象 1、简单上手 2、对象属性 3、对象方法 4、实际操作 三、Date对象 1、简单上手 2、构造方法 3、实…

Java零基础教学文档servlet(2)

【Servlet】 1.工程结构 2.J2EE平台 Java EE&#xff0c;Java平台企业版&#xff08;Java Platform Enterprise Edition&#xff09;&#xff0c;之前称为Java 2 Platform, Enterprise Edition (J2EE)&#xff0c;2018年3月更名为Jakarta EE。是Sun公司为企业级应用推出的标准…

Java实现高校大学生创业管理系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统公告模块2.2 创业项目模块2.3 创业社团模块2.4 政府政策模块2.5 创业比赛模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 系统公告表3.2.2 创业项目表3.2.3 创业社团表3.2.4 政策表 四、系统展示五、核心代码5.…

GAMES104-现代游戏引擎:从入门到实践 - 物理引擎课程笔记汇总

文章目录 0 入门资料1 物理引擎基本概念Actor & shapesRigid body dynamicsCollision DetectionCollision Resolution 应用与实践Character controllerRagdoll 0 入门资料 GAMES104-现代游戏引擎&#xff1a;从入门到实践_课程视频_bilibiliGAMES104官方账号 - 知乎课程主页…

时刻不忘为何而出发

&#x1d649;&#x1d65e;&#x1d658;&#x1d65a;!!&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦ &#x1f44f;&#x1f3fb;‧✧̣̥̇:Solitary-walk ⸝⋆ ━━━┓ - 个性标签 - &#xff1a;来于“云”的“羽球人”。…

Linux内核架构和工作原理详解(一)

简介 作用是将应用层序的请求传递给硬件&#xff0c;并充当底层驱动程序&#xff0c;对系统中的各种设备和组件进行寻址。目前支持模块的动态装卸(裁剪)。Linux内核就是基于这个策略实现的。Linux进程1.采用层次结构&#xff0c;每个进程都依赖于一个父进程。内核启动init程序…

Bug:Goland左侧丢失项目结构(Goland常用快捷键)

Goland快捷键&小tips 1 常用快捷键 # 格式化代码 optioncommandL# 在项目中搜索文件中的内容 commandshiftF# 搜索.go文件 shiftshift&#xff08;按两次shift&#xff09;# 修改方法、变量&#xff08;同时替换引用处的名称&#xff09; fnshiftF6# 将选中代码抽取为方法…

QQ视频聊天怎么录制

虽然微信几乎成为主流的聊天工具&#xff0c;但是不可否认的是QQ视频聊天仍有他的趣味所在&#xff0c;多种特效在视频的时候增加乐趣&#xff0c;那么如果QQ视频聊天的时候可以录制聊天内容吗&#xff1f;当然是可以的。可以使用电脑自带的视频录制工具&#xff0c;或者QQ自带…

❤ React报错问题分析

❤ React报错问题分析 ❤️ You passed a second argument to root.render(…) but it only accepts one argument. You passed a second argument to root.render(…) but it only accepts one argument. react-dom.development.js:86 Warning: You passed a second argumen…

EOCR电动机保护器故障原因查询

在电动机保护方面&#xff0c;电子式电机保护器的应用越来越广泛。电子式电动机保护器可靠性强、灵敏度高&#xff0c;并具有多种保护功能&#xff0c;如&#xff1a;过电流、欠电流、缺相、逆相、堵转、三相不平衡、接地、短路等。 但有一部分电子式电动机保护器产品在检测到…

自动化神器 Playwright 的 Web 自动化测试解决方案

1. 主流框架的认识 总结&#xff1a; 由于Selenium在3.x和4.x两个版本的迭代中并没有发生多大的变化&#xff0c;因此Selenium一统天下的地位可能因新框架的出现而变得不那么稳固。后续的Cypress、TestCafe、Puppeteer被誉为后Selenium时代Web UI自动化的三驾马车。但是由于这三…

Redcap UE camping相关规定

在接入网络时&#xff0c;可以通过系统信息的配置分别允许具有 1 个 Rx branch和 2 个 Rx branches的 RedCap UE接入。 此外&#xff0c;半双工 FDD RedCap UE也可以通过系统信息控制其接入。网络端会在在 SIB1 中提供 RedCap 特定的 Intra Frequency Reselection Indication&a…

CVE-2023-50290 Apache Solr 敏感信息泄露

项目介绍 Apache Solr 是流行的、速度极快的开源搜索平台&#xff0c;可满足您的所有企业、电子商务和分析需求&#xff0c;基于Apache Lucene构建。 项目地址 https://solr.apache.org 漏洞概述 Apache Solr 中未经授权的参与者漏洞暴露敏感信息。 Solr Metrics API 发布…

音频文件格式–celt

CELT介绍 CELT 是一种开放、免版税的有损音频压缩格式&#xff0c;也是一种免费软件编解码器&#xff0c;具有特别低的算法延迟&#xff0c;适用于低延迟音频通信。这些算法是公开记录的&#xff0c;并且可以不受软件专利限制地使用。 CELT 是一种基于改进的离散余弦变换 (MD…

vue中使用高德地图渲染多个不同类型的点,根据勾选数据 类型不同打点显示隐藏

一、在index.html文件中引入高德地图JavaScript API的2.0版本SDK <script src"https://webapi.amap.com/maps?v2.0&key你的高德地图Key"></script>二、创建一个Vue组件&#xff0c;用于渲染地图和点位 html <template><div class"m…

科研绘图(八)线性热图

线性热图&#xff08;Linear Heat Map&#xff09;是一种数据可视化技术&#xff0c;用于展示数值在一维线性空间上的分布情况。它通常用于展示沿着一条线&#xff08;例如时间线或任何一维序列&#xff09;的数据密度或强度变化。线性热图与传统的二维热图不同&#xff0c;后者…

Python-面向对象

面向对象 1.初识对象1.1理解使用对象完成数据组织的思路 2.成员方法2.1类的定义和使用语法2.2成员方法的使用 3.类和对象4.构造方法4.1使用构造方法向成员变量赋值 5.其他内置方法5.1__str__字符串方法5.2__lt__小于符号比较方法5.3__le__小于等于比较符号5.4__eq__比较运算符实…

快快销ShopMatrix 分销商城多端uniapp可编译5端 - 佣金倍数提现

本文来自应用中心-9999款应用在线选购 "佣金倍数提现"这个概念在不同的上下文中可能有不同的含义&#xff0c;但通常它涉及到基于用户赚取的佣金来设定提现条件。这是一种常见的机制&#xff0c;尤其是在那些提供佣金或回扣的平台上&#xff0c;如联盟营销、金融交易…
最新文章