鸿蒙开发基础入门

一、熟悉目录结构

请添加图片描述
在这里插入图片描述

二、ArkTS语法介绍

  • ArkTS是为构建高性能应用设计的编程语言,语法继承TypeScript,并进行了优化,拥有更强的类型约束
  • ArkTS提供了声明式UI范式,符合移动开发的最新趋势
    在这里插入图片描述
  • ArkTS摒弃了部分影响运行时的性能的语法,比如Any。取而代之的是显式类型定义或类型推断
  • ArkTS提供了更强的并发编程能力
  • ArkTS兼容JS/TS三方库

强的类型约束

  • ArkTS要求所有类型在程序运行前都是已知的。避免运行时类型检查。
// ArkTS
const peter: Person = { // 明确类型id: "001",  name: "Peter",  age: 10, gender: "male"
}

ArkTS在运行时不允许更改对象布局。

// TypeScript
class Person {id: string = ""name: string = ""
}let peter = new Person()
(peter as any).desc = "XX"
delete (peter as any).desc
// ArkTS
class Person {id: string = ""name: string = ""desc?: string
}let peter = new Person()
peter.desc = "XX" // 可选属性赋值
peter.desc = undefined // 置空可选属性

声明式UI

  • UI描述:装饰器、自定义组件和UI描述机制
  • 状态管理:数据驱动UI,数据可在组件、页面、应用及跨设备传递

在这里插入图片描述

变量

// 声明变量
let count: number = 0
count = 40// 声明常量
const MAX_COUNT: number = 100
MAX_COUNT = 200 // 报错,不能修改const声明的值

类型

基本类型:string,number,boolean,enum

引用类型:Array,自定义类

联合类型:Union

类型别名:Aliases

基本类型 string,number,boolean,enum
let name: string = "ZhangSan"
let age: number = 20
let isMale: boolean = trueenum Color {Red,Blue,Green
}let bgColor: Color = Color.Red
引用类型:Array,自定义类
let students: Array<string> = ["ZhangSan", "LiSi", "WangWu"]
let students: string[] = ["ZhangSan", "LiSi", "WangWu"]class Person {}
let person: Person = new Person()
联合类型:Union 允许变量的值为多个类型
let id: number | string = 1
id = "001"
类型别名:Aliases 允许给一个类型取别名,方便理解和复用
type Matrix = number[ ][ ]
type NullableObject = Object | null

空安全

基于联合类型,可以声明可空变量。

let name: string | null = null
console.log(name.length.toString()) // 报错,变量可能为null,无法获取null的length

但以上声明使用时不加判断,会报错,因为编译器认为name有可能为null。null无法调用length属性。

if (name != null) {} // 1. 用if/else判断
const unwrapped = name ?? "" // 2. 空值合并表达式,??左边为空时返回??右边的值
let len = name?.length // 3. ?可选链,如果name为null,运算符返回undefined

有多种方法使用可空变量,if/else判断、空值合并表达式或者可选链。

类型判断与类型推断

ArkTS是类型安全的语言,编译器会进行类型检查

let name: string = "ZhangSan"
name = 20 // 报错,number不能赋值给string类型变量

以上代码中,name已经为string类型,不能赋值number类型。

ArkTS可以省略类型声明,此时会自动推导类型

let age = 20 // age自动推断为number类型

age没有声明类型,编译器自动推导为number型。

语句

条件语句及条件表达式

条件语句:根据条件真值(true或false)执行不同代码块

let score: number = 90
let passed: boolean = false
if (score >= 60) {passed = true
} else {passed = false
}

条件表达式:条件 ? 为真返回值 : 为假返回值

let score: number = 90
let passed: boolean = score >= 60 ? true : false
循环语句

用于重复执行的语句。有for循环、for…of循环以及while循环

给定一个数组

let students: string[] = ["ZhangSan", "LiSi", "WangWu"]

for循环

for (let i = 0; i < students.length; i++) {console.log(students[i])
}

for…of循环

for (let student of students) {console.log(student)
}

while循环

let index = 0
while (index < students.length) {console.log(students[index])index++
}

函数的声明和使用

函数是多条语句的组合,组成一个可重用的代码块。

用function声明函数

格式如下

function 函数名(参数1: 参数类型1, 参数2: 参数类型2): 返回类型 {
// 函数体
}

例:

function printStudentInfo(students: string[]): void {for (let student of students) {console.log(student)}
}printStudentInfo(["ZhangSan", "LiSi", "WangWu"])

箭头函数/lambda

简化声明,匿名函数。通常用于把函数作为参数传递。格式如下

(参数1: 参数类型1, 参数2: 参数类型2): 返回类型 => { // 函数体 }

例:

(name: string): void => { console.log(name) }

上例中,返回类型void可以省略。如果函数体只有一行、整个函数声明只有一行,可以省略花括号。

闭包函数

一个函数可以作为另一个函数的返回值。

type returnType = () => string
function outerFunc(): () => string {let count = 0return (): string => {count++return count.toString()}
}let invoker = outerFunc()
console.log(invoker()) // 输出1
console.log(invoker()) // 输出2

类的声明和使用

ArkTS支持面向对象编程。可声明对象属性和方法。

class Person {name: string = "ZhangSan"age: number = 20isMale: boolean = true
}

创建类的实例

const person = new Person()
console.log(person.name)const person: Person = {name: "ZhangSan",age: 29,isMale: true
}
console.log(person.name)

构造器:在创建类实例的时候可以执行一些代码或传参。

class Person {name: string = "ZhangSan"age: number = 20isMale: boolean = trueconstructor(name: string,age: number,isMale: boolean) {this.name = namethis.age = agethis.isMale = isMale}
}const person = new Person("ZhangSan", 20, false)
console.log(person.name)

上例中在Person声明时定义了一个constructor构造器,创建实例的时候传入相应参数,会执行构造器里的语句。

实例方法:实例调用的方法

class Person {name: string = "ZhangSan"age: number = 20isMale: boolean = trueconstructor(...){...}printInfo() {if (this.isMale) {console.log(`${this.name} is a boy, he is ${this.age} years old`)} else {console.log(`${this.name} is a girl, she is ${this.age} years old`)}}
}const person = new Person("LiSi", 18, false)
person.printInfo()

上例中声明了一个实例方法printInfo()。在实例person中被调用。

可见性及属性

类成员有 public、private、protected可见性。
public修饰符表示这是公开成员,外部可以自由访问。
private修饰符表示私有成员,只能用在当前类的内部,类的实例和子类都不能使用该成员。
protected修饰符表示该成员是保护成员,只能在类的内部使用该成员,实例无法使用该成员,但是子类内部可以使用。

class Person {public name: string = "ZhangSan"private _age: number = 20isMale: boolean = trueconstructor(...) {...}printInfo() {...}get age(): number {return this._age}set age(age: number) {this._age = age}
}const person: Person = new Person("LiSi", 18, true)
console.log(person._age.toString()) // 无法访问
console.log(person.age.toString())

另外,对于上例中private _age可以声明setter和getter,在调用的时候可以与访问属性的语法一样来访问。

继承

子类继承父类的特征和行为。关键字extends。

class Employee extends Person {department: stringconstructor(name: string,age: number,isMale: boolean,department: string) {super(name, age, isMale)this.department = department}
}const employee: Employee = new Employee("LiSi", 18, false, "XCompany")
employee.printInfo()

上例中的Employee继承了Person。增加了department属性。构造函数重写了父类的构造函数。使用super()方法可以调用父类的方法实现。

多态

子类继承父类,可以重写父类方法

class Employee extends Person {department: stringconstructor(...) {...}printInfo(): void {super.printInfo()console.log(`working in ${this.department}}`)}
}const person: Person = new Person("LiSi", 18, false)
person.printInfo()const employee: Employee = new Employee("LiSi", 18, false, "XCompany")
employee.printInfo()

Employee构造函数重写了父类的构造函数。使用super()方法可以调用父类的方法实现。在Employee实例化的时候调用重写的构造函数。另外printInfo()也重写了父类的对应方法,并用super()调用了父类的方法。在调用Employee的printInfo()方法时会先调用父类的方法,再执行之后的代码。

模块导出与导入

ArkTS中文件内的作用域是独立的。如果想要在B文件中引用A文件定义的变量、函数、类等,需要使用export关键字。

// Person.ets
export class Person {...}
// Index.ets
import { Person } from './Person'const person = new Person("LiSi", 18, false)
person.printInfo()

声明式UI

声明式UI是当前移动开发的UI语法新趋势。它相较于传统的命令式UI,代码结构更直观。拥有声明式布局描述及状态驱动视图更新的特点。

传统的命令式UI代码,我们拿安卓举例,像这样

TextView title = findViewById(R.id.tv_title);
title.setText("HarmonyOS Developer World") ;
Button button = findViewById(R.id.btn_join);
Button.setText("Join Now");LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(300,300);
child.setLayoutParams(layoutParams);
linear_layout.addView(title, Integer.valueOf(add_index) - 1);
linear_layout.addView(button, Integer.valueOf(add_index) - 1);

而如果使用声明式UI,可以写成这样

Column() { Text("HarmonyOS Developer World") Button("Join Now") 
}

本章将以一个待办事项界面为例讲解ArkTS声明式UI。

image-20240418222343655

声明式描述

布局描述

下例中描述了一个横向布局,里面依次排列了一张图片和一个文本框,对应的UI组件如图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

属性设置

使用点语法设置组件属性

Text("待办").fontSize(28).fontWeight(FontWeight.Bold)

渲染控制

支持if、else条件渲染。

Column() {Text(`count=${this.count}`)if (this.count > 0) {Text(`count is positive`).fontColor(Color.Green)}
}

支持ForEach循环

Column() {ForEach(this.simpleList, (item: string) => {Text(item)}, (item: string) => item)
}

支持LazyForEach循环

private data: MyDataSource = new MyDataSource()List({ space: 3 }) {LazyForEach(this.data, (item: string) => {ListItem() {Row() {Text(item).fontSize(50)}}}, (item: string) => item)
}.cachedCount(5)class MyDataSource extends BasicDataSource {private dataArray: string[] = [];public totalCount(): number {return this.dataArray.length;}public getData(index: number): string {return this.dataArray[index];}public addData(index: number, data: string): void {this.dataArray.splice(index, 0, data);this.notifyDataAdd(index);}public pushData(data: string): void {this.dataArray.push(data);this.notifyDataAdd(this.dataArray.length - 1);}
}// Basic implementation of IDataSource to handle data listener
class BasicDataSource implements IDataSource {private listeners: DataChangeListener[] = [];private originDataArray: string[] = [];public totalCount(): number {return 0;}public getData(index: number): string {return this.originDataArray[index];}// 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {console.info('add listener');this.listeners.push(listener);}}// 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener);if (pos >= 0) {console.info('remove listener');this.listeners.splice(pos, 1);}}// 通知LazyForEach组件需要重载所有子组件notifyDataReload(): void {this.listeners.forEach(listener => {listener.onDataReloaded();})}// 通知LazyForEach组件需要在index对应索引处添加子组件notifyDataAdd(index: number): void {this.listeners.forEach(listener => {listener.onDataAdd(index);})}// 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件notifyDataChange(index: number): void {this.listeners.forEach(listener => {listener.onDataChange(index);})}// 通知LazyForEach组件需要在index对应索引处删除该子组件notifyDataDelete(index: number): void {this.listeners.forEach(listener => {listener.onDataDelete(index);})}// 通知LazyForEach组件将from索引和to索引处的子组件进行交换notifyDataMove(from: number, to: number): void {this.listeners.forEach(listener => {listener.onDataMove(from, to);})}
}

状态驱动UI更新

使用@State @Prop @Link等驱动UI更新。

下例说明了一个状态属性是如何驱动UI更新的,isComplete选择不同的图片资源。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

下图说明了isComplete控制了文字的样式

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

以下是其他状态装饰器

•@State:@State装饰的变量拥有其所属组件的状态,可以作为其子组件单向和双向同步的数据源。当其数值改变时,会引起相关组件的渲染刷新。

•@Prop:@Prop装饰的变量可以和父组件建立单向同步关系,@Prop装饰的变量是可变的,但修改不会同步回父组件。

•@Link:@Link装饰的变量和父组件构建双向同步关系的状态变量,父组件会接受来自@Link装饰的变量的修改的同步,父组件的更新也会同步给@Link装饰的变量。

•@Provide/@Consume:@Provide/@Consume装饰的变量用于跨组件层级(多层组件)同步状态变量,可以不需要通过参数命名机制传递,通过alias(别名)或者属性名绑定。

•@Observed:@Observed装饰class,需要观察多层嵌套场景的class需要被@Observed装饰。单独使用@Observed没有任何作用,需要和@ObjectLink、@Prop连用。

•@ObjectLink:@ObjectLink装饰的变量接收@Observed装饰的class的实例,应用于观察多层嵌套场景,和父组件的数据源构建双向同步。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

自定义组件

当界面中有相同结构的模块时,可以封装成一个独立组件。

比如每一行代办事项都是由一张图片和一个文本组成。

image-20240418222343655

自定义组件用@Component装饰器来声明。ToDoItem表示每一行代办事项。

@Component
export default struct ToDoItem {}

在首页如图所示来使用。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

子组件的数据

复用的子组件,需要传入数据来显示不同内容,本例中通过content属性给ToDoItem传递数据。

@Component
export default struct ToDoItem {private content?: stringbuild() {Row() {Image($r('app.media.ic_default'))Text(this.content)}}
}

在首页中参考如下代码传递数据

@Entry
@Component
struct ToDoListPage {build() {Column({ space: 16 }) {Text(“待办”)ToDoItem({ content: "学习" })}}
}

组件的生命周期

每个组件,包括页面组件和子组件,都有相应的生命周期。生命周期如下图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

自定义组件和页面的关系:

•自定义组件:@Component装饰的UI单元,可以组合多个系统组件实现UI的复用,可以调用组件的生命周期。

•页面:即应用的UI页面。可以由一个或者多个自定义组件组成,@Entry装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry。只有被@Entry装饰的组件才可以调用页面的生命周期。

页面生命周期,即被@Entry装饰的组件生命周期,提供以下生命周期接口:

•onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。

•onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。

•onBackPress:当用户点击返回按钮时触发。

组件生命周期,即一般用@Component装饰的自定义组件的生命周期,提供以下生命周期接口:

•aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。

•aboutToDisappear:aboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。

生命周期流程如下图所示,下图展示的是被@Entry装饰的组件(页面)生命周期。

@Builder装饰器:自定义构建函数

区别与子组件的声明,在当前组件中也可以使用@Builder来装饰一个方法,用于提取一些可复用的UI代码。

在ToDoItem中,对于Image组件的描述可以提取成一个方法,并用@Builder来装饰,然后可以根据条件引用不同图片文件。

@Component
export default struct ToDoItem {private content?: string@BuilderlabelIcon(icon: Resource) {Image(icon).objectFit(ImageFit.Contain).width(28).height(28).margin(20)}build() {Row() {this.labelIcon($r('app.media.ic_default'))Text()}}
}

渲染列表数据

子组件ToDoItem的设计已经完成,现在在首页,我们需要用一个属性提供列表数据。下例中totalTasks保存了一个字符串数组。用ForEach来循环渲染。

@Entry
@Component
struct ToDoListPage {private totalTasks: Array<string> = ["晨练","做早餐","读书","学习","刷剧"]build() {Column({ space: 16 }) {Text("待办")ForEach(this.totalTasks, (item: string) => {ToDoItem({ content: item })},)}}
}

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

大数据机器学习算法和计算机视觉应用01:博弈论基础

Game Theory 2-player Zero Sum GameMinimax Optimal StrategiesVon Neumann’s Minimax TheoremLower Bounds for Randomized AlgorithmsGeneral sum games, Nash quilibria (p.s:该系列是国际交流学术公开课的笔记&#xff0c;主讲人是Carnegie Melon University的终身教授…

如何安装和配置JDK17

教程目录 零、引言1、新特性概览2、性能优化3、安全性增强4、其他改进5、总结 一、下载安装二、环境配置三、测试验证 零、引言 JDK 17&#xff08;Java Development Kit 17&#xff09;是Java平台的一个重要版本&#xff0c;它带来了许多新特性和改进&#xff0c;进一步提升了…

【C++进阶】智能指针的使用及原理(1)

1. 智能指针的使用场景分析 下面程序中我们可以看到&#xff0c;new了以后&#xff0c;我们也delete了&#xff0c;但是因为抛异常导&#xff0c;后面的delete没有得到执行&#xff0c;所以就内存泄漏了&#xff0c;所以我们需要new以后捕获异常&#xff0c;捕获到异常后delete…

计算机课程管理:Spring Boot实现的工程认证路径

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了基于工程教育认证的计算机课程管理平台的开发全过程。通过分析基于工程教育认证的计算机课程管理平台管理的不足&#xff0c;创建了一个计算机管理基于工程教育认…

【人工智能训练师】综合案例 HBase与Hive的集成

9.1 HBase与Hive 任务目的 简单回顾了解hive 了解hive与hbase的区别 任务清单 任务1&#xff1a;hive简介 任务2&#xff1a;hbase与hive的区别 任务步骤 任务1&#xff1a;hive简介   什么是Hive呢&#xff1f; Apache Hive是一个构建在Hadoop基础设施之上的数据仓库。 构…

基于STM32的图像处理监控系统

1. 引言 随着物联网和智能家居的普及&#xff0c;图像处理和监控系统在安全防范、家庭监控等方面应用越来越广泛。本项目旨在使用STM32开发板和OV7670摄像头模块搭建一个简单的图像处理监控系统。系统能够捕获图像并进行基本的处理与展示。 2. 环境准备2.1 硬件需求 - STM32开…

QML-简单项目实战一

一、简介 使用QML创建一个简单的登录界面&#xff0c;代码内容来源于bilibili中的视频。 实现效果图如下&#xff1a; 二、实现步骤 1. 核心控件和布局管理和登录事件处理 import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Window 2.12 /*1. 核心控件和布局…

字节青训-小F的永久代币卡回本计划、

目录 一、小F的永久代币卡回本计划 问题描述 测试样例 解题思路&#xff1a; 问题理解&#xff1a; 数学公式&#xff1a; 代码实现&#xff1a; 最终代码&#xff1a; 运行结果&#xff1a; 二、构造特定数组的逆序拼接 问题描述 测试样例 解题思路&#xff1a;…

[ Linux 命令基础 4 ] Linux 命令详解-文本处理命令

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

06:(寄存器开发)对上电/复位的SystemInit函数进行分析

SystemInit函数分析 通过第5章的时钟树的学习&#xff0c;基本了解了SystemClock总线&#xff0c;AHB总线&#xff0c;APB1总线&#xff0c;APB2总线的时钟频率。那么单片机一上电或者按下复位时&#xff0c;这些总线的时钟频率是如何变化的喃&#xff1f; 这和STM32的启动文件…

C++ : STL容器(适配器)之stack、queue剖析

STL容器适配器之stack、queue剖析 一、stack、queue的接口&#xff08;一&#xff09;stack 接口说明&#xff08;二&#xff09;queue 接口说明 二、stack、queue的模拟实现&#xff08;一&#xff09;stack、queue是容器适配器stack、queue底层默认容器--deque1、deque概念及…

三菱QD77MS定位模块外部信号选择功能

“外部信号选择功能” 是在使用上/下限限位信号和近点狗信号的情况下&#xff0c;从以下信号中选择的功能。 "QD77MS的外部输入信号 "伺服放大器的外部输入信号 "经由 CPU 的外部输入信号(QD77MS 的缓冲存储器) 经由 CPU 的外部输入信号(QD77MS 的缓冲存储器)的…

Vue3-06_路由

路由 后台路由是根据请求url&#xff0c;匹配请求处理的后台模块&#xff08;路径&#xff09; 前台根据访问路径&#xff0c;决定显示的内容。 路由就是&#xff1a; 访问hash 与内容的对应关系 路由的工作方式 用户点击页面的路由链接导致url地址栏中的Hash值发生了变化前…

【知识科普】TCP与UDP这两者之间的对比

TCP与UDP这两者之间的对比 概述TCP 协议的基本概念TCP 协议的工作原理TCP 协议的报文结构TCP 协议的流量控制TCP 协议的可靠性TCP 与 UDP 的比较TCP 协议的应用TCP 协议的优缺点优点缺点 概述 TCP&#xff08;传输控制协议&#xff09;是一种面向连接的、可靠的、基于字节流的…

初次体验Tauri和Sycamore(1)

原创作者&#xff1a;庄晓立&#xff08;LIIGO&#xff09; 原创时间&#xff1a;2024年11月10日 原创链接&#xff1a;https://blog.csdn.net/liigo/article/details/143666827 版权所有&#xff0c;转载请注明出处。 前言 Tauri 2.0发布于2024年10月2日&#xff0c;Sycamore…

基于Spring Boot+Vue的学院食材采供管理系统

一.系统开发工具与环境搭建 1.系统设计开发工具 后端使用Java编程语言的Spring boot框架 项目架构&#xff1a;B/S架构 运行环境&#xff1a;win10/win11、jdk17 前端&#xff1a; 技术&#xff1a;框架Vue.js&#xff1b;UI库&#xff1a;ElementUI&#xff1b; 开发工具&…

【C++】vector模拟实现、迭代器失效问题(超详解)

vector会使用之后我们来模拟实现一下&#xff0c;通过对vector的模拟实现&#xff0c;我们来说一下迭代器失效问题。 1.准备工作 在头文件vector.h里声明和实现函数&#xff0c;然后在test.cpp里测试代码的正确性。 在vector.h中用命名空间分隔一下&#xff0c;因为c库里面也有…

基于SpringBoot的渔具管理系统【附源码】

基于SpringBoot的渔具管理系统 效果如下&#xff1a; 系统主页面 系统登陆页面 管理员主页面 用户管理页面 渔具信息管理页面 租赁信息管理页面 归还信息管理页面 渔具信息页面 用户登陆页面 个人中心页面 研究背景 随着社会的发展&#xff0c;渔具销售企业之间的竞争与合作…

string

文章目录 一. STL1.概念2.版本 二. string类2.1 为什么学习string类2. 标准库中的string类2.2.1 构造&#xff08;7个&#xff09;2.2.2 对string类对象进行访问和修改&#xff08;1&#xff09;operator[]&#xff08;2&#xff09;迭代器1.迭代器的使用2.迭代器的价值&#x…

B2B订货系统功能设计与代码开发(PHP + MySQL)

在B2B&#xff08;Business to Business&#xff09;电子商务中&#xff0c;企业之间的商品订购、交易和供应链管理是核心功能。一个高效的B2B订货系统可以帮助企业管理库存、订单、采购等业务流程。本文将介绍一个基于PHP与MySQL技术栈的B2B订货系统的功能设计与开发流程。 一…