跳到主要内容

duxapp框架是如何实现对鸿蒙的兼容,开发过程中遇到了哪些问题

· 阅读需 13 分钟
duxapp
duxapp框架作者

Taro 4.0 版本已经搭建好了兼容鸿蒙的基础框架,duxapp 是在这个基础上进一步开发,降低了开发者上手门槛,提供了统一的 UI 库。

duxapp 是一个模块化的多端开发框架,其中提供了多端 UI 库,能真正实现一套代码开发小程序、React Native、H5、鸿蒙

兼容鸿蒙之前

在开始兼容鸿蒙之前,我们来讲一下,duxapp 是一个什么状况

1、样式兼容性

现在 duxapp 已经兼容 小程序、React Native以及H5了,因为 React Native 端在这些端中兼容的样式是最少的,我们是按照React Native的标准去做,我们针对 H5 端和 小程序端的基础样式做了调整

需要将 View 组件调整为默认 Flex 竖向布局、默认的定位方式、模型盒、以及边框等,像下面这样:

/* H5 端 */
taro-view-core {
display: flex;
flex-direction: column;
position: relative;
border-style: solid;
border-width: 0;
}
input,
textarea,
taro-view-core {
box-sizing: border-box;
}
/* 小程序端 */
view {
display: flex;
flex-direction: column;
position: relative;
border-style: solid;
border-width: 0;
}
input,
textarea,
view {
box-sizing: border-box;
}

通过这样的调整,我们在项目中使用 View 组件的时候将默认使用 Flex 竖向布局,这样编写的 css 就能做到同时兼容多个端了

2、现有模块

duxapp 框架中现在已提供了很多的公开模块了,这里介绍三个相关的基础模块,这三个模块在我们兼容鸿蒙的过程中都有很大的关联

  • duxapp

这个和框架名称相同的模块,是所有模块的根模块,所有的模块都需要依赖于这个模块

这个模块内提供了一些非常基础的组件、函数、配置等,且一些东西是必须使用的

用来包裹页面的容器组件 TopView
路由跳转函数 route
导出的用户配置

  • duxui

这个是提供的UI组件库,模块内提供了大量兼容多端的组件库,使用这些组件,能快速开发出兼容多端的应用

  • duxappReactNative

React Native端支持,要兼容 React Native,必须使用这个模块,模块内有 React Native 端的三方依赖、用户协议、版本更新、权限管理等

Taro 和鸿蒙的现状

Taro 4.0 目前有两套实现鸿蒙的方案

  • @tarojs/plugin-platform-harmony-hybrid

webview方案,将 H5 运行在 webview 上,和小程序类似

  • @tarojs/plugin-platform-harmony-ets

duxapp 中为了更好的性能和体验,选择的的是这个方案

底层通过 arkts 以及 arkui 实现,将 Taro 的组件转化为对应的 arkui 里面对应的组件,并实现了 React 的运行时

例如 View 组件会通过 arkui 的 FlexRowColumn 这三个组件来实现,会根据编写的不同样式选择不同的原生组件去渲染,从这里也大概能看出,鸿蒙的 arkui 里面也是 Flex 布局

兼容过程

1、创建一个鸿蒙模块

就像上面介绍的 React Native 端那样,鸿蒙端也需要创建一个模块,用来处理鸿蒙端的基础内容,例如用到的三方依赖、配置插件等

这个模块的名称为 duxappHarmony

2、添加需要的三方依赖

目前鸿蒙端仅支持使用 vite 编译,因此鸿蒙端需要添加编译的相关的依赖,在模块内创建 package.json,内容如下

{
"scripts": {
"build:harmony": "duxapp runtime enterFile && duxapp harmony create && taro build --type harmony",
"dev:harmony": "npm run build:harmony -- --watch"
},
"dependencies": {
"@tarojs/plugin-platform-harmony-ets": "4.0.7",
"vite": "^5.4.10",
"terser": "^5.36.0",
"@tarojs/vite-runner": "4.0.5",
"@vitejs/plugin-react": "^4.3.3",
"vite-plugin-commonjs": "^0.10.3"
}
}

这个完整的文件中还包含了编译鸿蒙的两个命令

3、配置依赖项

添加了依赖这些依赖并不会自动起作用,需要配置 Taro 编译配置,我们在这个模块内创建一个 Taro 的配置文件 taro.config.js,内容如下

// eslint-disable-next-line import/no-commonjs
const commonjs = require('vite-plugin-commonjs').default

module.exports = {
// 鸿蒙编译插件
plugins: [
'@tarojs/plugin-platform-harmony-ets'
],
harmony: {
compiler: {
// 指定 vite 编译
type: 'vite',
vitePlugins: [
commonjs()
]
},
projectPath: './dist/harmony'
}
}

这里面配置了平台插件、指定使用 vite 编译、设置了项目目录

这其中还加入了一个 vite 插件,因为 vite 默认不支持 commonjs,但是 duxapp 项目中使用了commonjs,所以加入了这个插件

到此基础环境配置好了,但是我们还需要创建鸿蒙工程文件,Taro 没有提供这个内容

4、在cli内处理鸿蒙工程文件

在第 2 步的命令中,duxapp harmony create 有这个命令,我们就是通过这个命令来创建的鸿蒙工程文件,这样就不需要再通过鸿蒙的 IDE 再去手动操作创建了,这也是一个相对麻烦的过程

这个过程和 React Native 端是类似的,React Native端也是自动创建安卓和 ios 的工程文件,鸿蒙的工程文件创建会放在 dist/harmony 目录中

创建工程文件的过程中,可以通过配置或脚本对工程文件进行处理,例如鸿蒙端需要包名、版本号、app 名称等信息,都是通过配置文件来生成的

这里就不详细展开了,可以查看文档了解 鸿蒙入门文档

5、组件库兼容

上面的步骤处理完之后,框架的东西就处理完了,但是仍然是不可用的,因为要编写一个兼容多端的项目,现有的组件,还需要针对鸿蒙端做相应的兼容,要兼容的组件包括基础模块中的组件和 duxui 模块中的组件

鸿蒙端兼容是一个很费事的过程,对鸿蒙端的兼容性 React Native还难,难就难在,鸿蒙有一个非标准的 Flex

在上面介绍了 View 组件会被在不同的情况编译的 Flex Row Column,具体来说,

  • align-items 的属性值为 stretchdaseline 或者 flex-wrap 的属性值为 wrap会使用 Flex 组件
  • 否则当 flex-directionrow 或者 row-reverse 使用 Row 组件
  • 其他的情况使用 Column 组件

因为鸿蒙的 RowColumn 组件的align-items不支持 stretchdaseline,而 Row组件又不支持 wrap 换行

在使用 Row 或者 Column 的情况下,这和标准 Flex 布局基本没多大差别,问题就出在使用 Flex 组件这,这个组件有个奇怪的特性

Flex组件主轴默认不设置时撑满父容器,Column、Row组件主轴不设置时默认是跟随子节点大小。

这是官方文档的描述,这是一个很奇怪的特性,且没有办法通过设置取消,这导致很多奇怪的布局行为,这个效果类似于给元素永久设置了个 flex: 1,且比这个好厉害,就算有多层父元素,他也能把外层父元素撑开,感觉它才是爹!

在做兼容的时候,我总结了关于这个特性的兼容方式:

当 flex 的 alitn-items 为 stretch 或者 daseline 时,需要给当前元素指定尺寸,在任何能不用 alitn-items 的这两个属性的时候,都不要用这两个属性。如果要使用stretch,建议父元素使用 alitn-items: flex-start,子元素使用 align-self: stretch,这样的形式

当然,除了 Flex 布局这个最大的问题之外,还有很多与标准不太一样的地方,我整理总结了放在 兼容性章节,在开发的时候需要特别注意

可以说我在兼容鸿蒙的过程中,大部分时间都是在做组件库的兼容,和这些样式斗智斗勇

开始使用

通过上面这一系列的操作,duxapp 框架已经基本实现了兼容鸿蒙,且提供了大量可用的 UI 组件库

你可以快速初始化一个 UI 库的示例项目,你可以通过这个示例项目快速的查看到 UI 库在鸿蒙端以及其他端的效果

npx duxapp-cli create projectExample duxuiExample

在运行这个命令之前,你需要安装好 nodejs 20+ yarn git

创建项目后,进入项目运行以下命令

yarn dev:harmony --app=duxuiExample

编译完成后,使用 DevEco Studio 打开 dist/harmony 目录,这个目录就是一个原生鸿蒙项目

打开项目后,右上角工具栏区域,选择已经开启的虚拟机,并且点击右侧的运行按钮(三角符号),开始编译,编译后,会自动在虚拟机上启动这个 app

如果想继续开发项目,请查看鸿蒙入门文档

当前版本在 windows 系统上,仍然存在问题,能编译成功,但是启动 app 会报错,你可以先尝试使用其他系统进行编译,例如 Mac,这个问题将会在不久后修复

未来展望

目前对鸿蒙的兼容取得了初步的成效,但是也还有少数一些组件和功能还未实现兼容,像下面这些,将逐步完善这些组件或功能

  • PullView Modal 等组件动画效果
  • Picker 组件暂不支持
  • Sign 签名组件待完善
  • 录音组件录音功能待完善
  • 微信模块相关功能暂不支持
  • 地图暂不支持
  • List、Detail 组件待完善

Taro 目前开发中的版本,将放弃使用鸿蒙的 Flex 布局方式,转而使用更底层的实现方式,使用了 React Native 的布局引擎 Yoga,用过 Yoga 引擎来来布局,将引擎的计算结果再赋值到具体的组件上,这样就能极大改善鸿蒙布局兼容性问题,等待 Taro 的这套方案开源后,duxapp 也将跟进

如果你有兴趣,请继续查看文档了解详情吧

开发文档
GitHub

Taro首个支持鸿蒙的 UI 库,同时还兼容 React Native、小程序、H5

· 阅读需 16 分钟
duxapp
duxapp框架作者

Taro 4.0 已经推出一段时间了,4.0 版本主要是支持了鸿蒙端的开发以及 Vite 编译工具的支持。duxapp 在这段时间也跟随 Taro 的脚步,实现的对鸿蒙端的支持,并且也将之前的 duxui 这个多端的 UI 库,对鸿蒙端实现了兼容。

duxui 组件库提供了 60+ 的组件支持,能快速帮助你完成业务。

现在使用这个 UI 库,不仅能开发鸿蒙,还能实现同时开发 React Native、小程序和 H5,也是目前唯一一个能兼容这么多端的 UI 库。

组件展示

效果图

下面我将介绍这一切是如何做到的,这其中遇到了哪些问题

兼容鸿蒙之前

在开始兼容鸿蒙之前,我们来讲一下,duxapp 是一个什么状况

1、样式兼容性

现在 duxapp 已经兼容 小程序、React Native以及H5了,因为 React Native 端在这些端中兼容的样式是最少的,我们是按照React Native的标准去做,我们针对 H5 端和 小程序端的基础样式做了调整

需要将 View 组件调整为默认 Flex 竖向布局、默认的定位方式、模型盒、以及边框等,像下面这样:

/* H5 端 */
taro-view-core {
display: flex;
flex-direction: column;
position: relative;
border-style: solid;
border-width: 0;
}
input,
textarea,
taro-view-core {
box-sizing: border-box;
}
/* 小程序端 */
view {
display: flex;
flex-direction: column;
position: relative;
border-style: solid;
border-width: 0;
}
input,
textarea,
view {
box-sizing: border-box;
}

通过这样的调整,我们在项目中使用 View 组件的时候将默认使用 Flex 竖向布局,这样编写的 css 就能做到同时兼容多个端了

2、现有模块

duxapp 框架中现在已提供了很多的公开模块了,这里介绍三个相关的基础模块,这三个模块在我们兼容鸿蒙的过程中都有很大的关联

  • duxapp

这个和框架名称相同的模块,是所有模块的根模块,所有的模块都需要依赖于这个模块

这个模块内提供了一些非常基础的组件、函数、配置等,且一些东西是必须使用的

用来包裹页面的容器组件 TopView
路由跳转函数 route
导出的用户配置

  • duxui

这个是提供的UI组件库,模块内提供了大量兼容多端的组件库,使用这些组件,能快速开发出兼容多端的应用

  • duxappReactNative

React Native端支持,要兼容 React Native,必须使用这个模块,模块内有 React Native 端的三方依赖、用户协议、版本更新、权限管理等

Taro 和鸿蒙的现状

Taro 4.0 目前有两套实现鸿蒙的方案

  • @tarojs/plugin-platform-harmony-hybrid

webview方案,将 H5 运行在 webview 上,和小程序类似

  • @tarojs/plugin-platform-harmony-ets

duxapp 中为了更好的性能和体验,选择的的是这个方案

底层通过 arkts 以及 arkui 实现,将 Taro 的组件转化为对应的 arkui 里面对应的组件,并实现了 React 的运行时

例如 View 组件会通过 arkui 的 FlexRowColumn 这三个组件来实现,会根据编写的不同样式选择不同的原生组件去渲染,从这里也大概能看出,鸿蒙的 arkui 里面也是 Flex 布局

兼容过程

1、创建一个鸿蒙模块

就像上面介绍的 React Native 端那样,鸿蒙端也需要创建一个模块,用来处理鸿蒙端的基础内容,例如用到的三方依赖、配置插件等

这个模块的名称为 duxappHarmony

2、添加需要的三方依赖

目前鸿蒙端仅支持使用 vite 编译,因此鸿蒙端需要添加编译的相关的依赖,在模块内创建 package.json,内容如下

{
"scripts": {
"build:harmony": "duxapp runtime enterFile && duxapp harmony create && taro build --type harmony",
"dev:harmony": "npm run build:harmony -- --watch"
},
"dependencies": {
"@tarojs/plugin-platform-harmony-ets": "4.0.7",
"vite": "^5.4.10",
"terser": "^5.36.0",
"@tarojs/vite-runner": "4.0.5",
"@vitejs/plugin-react": "^4.3.3",
"vite-plugin-commonjs": "^0.10.3"
}
}

这个完整的文件中还包含了编译鸿蒙的两个命令

3、配置依赖项

添加了依赖这些依赖并不会自动起作用,需要配置 Taro 编译配置,我们在这个模块内创建一个 Taro 的配置文件 taro.config.js,内容如下

// eslint-disable-next-line import/no-commonjs
const commonjs = require('vite-plugin-commonjs').default

module.exports = {
// 鸿蒙编译插件
plugins: [
'@tarojs/plugin-platform-harmony-ets'
],
harmony: {
compiler: {
// 指定 vite 编译
type: 'vite',
vitePlugins: [
commonjs()
]
},
projectPath: './dist/harmony'
}
}

这里面配置了平台插件、指定使用 vite 编译、设置了项目目录

这其中还加入了一个 vite 插件,因为 vite 默认不支持 commonjs,但是 duxapp 项目中使用了commonjs,所以加入了这个插件

到此基础环境配置好了,但是我们还需要创建鸿蒙工程文件,Taro 没有提供这个内容

4、在cli内处理鸿蒙工程文件

在第 2 步的命令中,duxapp harmony create 有这个命令,我们就是通过这个命令来创建的鸿蒙工程文件,这样就不需要再通过鸿蒙的 IDE 再去手动操作创建了,这也是一个相对麻烦的过程

这个过程和 React Native 端是类似的,React Native端也是自动创建安卓和 ios 的工程文件,鸿蒙的工程文件创建会放在 dist/harmony 目录中

创建工程文件的过程中,可以通过配置或脚本对工程文件进行处理,例如鸿蒙端需要包名、版本号、app 名称等信息,都是通过配置文件来生成的

这里就不详细展开了,可以查看文档了解 鸿蒙入门文档

5、组件库兼容

上面的步骤处理完之后,框架的东西就处理完了,但是仍然是不可用的,因为要编写一个兼容多端的项目,现有的组件,还需要针对鸿蒙端做相应的兼容,要兼容的组件包括基础模块中的组件和 duxui 模块中的组件

鸿蒙端兼容是一个很费事的过程,对鸿蒙端的兼容性 React Native还难,难就难在,鸿蒙有一个非标准的 Flex

在上面介绍了 View 组件会被在不同的情况编译的 Flex Row Column,具体来说,

  • align-items 的属性值为 stretchdaseline 或者 flex-wrap 的属性值为 wrap会使用 Flex 组件
  • 否则当 flex-directionrow 或者 row-reverse 使用 Row 组件
  • 其他的情况使用 Column 组件

因为鸿蒙的 RowColumn 组件的align-items不支持 stretchdaseline,而 Row组件又不支持 wrap 换行

在使用 Row 或者 Column 的情况下,这和标准 Flex 布局基本没多大差别,问题就出在使用 Flex 组件这,这个组件有个奇怪的特性

Flex组件主轴默认不设置时撑满父容器,Column、Row组件主轴不设置时默认是跟随子节点大小。

这是官方文档的描述,这是一个很奇怪的特性,且没有办法通过设置取消,这导致很多奇怪的布局行为,这个效果类似于给元素永久设置了个 flex: 1,且比这个好厉害,就算有多层父元素,他也能把外层父元素撑开,感觉它才是爹!

在做兼容的时候,我总结了关于这个特性的兼容方式:

当 flex 的 alitn-items 为 stretch 或者 daseline 时,需要给当前元素指定尺寸,在任何能不用 alitn-items 的这两个属性的时候,都不要用这两个属性。如果要使用stretch,建议父元素使用 alitn-items: flex-start,子元素使用 align-self: stretch,这样的形式

当然,除了 Flex 布局这个最大的问题之外,还有很多与标准不太一样的地方,我整理总结了放在 兼容性章节,在开发的时候需要特别注意

可以说我在兼容鸿蒙的过程中,大部分时间都是在做组件库的兼容,和这些样式斗智斗勇

开始使用

通过上面这一系列的操作,duxapp 框架已经基本实现了兼容鸿蒙,且提供了大量可用的 UI 组件库

你可以快速初始化一个 UI 库的示例项目,你可以通过这个示例项目快速的查看到 UI 库在鸿蒙端以及其他端的效果

npx duxapp-cli create projectExample duxuiExample

在运行这个命令之前,你需要安装好 nodejs 20+ yarn git

创建项目后,进入项目运行以下命令

yarn dev:harmony --app=duxuiExample

编译完成后,使用 DevEco Studio 打开 dist/harmony 目录,这个目录就是一个原生鸿蒙项目

打开项目后,右上角工具栏区域,选择已经开启的虚拟机,并且点击右侧的运行按钮(三角符号),开始编译,编译后,会自动在虚拟机上启动这个 app

如果想继续开发项目,请查看鸿蒙入门文档

当前版本在 windows 系统上,仍然存在问题,能编译成功,但是启动 app 会报错,你可以先尝试使用其他系统进行编译,例如 Mac,这个问题将会在不久后修复

未来展望

目前对鸿蒙的兼容取得了初步的成效,但是也还有少数一些组件和功能还未实现兼容,像下面这些,将逐步完善这些组件或功能

  • PullView Modal 等组件动画效果
  • Picker 组件暂不支持
  • Sign 签名组件待完善
  • 录音组件录音功能待完善
  • 微信模块相关功能暂不支持
  • 地图暂不支持
  • List、Detail 组件待完善

Taro 目前开发中的版本,将放弃使用鸿蒙的 Flex 布局方式,转而使用更底层的实现方式,使用了 React Native 的布局引擎 Yoga,用过 Yoga 引擎来来布局,将引擎的计算结果再赋值到具体的组件上,这样就能极大改善鸿蒙布局兼容性问题,等待 Taro 的这套方案开源后,duxapp 也将跟进

如果你有兴趣,请继续查看文档了解详情吧

开发文档
GitHub

duxui:基于Taro,兼容React Native、小程序、H5的多端UI库

· 阅读需 13 分钟
duxapp
duxapp框架作者

duxui是duxapp官方开发的一款兼容多端的UI组件库,兼容小程序、H5、React Native,库中提供了60+的组件,覆盖大部分使用场景

它能帮助你通过统一的组件样式,快速完成多端应用的开发,包括React Native端的APP开发

duxui已经被我用于很多项目中,其中包含APP的项目就开发了几十个了,你可以通过这个链接去查看或者体验这些APP,https://app.share.dux.plus/,这里只包含了其中一部分,其中一些APP可能因为停止运营,无法正常浏览

示例

如果你想看到这些组件的展示效果,请根据不同的端进行查看

如果你想通过项目来查看这些组件的运行效果或者组件源码,使用下面这个命令创建一个UI库的示例代码

npx duxapp-cli create projectName duxuiExample

其中的 projectName 是要创建的项目名称,创建之后根命令行据提示进行下一步操作

使用

这是duxapp的一个模块,需要在duxapp中使用才能使用这个模块,UI库的文档请查看https://duxapp.cn/docs/duxui/start

如果你还没有这个模块,需要先安装duxui模块,如果你还不知道什么是duxapp框架,请先查看这篇文章了解

yarn duxapp app add duxui

安装之后在你的模块中导入即可使用,像下面这样

import { Column, Header, Image, px, Row, ScrollView, Tab, Text, TopView } from '@/duxui'
import { GridIcon } from '@/gridOperator'

export default function LedgerDetail() {
return <TopView>
<Header title='企业台账详情' />
<ScrollView>
<Row className='mt-3 mh-3 bg-white r-1 p-3 gap-3 items-start'>
<Image style={{ width: px(80) }} square />
<Column grow className='mt-1 gap-1'>
<Text numberOfLines={1}>鄂尔多斯市九工建筑有限责任公司</Text>
<Text className='mt-1' size={18} color={2}>联系人:小阿呆 | 联系电话:010-4521-8652</Text>
<Text size={18} color={2}>所属网格片区:<Text type='primary'>轻纺织产业园</Text> 历史问题:<Text type='danger'>12个</Text></Text>
</Column>
<GridIcon className='self-center text-primary' size={56} name='biaoqianlanbodianhua' />
</Row>
<Column className='mt-3'>
<Tab>
{
tabs.map(item => <Tab.Item key={item.value} title={item.name} paneKey={item.value} />)
}
</Tab>
</Column>
<Column className='mt-3 mh-3 bg-white r-1 p-3'>
<Text size={1} style={{ lineHeight: px(42) }}>公司是适应市场经济社会化大生产的需要而形成的一种企业组织形式。中国的公司是指依照《中华人民共和国公司法》在中国境内设立的以营利为目的社团法人,包括有限责任公司和股份有限公司。 指企业的组织形式。以营利为目的的社团法人。在资本主义社会获得高度发展。我国在建国后对私营公司进行了社会主义改造。国营工、商、建筑、运输等部门中实行独立经济核算的经营管理组织和某些城市中按行业划分的专业管理机构,也通称公司。随着我国经济体制的改革,享有法人资格的各种公司纷纷设立,按章程从事自身的生产经营活动。 指企业的组织形式。以营利为目的的社团法人。在资本主义社会获得高度发展。我国在建国后对私营公司进行了社会主义改造。国营工、商、建筑、运输等部门中实行独立经济核算的经营管理组织和某些城市中按行业划分的专业管理机构,也通称公司。随着我国经济体制的改革,享有法人资格的各种公司纷纷设立,按章程从事自身的生产经营活动。 折叠</Text>
</Column>
</ScrollView>
</TopView>
}

const tabs = [
{ name: '基本信息', value: 0 },
{ name: '上报工单', value: 1 },
{ name: '历史问题', value: 2 }
]

组件库

下面我来介绍一下这些组件和组件的功能

TopView 顶层容器

TopView是作为页面的根组件使用的,在duxapp中,每个页面都需要存在这个组件,他用来实现duxapp的多项功能

Header 头部导航

duxapp默认将头部设置为了自定义,每个页面面建议都使用一个头部组件作为标题的显示,且Header组件会控制状态栏颜色显示、H5页面标题显示

ScrollView 滚动容器

这是对Taro ScrollView的封装,实现了多个端的下拉刷新功能,ScrollView仅支持垂直滚动,需要横向滚动使用 ScrollView.Horizontal

PullView 弹窗

可以从上下左右4个方向弹出内容

Loading 加载动画

用于展示loading动画,类似于ios那样的菊花

Absolute 绝对定位

放在这个组件内的子元素,会被渲染在最外层,这是用TopView.add方法实现的

List 分页列表

当你的列表接口采用 page 进行分页时,可以用这个组件快速实现列表页面,这个组件有用以下特性

  • 下拉刷新
  • 自动分页
  • 空组件展示
  • 自定义头部底部渲染
  • RN端使用 @shopify/flash-list 实现,拥有更好的性能
  • 多列支持

Layout 布局计算

用来计算Layout所在的组件的位置、尺寸信息

ActionSheet 弹出菜单

封装弹出菜单功能

Button 按钮

按钮组件

Cell 单元格

单元格组件用来展示竖向的菜单列表,默认拥有阴影属性

LinearGradient 线性渐变

因为RN不支持通过css编写渐变,所以写了这个组件用于实现渐变功能

BoxShadow 阴影

由于RN 安卓端对阴影的支持不太完善,使用这个组件可以实现阴影效果

Column flex竖向

这是用于快速竖向布局的组件

Row flex横向

这是用于快速横向布局的组件

Space 间距

用于控制子元素的间距,通过 gap 实现,建议直接使用全局样式的 gap

Divider 分割线

使用边框实现的分割线

Grid 宫格

用于实现9宫格这样的布局方式

Card 卡片

卡片布局,带有外边距,内边距、圆角、阴影的组件

Tab 选项卡

选项卡切换,也可以用于表单项使用

TabBar 底部导航

通常用于app首页的底部导航,要使用这个组件需要通过创建函数创建

Elevator 电梯楼层

例如城市选择,可以通过城市名称首字母进行导航,快速选择到需要的城市,就可以用这个组件

下拉菜单,常见用于一些表单的筛选

Form 表单

封装了表单组件,能快速完成表单的布局、表单验证、复杂对象表单

表单包含了一系列的组件或者方法,包括下面这些

  • Form 表单
  • Form.Item 表单项
  • Form.Submit 表单提交
  • Form.Reset 表单重置
  • Form.Object 对象表单
  • Form.Array 数组表单
  • Form.ArrayAction 数组表单操作管理
  • Form.useFormContext 获取表单上下文
  • Form.useFormItemProxy 给表单代理值和事件

Input 输入框

对Taro Input的封装,新增了一些属性,基本不带样式,需要自行编写样式

Textarea 多行文本

对Taro Textarea的封装,新增了一些属性

Picker 选择器

选择器包含了单列选择器和多列选择器

PickerDate 日期选择

用于日期时间的选择

Radio 单选

单选组件

Checkbox 多选

多选组件

Switch 开关

开关组件

Calendar 日历

为兼容多端,所以编写的日历组件,支持日、周、范围选择,支持多种自定义日历的方式

Grade 评分

评分组件

Cascade 级联选择

一个功能丰富的级联选择器,支持单选、多选,支持多级选择、单级选择,等更多功能

CardSelect 卡片选择

一个丰富样式的卡片类型的选择器,同时支持单选和多选模式

Upload 上传

用于上传图片或者视频的组件

ModalForm 弹出表单

用于将其他表单封装为一个弹出表单,例如单列选择器、日期选择器都是由这个组件封装的

这里包含了

  • ModalForm 单出单个表单,作为一个表单使用,放在 Form.Item 中
  • ModalForms 弹出多个表单,独立内容,放在Form内任何位置

NumberKeyboard 数字键盘

用于数字的输入,例如输入验证码,支付密码、电话号码、身份证等

InputCode 验证码密码输入

通常需要配合 NumberKeyboard 组件来实现验证码的输入功能

LicensePlate 车牌号输入

车牌号输入组件

Text 文本

对Text的封装,对主题色、字号等进行了主题配置,对常用css的快捷属性封装

Image 图片

对图片的封装,实现了预览,多图预览,在RN端使用 expo-image 实现,提供更好的性能

Badge 徽标

用于展示未读消息数量,红点

Tag 标签

标签展示

Avatar 头像

显示一个头像或者头像组

HtmlView 富文本显示

用于显示富文本

Step 步骤条

例如快递更新日志,就可以用这个组件来实现

Empty 空数据

某些列表数据为空时可以用这个组件显示

Status 角标状态

显示在卡片四个角的状态

LongPress 长按

长按事件封装

TouchableOpacity 触摸反馈

点击具有不透明效果的组件

将内容弹出,显示在屏幕中间

在元素做所在位置弹出一个菜单

loading 显示加载动画

显示加载中动画

message 消息通知

显示一个消息通知

confirm 确认弹框

异步调用一个确认弹框

Sign 签名

手写签名组件

HorseLanternLottery 跑马灯抽奖

抽奖组件,可以随机结果,也可以异步指定抽奖结果

总结

可以看到,组件库很丰富,覆盖的使用场景很多

详细的使用方法,请前往开发文档查看

开发文档:http://duxapp.cn

GitHub:https://github.com/duxapp

duxapp对RN端开发进行了哪些改动,如何提升开发效率

· 阅读需 12 分钟
duxapp
duxapp框架作者

Taro的React Native端开发提供了两种开发方式,一种是将壳和代码分离,一种是将壳和代码合并在一起开发

信息

duxapp中更进一步,你不需要太关注壳子什么的,你只需要安装好安卓和ios的编译环境,用一个命令就能编译apk或者ios,并且这个编译的过程和duxapp的模块化理念高度绑定,通过指定 --app= 指定不同的入口,就能打包出不同的项目,就像下面这样

# 编译 duxuiExample 的安卓调试版本
yarn android --app=duxuiExample

# 编译 duxuiExample 的IOS调试版本
yarn ios --app=duxuiExample

# 编译成功后启动Metro代码编译服务
yarn start --app=duxuiExample

下面我来详细介绍一下,在duxapp中是如何对RN进行优化的

配置化

对于Taro的壳子,或者原生React Native,都会存在 android ios这两个文件夹,而在duxapp中,这些文件夹的内容是自动生成的,那么对于需要在这些文件夹中修改的配置内容,例如包名、版本号、新架构开关等,都通过配置文件的方式配置了,而不需要需修改具体的文件

这个配置文件是项目配置文件夹下的 configs/duxuiExample/duxapp.rn.js,其中 duxuiExample 就是我通过--app=duxuiExample 指定的入口模块

这个配置文件的内容就像下面这样,可以清晰的看到,对安卓配置了包名、名称、版本号等信息,IOS同样如此

const config = {
android: {
appid: 'cn.duxapp.duxui',
appName: 'duxUI库',
versionCode: 2,
versionName: '1.1.0',
keystore: {
storeFile: 'duxui.keystore',
keyAlias: 'duxui',
storePassword: 'TN62eyasJAKm2ksD',
keyPassword: 'TN62eyasJAKm2ksD'
}
},
ios: {
BundleId: 'cn.duxapp.duxui',
appName: 'duxUI库',
versionCode: 1,
versionName: '1.0.0',
team: '',
plist: {
'duxapp/Info.plist': {
NSCameraUsageDescription: 'duxUI库需要拍照用于APP内图片上传更换头像',
NSContactsUsageDescription: 'duxapp需要访问你的通讯录,将客户信息保存到通讯录中',
NSLocalNetworkUsageDescription: 'App需要访问你的本地网络,用于和服务器建立连接',
NSLocationAlwaysAndWhenInUseUsageDescription: '使用你的位置信息用于地图定位和位置选择',
NSLocationAlwaysUsageDescription: '使用你的位置信息用于地图定位和位置选择',
NSLocationWhenInUseUsageDescription: '使用你的位置信息用于地图定位和位置选择',
NSPhotoLibraryAddUsageDescription: 'duxUI库需要保存宣传图到你的相册用于分享',
NSPhotoLibraryUsageDescription: 'duxUI库需要访问相册用于APP内图片上传更换头像',
}
}
}
}

module.exports = config

内容复制

上面这个配置文件已经解决了大部分打包需要用到的配置,但是你开发过RN的话你会看出来,证书他是一个文件,这里只指定了证书文件名称,但是并未指定证书具体内容,还有打包一个app,它总是需要一个app图标的,包括安卓和ios的图标,那么这些内容,可以通过配置文件中的copy文件夹,将这些项目文件复制到安卓或者ios对应的文件位置

这个文件夹内容看起来是这样的

copy结构

那么你又会发现,好像这些文件的结构,以及如何生成这些文件,又是一个头疼的问题,duxapp-cli,帮你解决了这个麻烦的问题,只需要两个简单的命令,就可以自动创建这个些文件

首先是安卓证书文件,需要注意的是,这里是指定--config=,而不是指定 --app=

yarn duxapp android keystore --config=duxuiExample

创建成功后,需要手动将命令行打印的配置内容,放进duxapp.rn.js相应位置

然后是logo创建,需要将你项目的logo文件放在配置文件根目录,也就是 configs/duxuiExample/logo.png

yarn duxapp rn logo --config=duxuiExample

命令使用成功后,他会自动把logo放进对应位置,你就不需要进一步操作了

这样是不是就简单起来了,下面来看看,要如何使用第三方插件,例如微信插件、高德地图插件等

信息

以上所有提到的 duxuiExample 都是以 UI库示例 这个模块项目来举例的,在你的项目中根据实际情况替换

三方模块

你的项目或多或少都要用一些第三方的插件,React Native基础模块中已经包含了很多基础常用插件,你可以通过三方模块查看到,包含的基础插件

传统的方法是将他们添加到 package.json 依赖中,然后根据文档内容修改安卓或者ios文件夹对应的内容,在duxapp中提供了另外一种方式来实现第三方插件的使用

像这个react-native-view-shot安装方式很简单的插件,他只要求你将他添加到 package.json 的依赖中就可以使用了

那么我们结合模块,在你需要用到这个功能的模块配置文件中,一样的添加上这个依赖即可,像下面这个duxui模块的package.json文件一样

{
"dependencies": {
"b-validate": "^1.5.3",
"react-native-view-shot": "~3.8.0",
"react-native-fast-shadow": "~0.1.1",
"array-tree-filter": "^2.1.0"
}
}

其实开源的大多数第三方插件都是这样的,只需要添加到依赖中,重新打包就能用了,但是很少数的插件,他就是要改一些安卓或者ios里面的原生内容,像微信插件,它需要的改动还挺多的,我根据他文档需求,列举了下面这些

安卓:

  • 添加 proguard
  • 新建 WXEntryActivity.java 用于回调处理
  • 新建 WXPayEntryActivity.java 用于支付回调处理
  • 添加 <package android:name="com.tencent.mm" /> 用于跳转到微信的白名单
  • 添加 .wxapi.WXEntryActivity
  • 添加 .wxapi.WXPayEntryActivity

ios:

  • 由于插件bug,需要添加 pod 依赖项 pod 'WechatOpenSDK'
  • 修改 AppDelegate.h 入口文件
  • 修改 AppDelegate.mm 文件进行一些处理
  • Info.plist 添加 Schemes 和 BundleURLTypes 和 applinks
  • 在项目配置中,添加 UniversalLink

其他:

  • 通过patch修复当前版本的一个bug

首先还是要在模块中添加依赖 package.json 文件

{
"dependencies": {
"react-native-wechat-lib": "^3.0.4",
"wechat-jssdk": "^5.1.0"
}
}

那么在duxapp前面提到,安卓和ios文件夹的内容都是自动生成的,我又是如何处理这些修改的呢?这里就需要用到 duxapp-cli 提供的模块更新脚本来处理

针对微信插件的处理脚本文件位于 src/wechat/update/index.js,这个文件的内容是下面这样的

// eslint-disable-next-line import/no-commonjs
module.exports = ({ config }) => {
const { android, option } = config
return {
// 描点插入
insert: {
'android/app/proguard-rules.pro': {
'content': `
##### 微信 ######
-keep class com.tencent.mm.opensdk.** { *; }
-keep class com.tencent.wxop.** { *; }
-keep class com.tencent.mm.sdk.** { *; }`
},
'ios/Podfile': {
'podEnd': ` pod 'WechatOpenSDK'`
},
'ios/duxapp/AppDelegate.h': {
import: ' #import "WXApi.h"',
'appDelegate.protocol': ' ,WXApiDelegate'
},
'ios/duxapp/AppDelegate.mm': {
import: '#import <React/RCTLinkingManager.h>',
appDelegate: `// react-native-wechat-lib start

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
return [WXApi handleOpenURL:url delegate:self];
}

- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void(^)(NSArray<id<UIUserActivityRestoring>> * __nullable
restorableObjects))restorationHandler {
// 触发回调方法
[RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
return [WXApi handleOpenUniversalLink:userActivity
delegate:self];
}

// Universal Links 配置文件, 没使用的话可以忽略。
// ios 9.0+
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
options:(NSDictionary<NSString*, id> *)options
{
// Triggers a callback event.
// 触发回调事件
[RCTLinkingManager application:application openURL:url options:options];
return [WXApi handleOpenURL:url delegate:self];
}
// react-native-wechat-lib end`
}
},
create: {
'android/app/src/main/java/cn/duxapp/wxapi/WXEntryActivity.java': `package ${android.appid}.wxapi;

import android.app.Activity;
import android.os.Bundle;
import com.wechatlib.WeChatLibModule;

public class WXEntryActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WeChatLibModule.handleIntent(getIntent());
finish();
}
}
`,
'android/app/src/main/java/cn/duxapp/wxapi/WXPayEntryActivity.java': `package ${android.appid}.wxapi;

import android.app.Activity;
import android.os.Bundle;
import com.wechatlib.WeChatLibModule;

public class WXPayEntryActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WeChatLibModule.handleIntent(getIntent());
finish();
}
}
`
},
android: {
xml: {
'app/src/main/AndroidManifest.xml': {
tag: {
queries: {
child: '<package android:name="com.tencent.mm" />'
}
},
attr: {
'android:name=".MainApplication"': {
child: `<activity
android:name=".wxapi.WXEntryActivity"
android:label="@string/app_name"
android:exported="true"
android:taskAffinity="${android.appid}"
android:launchMode="singleTask"
/>
<activity
android:name=".wxapi.WXPayEntryActivity"
android:label="@string/app_name"
android:exported="true"
/>`
}
}
}
}
},
ios: {
plist: {
'duxapp/Info.plist': {
CFBundleURLTypes: [
{
CFBundleTypeRole: 'Editor',
CFBundleURLName: 'weixin',
CFBundleURLSchemes: [
option?.wechat?.appid || 'wx'
]
}
],
LSApplicationQueriesSchemes: ['weixin', 'wechat', 'weixinULAPI']
},
'duxapp/duxapp.entitlements': {
'com.apple.developer.associated-domains': [
`applinks:${option?.wechat?.applinks || 'duxapp.cn'}`
]
}
}
}
}
}

这个文件导出了一个函数,这个函数参数中的 config 就是当前项目的RN编译配置文件,这个文件中可以获取到了包名、版本号等信息

函数返回了一个对象,这个对象中的每一个key就代表不同的功能,下面一一介绍一下这些key

  • insert 用于将内容插入到指定文件的指定位置
  • create 用于将文件创建于指定位置
  • android 其中的xml用来处理合并安卓中的xml文件的,这是用 xmldom来实现的
  • ios 其中的plist是用来合并ios的plist配置文件的

关于这个脚本文件的详细内容需阅读 使用原生模块 了解详情

看了半天,是不是感觉这个模块处理也是挺复杂的,其实我已经封装了一些常用的原生模块,就像这个微信插件,你不需要再去实现一遍,你只需要安装这个微信模块并把他添加到你项目模块的依赖中就能使用了

yarn duxapp app add wechat

然后就像 duxuiExample 这个模块的配置文件一样,将 wechat 添加到依赖中,然后重新编译

{
"name": "duxuiExample",
"description": "ui库示例",
"version": "1.0.13",
"dependencies": [
"duxui",
"duxcms",
"amap",
"echarts",
"wechat"
]
}

还有更多的模块,请前往应用商店查看 https://www.dux.cn/page/apps

总结

通过上面的说明,你已经基本了解了duxapp是如何处理RN端开发的,还有很多的详细的内容,还需要前往文档查看http://duxapp.cn/docs/course/rn/start

再结合duxapp提供的ui库、工具库、全局样式等方法,就能很快的完成你的APP项目了

GitHub:https://github.com/duxapp

Taro使用模块化开发,以提升开发效率

· 阅读需 17 分钟
duxapp
duxapp框架作者

duxapp是基于Taro二次开发的模块化框架

使用这个框架,结合框架提供的UI库和工具库,能帮助你快速且高质量的完成项目,且能实现同时开发小程序、H5、APP(React Native),并且保证各个端的一致性

duxapp还针对APP开发(React Native)做了大量优化,大大降低了APP发开的难度,你可以阅读React Native教程,了解详情

下面让我来详细介绍如何使用duxapp

何为模块化

什么是模块化?就像npm包一样,我们可以将一些通用的功能或页面编写在一个模块内,提供给多个项目来使用,以提高代码的复用性。

模块的概念在很多后端框架中很常见,它们可以在应用商店通过安装应用的方式来获得新功能,在前端框架中确很少见到类似的设计方案,当然你其实也可以理解为发布到npm就是一种模块化的设计,但是在Taro中很多功能他并不能发布到npm中,例如页面。页面需要放在项目中,当发布到npm之后就会无法使用

在duxapp框架中的模块化设计原理,和npm的依赖关系是类似的,每个模块有一个配置文件app.json,里面的依赖字段dependencies,用来填写我要用到的依赖,就像下面ui库示例这个模块的配置

{
"name": "duxuiExample",
"description": "ui库示例",
"version": "1.0.13",
"dependencies": [
"duxui",
"duxcms",
"amap",
"echarts",
"wechat"
]
}

和npm依赖不一样的是,这里的依赖不包含版本信息。因为页面等限制条件,你一个项目中,同一个模块无法存在两个不同的版本,因此并未设计指定版本号的功能

依赖关系是逐层查找的,就像npm一样,例如这里依赖的duxui模块,他的模块配置文件是这样的

{
"name": "duxui",
"description": "DUXUI库",
"version": "1.0.42",
"dependencies": [
"duxapp"
]
}

在duxui模块中他又使用了duxapp这个依赖,通过每个模块都去查找,我们最终整理出这样的依赖关系图

dependencies

那么最终当我们使用下面的命令编译duxuiExample模块的时候

# 调试小程序
yarn dev:weapp --app=duxuiExample
# 调试h5
yarn dev:h5 --app=duxuiExample

实际被编译的模块就包含下面这些

  • duxuiExample
  • duxcms
  • amap
  • echarts
  • wechat
  • duxui
  • duxappReactNative
  • duxapp

使用duxapp

上面介绍了模块化的原理,现在我们来看看,具体要怎么使用这个框架

首先使用cli命令创建一个项目,中途会要求你选择模板,你可以选择 duxui 示例代码(包含所有组件的示例代码 支持RN端) 这个选项,和上面使用的示例一样

npx duxapp-cli create projectName

在使用这个命令之前,确保安装了以下工具和环境

安装后会自动安装项目依赖

进入项目目录projectName,使用上面提到的命令 yarn dev:weapp --app=duxuiExample 或者 yarn dev:h5 --app=duxuiExample 编译为小程序或者H5,使用开发者工具或者浏览器就能预览

可以看到编译命令是在Taro原有的命令基础上增加了 --app= 参数,参数用来指定一个模块,通常你都需要指定这个参数,因为你的项目中除了上面提到的模块之外,大多是时候还会存在其他模块,如果你不指定的话,他会把所有模块都打包进去

通过上面的描述可以看出,其实在一个项目中不是真的只有一个项目,在我的实际开发经验中,我是将很多项目放在一起开发的,我只需要通过 --app= 参数指定我的项目入口文件进行编译,他就是不同的项目

多个项目同时存在,如何保持他们不混乱呢,例如第三方npm依赖,每个项目可能都有不同的npm依赖,这通过下面的章节来介绍

模块

在duxapp框架中,src目录下每个文件夹将被识别为一个模块,模块一般是像下面这样设计结构的

├── duxapp                      模块名称
│ ├── components 模块组件库
│ │ ├── ComponentName 组件
│ │ │ └── index.jsx
│ │ └── index.js 导出需要导出的组件
│ ├── config 配置目录
│ │ ├── route.js 路由配置文件(路径固定)
│ │ ├── theme.js 主题配置文件(路径固定)
│ │ └── themeToScss.js 主题转换函数(路径固定)
│ ├── pages 页面放置文件夹
│ │ └── index 页面文件夹
│ │ ├── index.jsx 页面
│ │ └── index.scss
│ ├── utils 工具库
│ │ ├── index.js 导出工具库
│ │ └── ...you util.js
│ ├── update 模块安装目录
│ │ ├── copy 需要复制到项目的文件(路径固定)
│ │ │ └── ...
│ │ └── index.js 安装脚本 主要针对RN端 插件安装方法(路径固定)
│ ├── app.js 模块入口文件
│ ├── app.json 模块配置文件 包括名称 依赖等(必须)
│ ├── app.scss 全局样式文件(次样式文件无需导入到js文件中,会自动注入全局)
│ ├── package.json 和项目的package.json相同,用于指定当前模块需要用到的三方依赖等
│ ├── changelog.md 更新日志(必须 如果发布)
│ ├── index.js 模块出口文件 可以导出组件和方法给其他模块使用
│ ├── index.html 如果是h5的项目可以自定义index.html,仅当作为入口模块时可用
│ ├── app.config.js 用于覆盖项目全局配置
│ ├── babel.config.js babel配置文件
│ ├── metro.config.js metro配置文件
│ ├── taro.config.js Taro编译配置文件
│ ├── taro.config.prod.js Taro 发布配置文件
│ ├── taro.config.dev.js Taro 调试配置文件
│ └── readme.md 自述文件(必须 如果发布)

关于模块目录的详细内容查看这个 模块结构 获取

模块配置

在duxui这个模块中,它的配置文件是这样的

{
"name": "duxui",
"description": "DUXUI库",
"version": "1.0.42",
"dependencies": [
"duxapp"
]
}

我们看到,他有一个字段 npm,(新版本中,这个配置已经移动到模块的package.json文件中)它的内容和项目的 package.json 的配置是完全一样的,在模块中编写这个内容,将会和项目的 package.json 进行覆盖合并,那么你就可以通过模块来安装当前模块需要依赖了,每个模块中都可以指定这个依赖,他们会合并在一起

当你指定了不同的 --app= 入口模块之后,框架会根据你使用到的模块中的第三方依赖自动重新安装

在模块中还有很多类似的设计,用来编写配置或者文件,包括下面这些

  • app.scss 编写全局样式
  • index.html 如果是h5的项目可以自定义index.html,仅当作为入口模块时可用
  • app.config.js 用于覆盖项目全局配置
  • babel.config.js babel配置文件
  • metro.config.js metro配置文件
  • taro.config.js Taro编译配置文件

模块路由

每个模块中都可以编写页面,当然这不是必选项,这些页面会被定义在自己的模块中

通过 modeName/config/route.js 定义当前的模块路由,例如 duxuiExample 的路由定义如下

/**
* login:是否需要登录
* platform:支持的平台(weapp, h5, rn)不配置支持所有
* subPackage:是否将其设置为分包
* home: 是否是主页 是主页的页面将会被排在前面
*/
const config = {
pages: {
'duxuiExample/index': {
pages: {
index: {
home: true
}
}
},
'duxuiExample/example': {
pages: {
Button: {},
Cell: {},
Grid: {},
Divider: {},
Space: {},
// 更多未展示
}
}
}
}

module.exports = config

路由的定义也是经过封装的,配置的时候是将一个文件夹作为一个对象来处理,这样我们能很方便的将某个文件夹进行分包等操作

使用UI库和全局样式编写页面

在基础模块 duxapp 中提供了可以用于快速布局页面的全局样式,他就和 tailwindcss 类似,在结合UI组件,编写页面像下面这样的,可以看到我们不需要编写 scss 文件就能完成页面的编写

import { Avatar, Card, ScrollView, Column, Divider, Header, Text, TopView, Row, px, Image, nav, Tag } from '@/duxui'
import { useRequest, CmsIcon, saleHook, Qrcode } from '@/duxcmsSale'
import { setClipboardData } from '@tarojs/taro'

export default function Sale() {

const [{ info = {}, day = {}, money, total = {} }] = useRequest('sale/index')

return <TopView>
<Header absolute title='推广中心' color='#FFFFFF' style={{ backgroundColor: 'transparent' }} />
<Image style={{ height: px(396) }} className='w-full absolute' src={require('./images/tui_bag.png')} />
<Row justify='between' items='center' style={{ marginTop: px(208) }} className='mt-3 ph-3'>
<Row items='center' justify='start'>
<Avatar url={info.avatar}>{info.nickname}</Avatar>
<Column className='mh-3'>
<Row items='center' className='gap-2'>
<Text size={33} bold color='#FFFFFF' >{info.nickname}</Text>
{!!info.level_name && <Tag type='primary' size='s'>{info.level_name}</Tag>}
</Row>
<Row className='mt-2'>
<Text color='#FFFFFF' size={1}>邀请码:{info.code}</Text>
<CmsIcon className='mh-2' size={36} name='copy' color='#FFFFFF' onClick={() => setClipboardData({ data: info.code })} />
</Row>
</Column>
</Row>
<CmsIcon size={60} name='QRcode1' color='#FFFFFF' onClick={Qrcode.show} />
</Row>
<ScrollView className='mt-3'>
<Card margin disableMarginTop>
<Row jtems='center' justify='between' className='gap-3'>
<Column justify='center' items='center' grow >
<Text bold type='primary'>{total.order_num || 0}</Text>
<Text color={2} size={2} className='mt-2'>直推订单</Text>
</Column>
<Column justify='center' items='center' grow>
<Text bold type='primary'>{total.user_num || 0}</Text>
<Text color={2} size={2} className='mt-2'>直推客户</Text>
</Column>
</Row>
<Row className='mt-2' jtems='center' justify='between'>
<Column justify='center' items='center' grow>
<Text bold type='primary'>{total.month_sale_money || 0}</Text>
<Text color={2} size={2} className='mt-2'>本月收益</Text>
</Column>
<Column justify='center' items='center' grow>
<Text bold type='primary'>{total.sale_money || 0}</Text>
<Text color={2} size={2} className='mt-2'>累计收益</Text>
</Column>
</Row>
</Card>
<Card shadow margin disableMarginTop onClick={() => nav('duxcmsAccount/cash/index')}>
<Row items='center' justify='between'>
<Text bold>佣金管理</Text>
<CmsIcon name='direction_right' size={32} />
</Row>
<Row className='mt-3' items='baseline'>
<Text size={2}>可提现佣金:</Text>
<Text className='mh-3' bold size={50}>{money || 0}</Text>
</Row>
</Card>
<Card margin disableMarginTop>
<Row items='center' justify='around'>
<Column items='center'>
<Text size={2}>今日预估收益</Text>
<Text className='mt-1' bold size={40} >{day.sale_money || 0}</Text>
</Column>
<Column items='center'>
<Text size={2}>今日有效订单</Text>
<Text className='mt-1' bold size={40} >{day.order_num || 0}</Text>
</Column>
<Column items='center'>
<Text size={2}>今日新增客户</Text>
<Text className='mt-1' bold size={40} >{day.user_num || 0}</Text>
</Column>
</Row>
</Card>

<Card margin className='gap-4'>
<Text size={4} bold>其他操作</Text>
<Row justify='between' items='center' onClick={() => nav('duxcmsSale/index/order')}>
<Text size={2} className='mh-2' bold>推广订单</Text>
<CmsIcon name='direction_right' size={32} />
</Row>
<Row justify='between' items='center' onClick={() => nav('duxcmsSale/index/customer')} >
<Text size={2} className='mh-2' bold>我的客户</Text>
<CmsIcon name='direction_right' size={32} />
</Row>
<saleHook.Render mark='index.menus' />
</Card>
<Row style={{ height: px(16) }}></Row>
</ScrollView >
</TopView>
}

为何获得更好的编辑体验,需要在vscode中安装 SCSS Everywhere 插件,他能识别到全局样式并给出编写提示

用户配置

很多模块都是通用的,那么一些需要根据不同项目变化的内容,就不能写在模块中,而是要通过配置的形式来配置

项目配置放在项目根目录下的 configs 目录中,其中每个文件夹就是一个配置,文件中的index.js就是项目配置

像下面这个duxuiExample的配置,其中option中的每一项就是对应模块的配置

// import qiniu from './base/components/UploadFileManage/drive/qiniu'

const config = {
// 覆盖app.config.js 配置
appConfig: {
requiredPrivateInfos: [
'chooseLocation',
'getLocation',
'onLocationChange',
'startLocationUpdateBackground',
'chooseAddress'
]
},
// 调试配置
debug: {
// 在h5端开启vconsole调试功能
vconsole: false
},
// 模块配置 将会调用模块生命周期的option,将对应模块的参数传入
option: {
// 基础模块
duxapp: {
theme: {
primaryColor: '#E70012',
secondaryColor: '#E84C00',
successColor: '#34a853',
warningColor: '#fbbc05',
dangerColor: '#ea4335',
pageColor: '#F7F9FC',

textColor1: '#373D52',
textColor2: '#73778E',
textColor3: '#A1A6B6',
textColor4: '#FFF',
header: {
color: '#fff', // 仅支持rgb hex值,请勿使用纯单词 设置为数组将显示一个渐变按钮
textColor: '#000', // 文本颜色
showWechat: false, // 微信公众号是否显示header
showWap: true, // h5是否显示header
}
}
},
codepush: {
androidKey: '',
iosKey: '',
},
wechat: {
// 分享组件配置
share: {
open: true,
// 开启未定义的页面分享
pageSlef: {
// 包含这些页面分享自身 页面路径关键词匹配 include 优先级比 exclude 高,
// 可以配置exclude为空数组表示支持所有页面
// pageSlef优先级高于pageHome
// include: ['page/test'],
// 排除这些页面 不进行分享
exclude: []
},
// 开启未定义的页面分享到指定页面
pageHome: {
path: '',
params: {},
// 包含这些页面分享自身 页面路径关键词匹配
// include: [],
// 排除这些页面 不进行分享
// exclude: []
},
// 公共分享参数
common: {
title: 'DUXUI',
desc: '同时兼容小程序、H5、RN',
image: 'https://img.zhenxinhuixuan.com/weiwait/cropper/2lVCofRIu6Jl3jNebxCA6VkEMUeaobvLWFYMTiaG.jpg'
},
platform: {
app: {
// 配置分享到小程序的原始id 同时相当于开关
weappUserName: '',
// 配置分享到h5的url 同时相当于开关
h5Url: 'https://duxui.cn',
}
}
}
},
// 新php模块化系统
duxcms: {
request: {
origin: 'https://mock.dux.plus',
path: 'api', // 域名二级目录
secretId: '53368068',
secretKey: '6c278fbf1791fbed3ae79197de03f65f',
devOpen: false,
devToken: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtZW1iZXIiLCJpYXQiOjE2ODc5Mzg3NDEsImV4cCI6MTY5MDUzMDc0MSwiaWQiOjZ9.kCb82Y3bgUJWUo_WYsUPO1cLYzF1OJdEWTKAj9iNlF0'
},
// 登录相关配置
loginConfig: {
// 手机号登录
phone: true,
// 邮箱登录
email: false,
// app微信登录
appWatch: true,
// 小程序微信登录
weappWatch: true,
// 名称
appName: 'duxui'
}
}
}
}

export default config

后端框架

在我开发的项目中,后端框架是我的合伙人负责的,后端同样也是采用模块化的开发模式完成的,duxapp框架对后端框架已经打好了对接的基础,如果你考虑进一步提升后端的开发效率,可以考虑使用

后端开发文档:https://www.dux.cn/

总结

这一篇文章已经很长了,duxapp框架中还有很多内容,无法在这里一一介绍,像下面这些

  • 模块主题
  • 模块安装与发布
  • 快速开发APP(React Native)
  • 请求上传
  • 路由系统
  • 开放模块(用户管理、微信、支付宝、页面设计器等)
  • 等等

请前往开发文档查看详细教程

开发文档:http://duxapp.cn/

GitHub:https://github.com/duxapp

2024-09 升级到Taro4 RN0.75

· 阅读需 4 分钟
duxapp
duxapp框架作者

在此次更新之前的版本中,Taro使用的是3.6.7,RN使用的是0.71,在这次升级中将Taro和RN都更新到了最新版本,让我们来看看这次更新中都有哪些变化

完善的开发文档

这次更新后,对duxapp进行了阶段性的总结,于是将开发文档进行了全面的完善,包括基础教程、基础模块教程、UI库教程等等模块的教程

点击导航栏去查看它们

模块更新

  • 将duxui库的types进行了全面的完善
  • 将公共模块进行了全面更新以兼容新版本的RN和Taro
  • 将这些模块重新发布到了应用市场

Taro升级4.0

Taro4.0主要是新增了对鸿蒙的支持、支持Vite编译等

这个版本对小程序和H5端的影响不是很大,但是对RN升级有影响,只有4.0的版本才能将RN升级到0.75

RN升级0.75

从0.71到0.75的升级RN为我们带来了诸多好处

  • 开启新架构后依然能兼容不支持新架构的模块 所以在升级到0.75后,新架构是默认开启的,这可以为你的APP提供更好的性能
  • 更完善的样式布局,例如新增了对transform-origintranslate百分比、gap百分比、position: 'static'等样式的支持
  • 更多更新你可以查看 RN 博客

三方依赖更新

  • 将RN的依赖都更新到了最新的版本
  • 移除了react-native-fs依赖,因为这个依赖已经不更新了,使用expo-file-system
  • 移除了rn-fetch-blob依赖,也是不更新,将其替换为react-native-blob-util
  • 新增了expo-image用于RN端图片的实现

导入方式变更

不支持这种方式导入

import Taro from '@tarojs/taro'

需要修改为这种方式

import { showModal } from '@tarojs/taro'

CLI更新

对cli更新,提供了更完善的支持

  • 新增模块 app.config.js 全局配置的支持
  • 对入口文件app.js修改为可选
  • 对路由配置文件config/route.js修改为可选
  • 优化创建项目命令
  • 安卓更新脚本支持对xml文件处理
  • 更新脚本支持创建文件
  • 自动更新项目公共文件,让你的项目保持最新

如何更新到最新版

更新其实也比较简单,将你依赖的公共模块都升级到最新版本

例如你是用了duxui yarn duxapp add duxui,就更新它,然后重新编译即可

当然更新有风险,你可能还需要对现有代码进行一些兼容性修改才能正常运行,需要谨慎操作

用duxapp快速完成项目

· 阅读需 10 分钟
duxapp
duxapp框架作者

使用duxapp,我是如何实现快速完成项目开发的?

像下面这个例子,这个项目有140多个页面,但是真实的开发时间,在熟练使用duxapp的情况下,不会超过两周,并且可以将它兼容APP、小程序、H5

p1 p2 p3

这里仅展示了其中一部分页面,这个项目主要包含下这些功能

  • 购物订单流程
  • 售后退换
  • 文章发布
  • 门店功能
  • 送货人员功能
  • 经销商功能
  • 扫码抽奖
  • 等其他功能

下面我将详细介绍使用了哪些方法,来快速完成项目

使用全局样式快速布局页面

以这个门店管理首页为例,你可以看到我并没有导入一个scss文件,但是我使用了很多的className,这些就是用的全局样式,这全局样式都是duxapp模块提供的

import { TopView, Header, Card, Divider, ScrollView, Tag, Row, Image, Column, Text, px, nav, ModalForm, DatePicker, dayjs } from '@/duxui'
import { ArecaIcon, CmsIcon, TipNav, useRequest } from '@/arecaShop'
import { useState } from 'react'

export default function StoreManage() {

const [date, setDate] = useState()

const [{ info = {}, sku = {}, distribution = {} }] = useRequest({
url: 'store/index',
toast: true,
data: {
date
}
}, { reloadForShow: true })

return (
<TopView>
<Header title='门店详情' />
<ScrollView>
<Card className='p-2 gap-2' margin disableMarginBottom>
<Row className='gap-2'>
<Image className='r-2'
src={info.image}
style={{ width: px(148) }}
square
/>
<Column justify='between' grow>
<Row justify='between' grow>
<Text bold size={4}>{info.name}</Text>
<ArecaIcon name='shezhi' className='text-c1 text-s7'
onClick={() => nav('arecaShop/storeManage/info')}
/>
</Row>
<Column>
<Text color={2} size={1}>联系人:{info.contact}</Text>
<Row grow justify='between' items='center'>
<Text color={2} size={1}></Text>
<Tag type='secondary' size='s' radiusType='round' onClick={() => nav(`tel:${info.distributor_tel}`)}>
<ArecaIcon className='text-white text-s2' name='shiwu-shouji' /> 一键报单进货
</Tag>
</Row>
</Column>
</Column>
</Row>
<Divider />
<Row>
<Text color={2} size={1} shrink>门店地址:</Text>
<Text bold size={1}>{info.area}{info.address}</Text>
</Row>
</Card>
<TipNav title='货品统计' url='arecaShop/storeManage/mallTotal'>
<Row className='items-center' grow>
<ArecaIcon className='text-secondary' name='promot_tips' />
<Text type='secondary' size={2}>{dayjs().format('MM/DD HH:mm')} 更新</Text>
</Row>
</TipNav>
<Card margin disableMarginBottom className='gap-2'>
<Text color={3} size={2}>历史进货数量(小包):{sku.in}</Text>

<Row justify='between' className='gap-2'>
<Column grow className='bg-page r-2 p-3 gap-1'>
<Text color={3} size={2}>历史销售数量(小包)</Text>
<Text bold size={40}>{sku.out}</Text>
</Column>
<Column grow className='bg-page r-2 p-3 gap-1'>
<Text color={3} size={2}>历史剩余数量(小包)</Text>
<Text bold size={40}>{sku.supply}</Text>
</Column>
</Row>
<Text size={22} type='secondary'>*销量及剩余仅供参考:记录消费者扫码数量,可能与实际结果有出入</Text>
</Card>
<TipNav title='配送记录'
right={<ModalForm
title='月份'
renderForm={<DatePicker
mode='month'
/>}
childPropsValueKey='value'
value={date}
onChange={setDate}
>
<DateChild />
</ModalForm>}
></TipNav>
<Card margin disableMarginBottom className='gap-2'>
<Text color={3} size={2}>门店配送(中包):{distribution.middle_num}</Text>

<Row justify='between' className='gap-2'>
<Column grow className='bg-page r-2 p-3 gap-1'
onClick={() => nav('arecaShop/storeManage/emptyRecovery', { type: 'store' })}
>
<Text color={3} size={2}>已结算空袋(小包)</Text>
<Row items='center' className='gap-1'>
<Text bold size={40}>{distribution.recycle_num}</Text>
<CmsIcon className='text-c3 text-s5' name='direction_right' />
</Row>
</Column>
<Column grow className='bg-page r-2 p-3 gap-1'
onClick={() => nav('arecaShop/storeManage/deliveryList')}
>
<Text color={3} size={2}>门店已退货数(小包)</Text>
<Row items='center' className='gap-1'>
<Text bold size={40}>{distribution.refund_num}</Text>
<CmsIcon className='text-c3 text-s5' name='direction_right' />
</Row>
</Column>
</Row>
</Card>
</ScrollView>
</TopView>

)
}

const DateChild = ({ value, onClick }) => {
return <Row items='center' className='r-2 ph-2 bg-white gap-1' onClick={onClick}>
<Text color={value ? 1 : 3}>{value || '请选择'}</Text>
<ArecaIcon name='rili1' className='text-c3' />
</Row>
}

使用UI库

在上面的示例中已经展示了如何使用UI库的组件,可以看到使用了UI库的组件,在结合全局样式,你会发现,似乎不需要编写scss就能很好的完成页面布局了

这些组件都是duxui UI库提供的

使用List组件完成列表页面

以下面这个列表页面为例,List组件帮你完成了数据请求、自动分页、下拉刷新、自动刷新等功能,你只需要关注你列表中的每一项是如何编写的

import { TopView, Header, Card, Tab, Row, Image, Column, Text, px, Empty, DatePicker, SelectorPicker, ModalForm, Button } from '@/duxui'
import { ArecaIcon, List, usePageData, nav } from '@/arecaShop'
import { useState } from 'react'

export default function DeliveryList() {

const [users] = usePageData('store/salesman')

const [user, setUser] = useState()

const [type, setType] = useState(0)

return (
<TopView>
<Header title='配送记录' />
<Tab className='bg-white' value={type} onChange={setType}>
<Tab.Item title='门店配送' paneKey={0} />
<Tab.Item title='门店退货' paneKey={1} />
</Tab>
<Row className='ph-3 mt-3' items='center' justify='between'>

<ModalForm
title='业务员'
renderForm={<SelectorPicker range={users} nameKey='nickname' valueKey='id' />}
childPropsValueKey='value'
value={user}
onChange={setUser}
>
<SelectChild />
</ModalForm>

</Row>

<List
key={type}
renderItem={Item}
renderEmpty={<Empty title='暂无记录' />}
url={type ? 'store/refund' : 'store/distribution'}
data={{
salesman_id: user
}}
/>
</TopView>
)
}

const SelectChild = ({ value, ...props }) => {

return (
<Row items='center' {...props}>
<Text size={2}>{value || '全部业务员'}</Text>
<ArecaIcon name='arrow_down_fill' className='text-s4 text-c2' />
</Row >
)
}

const Item = ({ item }) => {
return <Card margin disableMarginBottom className='gap-3'>
<Row items='center'>
<ArecaIcon className='text-c1 text-s7' name='store' />
<Text size={2} style={{ marginLeft: px(4) }}>{item.store_name}</Text>
<ArecaIcon className='text-c3 text-s5' name='you2' />
</Row>
{
item.goods.map(good => <Row className='gap-3' key={good.id}>
<Image style={{ width: px(160) }} square className='r-2' src={good.image} />
<Column grow justify='between'>
<Text size={2} bold numberOfLines={2}>{good.title}</Text>
<Text size={2} color={3}>规格:{good.spec}</Text>
<Text size={2} color={3}>数量:{good.num}</Text>
</Column>
</Row>)
}

<Column className='r-2 bg-page p-3 gap-2'>
<Row>
<Row grow items='center'>
<ArecaIcon className='text-secondary text-s7' name='man_mine' />
<Text size={2}>{item.salesman_name || '-'}</Text>
</Row>
<Row grow items='center'>
<ArecaIcon className='text-secondary text-s7' name='shiwu-shouji' />
<Text size={2}>{item.salesman_tel || '-'}</Text>
</Row>
</Row>
<Row>
<Row grow items='center'>
<ArecaIcon className='text-secondary text-s7' name='map' />
<Text size={2}>{item.area}</Text>
</Row>
<Row grow items='center'>
<ArecaIcon className='text-secondary text-s7' name='shijian1' />
<Text size={2}>{item.created_at}</Text>
</Row>
</Row>
</Column>
</Card>
}

使用hook快速请求数据

这里是指的使用useRequest替代request快速获取数据,而不需要在编写具体请求逻辑

import { TopView, Header, Card, Text, ScrollView, Space, HtmlView, useRoute } from '@/duxui'
import { useRequest } from '@/arecaShop'

export default function HelpDetail() {

const { params } = useRoute()

const [detail] = useRequest(`tools/magic/help/${params.id}`)

return (
<TopView isSafe>
<Header title='帮助详情' />
<ScrollView >
<Card shadow={false} margin>
<Space size={32}>
<Text bold size={42} >{detail.title}</Text>
<HtmlView html={detail.content} />
</Space>
</Card>
</ScrollView>
</TopView>
)
}

使用现有模块

这个项目中的 购物订单流程、售后退换、文章发布等功能,在之前的模块中都已经是开发过的功能,并且他们都是成熟可用的,我们只需要直接使用他们就行了

{
"name": "arecaShop",
"description": "大工匠槟榔商城",
"version": "1.0.0",
"dependencies": [
"duxcmsMall",
"amap"
]
}

在我这个项目模块的配置文件中,将 duxcmsMall 添加到依赖中,这是商城模块他提供了商品、订单、售后、购物车、收货地址等功能

因为当前项目需求是积分兑换商品,但是默认的商城不支持积分功能,下面介绍是使用渲染钩子来修改为积分商品

使用渲染钩子对商城进行修改

使用RenderHook,将商城购物流程修改为积分兑换流程,这个文件需要在模块入口文件中导入

import { mallHook, orderHook, Price } from '@/duxcmsMall'
import { Card, contextState, Row, Text } from '@/duxui'

const ListPrice = ({ item, children }) => {
if (item.type === 'point') {
return <Text bold size={4} type='danger'>{item.sell_point}积分</Text>
}
return children
}

mallHook.add('goods.list.item.sellPrice', ListPrice)
mallHook.add('MallCateQuick.item.sellPrice', ListPrice)
mallHook.add('MallList.item.sellPrice', ListPrice)

mallHook.add('detail.info.price', ({ children }) => {

const [data] = contextState.useState()

if (data.type !== 'point') {
return children
}

return <Row className='gap-2' items='center'>
<Text size={40} bold color={4}>
{data.sell_point}积分
</Text>
<Price size={1} delete color={4} className='mt-2'>{data.market_price}</Price>
</Row>
})

mallHook.add('GoodsSpec.price', ({ children, data, item }) => {
if (data.type !== 'point') {
return children
}
return <Text size={48} bold type='danger'>
{item?.sell_point || data.sell_point}积分
</Text>
})

orderHook.add('order.create.data.total', ({ store }) => {

// const isPoint = store.items.some(v => v.type === 'point')

return <>
<Row items='center' justify='between'>
<Text bold>商品金额</Text>
<Price color={2}>{store.total.order_price}</Price>
</Row>
<Row items='center' justify='between'>
<Text bold>运费</Text>
<Price color={2}>{store.total.delivery_price}</Price>
</Row>
{
store.discount.map(item => <Row key={item.name} items='center' justify='between'>
<Text bold>{item.desc}</Text>
<Text type='danger'>{item.price}</Text>
</Row>)
}
{/* {+store.total.discount_price > 0 && <Row items='center' justify='between'>
<Text bold>{isPoint ? '积分抵扣' : '优惠'}</Text>
<Price type='danger'>{-store.total.discount_price}</Price>
</Row>} */}
</>
})

orderHook.add('order.detail.total', () => {
const [{ data }] = contextState.useState()

return <Card margin disableMarginBottom className='gap-3'>
<Row items='center' justify='between'>
<Text color={2}>订单总额</Text>
<Price bold color={1}>{data.order_price}</Price>
</Row>
<Row items='center' justify='between'>
<Text color={2}>运费</Text>
<Price bold color={1}>{data.delivery_price}</Price>
</Row>
{
data.discount_data?.map(item => <Row key={item.name} items='center' justify='between'>
<Text color={2}>{item.desc}</Text>
<Text bold type='danger'>{item.price}</Text>
</Row>)
}
<Row items='center' justify='between'>
<Text color={2}>实付款</Text>
<Price bold type='primary'>{data.pay_price}</Price>
</Row>
</Card>
})

总结

上面提到的就是用于快速开发的主要方法,当你熟练掌握这些方法后,你的开发速度将一骑绝尘

当然这些上面提到这些并不是全部,你可以阅读开发文档,从中获取更多的使用方法