zerl
TypeScript icon, indicating that this package has built-in type declarations

0.4.0 • Public • Published

ZERL 云原生全栈技术框架开发指南

简介

一条来自 MVVM 江湖的传说:

武林至尊,宝刀 React

Vue 不出,谁与争锋。

天下武功,无坚不摧,唯 Solid 不破。

什么是 ZERL ?

  • ZERL 是基于 Solid.js MVVM 框架打造的全栈技术开发框架。

  • ZERL 全面采用 Typescript 进行开发,Typescript 能提供更良好的协作体验,提供实时的代码校验,实现更高质量的交付。

  • ZERL 将更深层次地诠释全栈技术。全栈,一般定义是对工程师技术能力的泛指。是一个组织开发成本控制的有效手段。因此全栈是个人能力的追求,也是组织发展的需要。


ZERL 对全栈实现作出了以下的尝试:

  • 保留了 MVVM 框架的优势,并非只是实现了 SSR
  • 实现了技术栈的统一,而非简单的语言统一或交互标准统一
  • 提供了简便的工程构建和发布部署工具包
  • 构建了云原生开发模式,客户端可直接导入服务端模块并在客户端使用,无需实现 api

开始使用

创建应用

$ npx zerli@latest create zerlapp
或
$ npx zerli@latest -c zerlapp

开发

  • 运行开发环境
$ cd zerlapp
$ npx zerli@latest
或
$ npm run dev

UI 模块开发

规范

UI 模块运行于浏览器上,开发规范及安全性要求须符合 W3C 标准。

ZERL 所有 UI 组件构建于 solid.js 的 MVVM 框架之上,zerl 的所有组件完全兼容 solid.js 所提供的 API。了解 solid.js API 请前往solidjs.com

UI 模块 API
// 导入ZERL API
import { Button } from 'zerl'
// 导入SolidJS API
import { createSignal, createMemo } from 'zerl'

服务模块开发

规范

服务模块运行于服务容器内,负责数据的存储与处理。服务模块使用 Node.JS 为运行时核心,因此可调用 Node.JS 的一切接口。了解 Node.JS API 前往nodejs.org

服务模块由逻辑组件和任务组件构成。

逻辑组件实现与 UI 组件或其他客户端进行数据通信,文件命名规则为:[组件名称].mod.ts。

任务组件在应用启动时触运行,可用于数据初始化、定时任务或启动自定义实例等场景应用,文件命名规则为:[组件名称].job.ts。

服务模块 API
// 导入ZERL API
import { Expose } from 'zerl'

封装

$ npx zerli build@latest
或
$ npm run build
  • 运行 Build 命令后根据运行需求选择相应提示的封装选项即可,封装后把生成的运行文件上传到服务器,直接运行即可。

  • 支持 x64 与 arm64 架构的运行文件封装,不支持交叉编译。生成 x64 运行文件须在 x64cpu 的计算机上进行封装,生成 arm64 运行文件须在 arm64cpu 的计算机上进行封装。


部署

  • Linux 部署环境下实现非阻塞式启动
  1. 使用 vi/vim/nano 或其他编辑工具创建 run.sh 文件
  2. 粘贴以下内容并修改应用程序名称进行保存
  3. 运行./run.sh 即可
log="`date +%Y%m%d%H%M%S`.log"
killall zerlapp*
nohup ./zerlapp-linux > $log 2& > &1 &
  • Docker

应用在封装时,自动检测有否安装 docker,如有安装则会出现 docker 生成镜像的选项,打包后会在本地生成 docker 镜像。


配置

  • 在工程目录(src)内创建工程配置文件(非必要),命名为 application.yml
  • 配置文格式如下:
# app名称
name: your_app_name
# 程序源码目录
root: ./src
# 服务端口
port: 8080
# MySQL数据库连接设置
mysql:
  ### 最大连接数
  connectionLimit: 200
  ### 数据库地址
  host: localhost
  ### 数据库访问端口
  port: 3306
  ### 数据库登录用户名
  user: root
  ### 数据库登录密码
  password: '123456'
  ### 数据库名称
  database: mysql
# Redis连接设置
redis:
  ## 数据库地址
  host: localhost
  ## 数据库访问端口
  port: 6379
  ## 数据库登录密码
  password: '123456'

UI 组件

布局

导入组件

import { Col, Row, Line } from 'zerl'

水平布局组件(Row)

左上角对齐
<Row>
	<div class={styles.block} style={{ height: '50px' }}></div>
	<div class={styles.block} style={{ height: '100px' }}></div>
	<div class={styles.block} style={{ height: '150px' }}></div>
</Row>
左对齐垂直居中
<Row middle class='fill'>
	<div class={styles.block} style={{ height: '50px' }}></div>
	<div class={styles.block} style={{ height: '100px' }}></div>
	<div class={styles.block} style={{ height: '150px' }}></div>
</Row>
左下角对齐
<Row bottom class='fill'>
	<div class={styles.block} style={{ height: '50px' }}></div>
	<div class={styles.block} style={{ height: '100px' }}></div>
	<div class={styles.block} style={{ height: '150px' }}></div>
</Row>
完全居中
<Row center class='fill'>
	<div class={styles.block} style={{ height: '50px' }}></div>
	<div class={styles.block} style={{ height: '100px' }}></div>
	<div class={styles.block} style={{ height: '150px' }}></div>
</Row>
靠顶部水平居中
<Row top center class='fill'>
	<div class={styles.block} style={{ height: '50px' }}></div>
	<div class={styles.block} style={{ height: '100px' }}></div>
	<div class={styles.block} style={{ height: '150px' }}></div>
</Row>
靠底部水平居中
<Row bottom center class='fill'>
	<div class={styles.block} style={{ height: '50px' }}></div>
	<div class={styles.block} style={{ height: '100px' }}></div>
	<div class={styles.block} style={{ height: '150px' }}></div>
</Row>
右上角对齐
<Row right class='fill'>
	<div class={styles.block} style={{ height: '50px' }}></div>
	<div class={styles.block} style={{ height: '100px' }}></div>
	<div class={styles.block} style={{ height: '150px' }}></div>
</Row>
右对齐垂直居中
<Row right middle class='fill'>
	<div class={styles.block} style={{ height: '50px' }}></div>
	<div class={styles.block} style={{ height: '100px' }}></div>
	<div class={styles.block} style={{ height: '150px' }}></div>
</Row>
右下角对齐
<Row right bottom class='fill'>
	<div class={styles.block} style={{ height: '50px' }}></div>
	<div class={styles.block} style={{ height: '100px' }}></div>
	<div class={styles.block} style={{ height: '150px' }}></div>
</Row>
属性说明
名称 属性 描述
middle Boolean 设置布局垂直居中
top Boolean 设置布局顶部对齐
bottom Boolean 设置布局底部对齐
center Boolean 设置布局水平居中
right Boolean 设置布局靠右对齐
class String 设置布局样式模块
style String/CSS Object 设置布局样式

垂直布局组件(Col)

左上角对齐
<Col class='fill' style={{ 'min-height': '300px' }}>
	<div class={styles.block} style={{ width: '50px' }}></div>
	<div class={styles.block} style={{ width: '100px' }}></div>
	<div class={styles.block} style={{ width: '150px' }}></div>
</Col>
靠左居中
<Col middle class='fill' style={{ 'min-height': '300px' }}>
	<div class={styles.block} style={{ width: '50px' }}></div>
	<div class={styles.block} style={{ width: '100px' }}></div>
	<div class={styles.block} style={{ width: '150px' }}></div>
</Col>
左下角对齐
<Col bottom class='fill'>
	<div class={styles.block} style={{ height: '50px' }}></div>
	<div class={styles.block} style={{ height: '100px' }}></div>
	<div class={styles.block} style={{ height: '150px' }}></div>
</Col>
完全居中
<Col center class='fill'>
	<div class={styles.block} style={{ height: '50px' }}></div>
	<div class={styles.block} style={{ height: '100px' }}></div>
	<div class={styles.block} style={{ height: '150px' }}></div>
</Col>
靠顶部水平居中
<Col top center class='fill'>
	<div class={styles.block} style={{ height: '50px' }}></div>
	<div class={styles.block} style={{ height: '100px' }}></div>
	<div class={styles.block} style={{ height: '150px' }}></div>
</Col>
靠底部水平居中
<Col bottom center class='fill'>
	<div class={styles.block} style={{ height: '50px' }}></div>
	<div class={styles.block} style={{ height: '100px' }}></div>
	<div class={styles.block} style={{ height: '150px' }}></div>
</Col>
右上角对齐
<Col right class='fill'>
	<div class={styles.block} style={{ height: '50px' }}></div>
	<div class={styles.block} style={{ height: '100px' }}></div>
	<div class={styles.block} style={{ height: '150px' }}></div>
</Col>
右对齐垂直居中
<Col right middle class='fill'>
	<div class={styles.block} style={{ height: '50px' }}></div>
	<div class={styles.block} style={{ height: '100px' }}></div>
	<div class={styles.block} style={{ height: '150px' }}></div>
</Col>
右下角对齐
<Col right bottom class='fill'>
	<div class={styles.block} style={{ height: '50px' }}></div>
	<div class={styles.block} style={{ height: '100px' }}></div>
	<div class={styles.block} style={{ height: '150px' }}></div>
</Col>
属性说明
名称 属性 描述
middle Boolean 设置布局垂直居中
top Boolean 设置布局顶部对齐
bottom Boolean 设置布局底部对齐
center Boolean 设置布局水平居中
right Boolean 设置布局靠右对齐
class String 设置布局样式模块
style String/CSS对象 设置布局样式

分割线

水平分割线
<Line style={{ width: '400px' }} />
垂直分割线
<Line vertical style={{ height: '50px' }} />
属性说明
名称 属性 描述
vertical Boolean 设置为垂直分割线
class String 设置分割线样式模块
style String/CSS对象 设置分割线样式

路由

导入组件

import { Route, Navigate } from 'zerl'
import Home from './page/home.tsx'

视图加载

同步加载
<div class="main">
  <Route default="/home" routers={import.meta.globEager("./pages/**/*.tsx")}/>
</div>
异步加载
<div class="main">
  <Route default="/home" routers={import.meta.glob("./pages/**/*.tsx")}/>
</div>
自定义加载
<div class="main">
  <Route routers={{"/": Home,"/about": () => lazy(import("./pages/about.tsx"))}}/>
</div>

导航

菜单导航
<Menu width={200} theme="simple">
  <Group name="功能组">
    <Item route="/main">
      功能
    </Item>
  </Group>
</Menu>
脚本导航
Navigate('/main')

属性说明

名称 属性 描述
default String 设置默认路由页面路径,路径为"/"时会跳转到该页面
routers Object 采用import.meta.glob方式导入所有路由页面对象

按钮

导入组件

import { Button } from 'zerl'

普通按钮

<Button>普通按钮</Button>

缩小设置

小号
<Button mini>小号按钮</Button>
超小号
<Button tiny>超小号按钮</Button>

状态设置

成功状态
<Button success>成功状态</Button>
<Button success mini>成功状态</Button>
<Button success tiny>成功状态</Button>
警告状态
<Button warning>警告状态</Button>
<Button warning mini>警告状态</Button>
<Button warning tiny>警告状态</Button>
危险状态
<Button danger>危险状态</Button>
<Button danger mini>危险状态</Button>
<Button danger tiny>危险状态</Button>
信息状态
<Button info>信息状态</Button>
<Button info mini>信息状态</Button>
<Button info tiny>信息状态</Button>
简单状态
<Button simple>简单状态</Button>
<Button simple mini>简单状态</Button>
<Button simple tiny>简单状态</Button>

形状设置

圆角
<Button rounded>圆角按钮</Button>
<Button rounded mini>圆角按钮</Button>
<Button rounded tiny>圆角按钮</Button>
圆形
<Button circle>圆</Button>
<Button circle mini>圆</Button>
<Button circle tiny>圆</Button>
方形
<Button square>方</Button>
<Button square mini>方</Button>
<Button square tiny>方</Button>

明亮模式

<Button lighten>明亮模式</Button>
<Button lighten success>明亮模式</Button>
<Button lighten warning>明亮模式</Button>
<Button lighten danger>明亮模式</Button>
<Button lighten info>明亮模式</Button>

属性说明

分类 名称 属性 描述
缩小 mini Boolean 按钮缩小为小号
缩小 tiny Boolean 按钮缩小为超小号
状态 success Boolean 按钮设置为成功状态
状态 warning Boolean 按钮设置为警告状态
状态 danger Boolean 按钮设置为危险状态
状态 info Boolean 按钮设置为信息状态
状态 simple Boolean 按钮设置为简单状态
形状 rounded Boolean 按钮设置为圆角形状
形状 circle Boolean 按钮设置为圆形
形状 square Boolean 按钮设置为方形
模式 lighten Boolean 按钮设置为明亮模式
样式 style String或CSS对象 设置按钮样式
样式 class String 设置按钮样式模块
事件 onClick Function 按钮点击事件,function(e){...},e为当前按钮点击事件对象

单选

导入组件

import { createSignal, createMemo, Button, Option } from 'zerl'

定义变量

const [getRadioValue1, setRadioValue1] = createSignal(0)
const [getRadioValue2, setRadioValue2] = createSignal(1)
const getRadioValue3 = createMemo(() => 2)

水平排列

<Button name='radio1' type='radio' value={getRadioValue1()} onChange={value => setRadioValue1(value)}>
  <Option name='优秀' value={0} />
  <Option name='良好' value={1} />
  <Option name='一般' value={2} />
  <Option name='' value={3} />
</Button>

垂直排列

<Button name='radio2' vertical type='radio' value={getRadioValue2()} onChange={value => setRadioValue2(value)}>
  <Option name='优秀' value={0} />
  <Option name='良好' value={1} />
  <Option name='一般' value={2} />
  <Option name='' value={3} />
</Button>

只读模式

<Button readonly name='radio3' type='radio' value={getRadioValue3()}>
  <Option name='优秀' value={0} />
  <Option name='良好' value={1} />
  <Option name='一般' value={2} />
  <Option name='' value={3} />
</Button>

属性说明

组件 名称 属性 描述
Button name String 设置单选按钮名称
Button type String 设置按钮类型,type="radio"为单选按钮
Button value Number/String 设置按钮当前选项的值
Button readonly Boolean 把单选按钮设置为只读状态
Button vertical Boolean 把单选按钮设置为纵向排列模式
Button onChange Function 选择内容时触发的事件,function(e){...},e为当前选择项的值信息,类型为Number/String
Option name String 设置单选按钮选项的显示名称
Option value Number/String 设置单选按钮选项的值

多选

导入组件

import { createSignal, createMemo, Button, Option } from 'zerl'

定义变量

const [getCheckValue1, setCheckValue1] = createSignal([0])
const [getCheckValue2, setCheckValue2] = createSignal([1])
const getCheckValue3 = createMemo(() => [0, 2])

水平排列

<Button name='check1' type='check' value={getCheckValue1()} onChange={value => setCheckValue1(value)}>
  <Option name='优秀' value={0} />
  <Option name='良好' value={1} />
  <Option name='一般' value={2} />
  <Option name='' value={3} />
</Button>

垂直排列

<Button name='check2' vertical type='check' value={getCheckValue2()} onChange={value => setCheckValue2(value)}>
  <Option name='优秀' value={0} />
  <Option name='良好' value={1} />
  <Option name='一般' value={2} />
  <Option name='' value={3} />
</Button>

只读模式

<Button readonly name='check3' type='check' value={getCheckValue3()}>
  <Option name='优秀' value={0} />
  <Option name='良好' value={1} />
  <Option name='一般' value={2} />
  <Option name='' value={3} />
</Button>

属性说明

组件 名称 属性 描述
Button name String 设置单选按钮名称
Button type String 设置按钮类型,type="check"为单选按钮
Button value Number[]/String[] 设置按钮当前选项的值
Button readonly Boolean 把多选按钮设置为只读状态
Button vertical Boolean 把多选按钮设置为纵向排列模式
Button onChange Function 选择内容时触发的事件,function(e){...},e为当前选择项的值信息,类型为Number[]/String[]
Option name String 设置多选按钮选项的显示名称
Option value Number/String 设置多选按钮选项的值

提示

导入组件

import { Alert } from 'zerl'

简单的提示

Alert('这是一个提示')

提示样式

Alert('这是一个成功样式的提示', { type: 'success' })

Alert('这是一个警告样式的提示', { type: 'warning' })

Alert('这是一个危险样式的提示', { type: 'danger' })

Alert('这是一个信息样式的提示', { type: 'info' })

提示方向

Alert('这是一个上方弹出的提示', { direction: 'ttb' })

Alert('这是一个下方弹出的提示', { direction: 'btt' })

Alert('这是一个右上方弹出的提示', { direction: 'rttl' })

Alert('这是一个右下方弹出的提示', { direction: 'rbtt' })

自动关闭

Alert('这是一个自动关闭的提示', { delay: 2 })

深色模式

Alert('这是一个深色模式的提示', { mode: 'darken' })

参数说明

名称 属性 描述
message String/JSX.Element 展示的消息内容,可以是文本消息内容/组件消息内容
options 选项参数 设置消息提示选项参数

options 参数说明

名称 属性 描述
type String 消息提示样式,可选值为:default-默认样式,success-成功样式,warning-警告样式,danger-危险样式,info-信息样式,默认为default
mode String 消息显示模式,可选值为:lighten-浅色模式,darken-深色模式,默认为lighten
diretion String 消息弹出方向,可选值为:ttb-上方弹出,btt-下方弹出,rttl-右上方弹出,rbtt-右下方弹出,默认为ttb
delay Number 消息展示时间,单位:秒,不设置时消息需要手动关闭

确认

导入组件

import { Confirm } from 'zerl'

组件使用

async function() {
	const res = await Confirm('确认提醒信息', () => (
		<div>
			<h3>提示信息</h3>
			<span>这是一条提示信息</span>
		</div>
	))
}

参数说明

名称 属性 描述
title String/JSX.Element 提示信息标题
content String/JSX.Element 提示信息内容

返回值说明

名称 属性 描述
result Promise(Boolean) 点击确认返回true,点击取消返回false

输入

导入组件

import { createSignal, createMemo, Text } from 'zerl'

定义变量

const [getInputValue, setInputValue] = createSignal('')
const [getDateValue, setDateValue] = createSignal(new Date())
const [getDateRangeValue, setDateRangeValue] = createSignal([])
const getValue = createMemo(() => 'readonly')

普通输入框

<Text placeholder='这是一个普通的输入框' value={getInputValue1()} onInput={e => setInputValue(e)} />

数字输入

<Text type='digit' placeholder='这是一个只能输入数字的输入框' value={getInputValue()} onInput={e => setInputValue(e)} />

密码输入

<Text type='password' placeholder='这是一个密码输入框'value={getInputValue()} onInput={e => setInputValue(e)}/>

只读模式

<Text readonly value={getValue()} />

多行输入

多行输入框
<Text type='multi' placeholder='这是一个普通的多行输入框' value={getInputValue()} onInput={e => setInputValue(e)}/>
设置行数
<Text type='multi' rows={10} placeholder='这是一个设置高度为10行的多行输入框' value={getInputValue()} onInput={e => setInputValue(e)}/>
富文本编辑器
<Text type='rich' value={getInputValue()} onInput={e => setInputValue(e)} placeholder='这是一个富文本编辑器' style={{ height: '300px' }}/>

日期时间输入

日期输入框
<Text type='date' placeholder='这是一个日期组件' value={getDateValue()} onInput={e => setDateValue(new Date(e))}/>
日期范围输入框
<Text type='daterange' placeholder='这是一个日期范围组件' value={getDateRangeValue()} onInput={([a, b]) => setDateRangeValue([new Date(a), b ? new Date(b) : null])}/>
时间输入框
<Text type='time' placeholder='这是一个时间组件' value={getDateValue()} onInput={e => setDateValue(new Date(e))}/>
日期时间输入框
<Text type='datetime'placeholder='这是一个日期时间组件' value={getDateValue9()} onInput={e => setDateValue(new Date(e))}/>
日期时间范围输入框
<Text type='datetimerange' placeholder='这是一个日期时间范围组件' value={getDateRangeValue()} onInput={([a, b]) => setDateRangeValue([new Date(a), b ? new Date(b) : null])}/>

属性说明

名称 属性 描述
type String 设置输入组件类型。"text":文本输入框;"password":密码输入框;"digit":数字输入框;multi:为多行文本框;"date":日期输入框;"datetime":日期时间输入框;"time":时间输入框;"daterange":日期范围输入框;"datetimerange":日期时间范围输入框;"rich":富文本编辑器。默认为"text"。
name String 输入组件名称
value Number/String 输入组件显示的值。type属性为"date"、"datetime"、"time"时输入日期的文本格式/日期对象;type属性为"daterange"、"datetimerange"时输入日期的文本格式数组/日期对象数组,数组第一个元素为开始日期,第二个元素为结束日期。
readonly Boolean 设置输入组件为只读状态
rows Number type属性为multi时生效,设置输入组件行数高度
all Boolean type属性为rich时生效,设置全功能富文本编辑器
image-width String type属性为rich时生效,设置上传图片压缩的宽度
image-height String type属性为rich时生效,设置上传图片压缩的高度
video-width String type属性为rich时生效,设置上传视频显示的宽度
video-height String type属性为rich时生效,设置上传视频显示的高度
rule required/RegExp/Function 设置为"required"时,该输入组件为必填;设置正则表达式时验证输入组件的输入合法性;验证函数则自定义验证规则,返回true时为验证通过
title String 设置输入组件的标题信息
onInput Function 输入组件输入内容时触发的事件,function(e){...},e为当前触发输入事件的对象。type属性为"date"、"datetime"、"time"时返回日期的文本格式;"daterange"、"datetimerange"时输入日期的文本格式数组,数组第一个元素为开始日期,第二个元素为结束日期。

下拉

导入组件

import { Picker, Option, createSignal } from 'zerl'

定义变量

const [getSingleValue, setSingleValue] = createSignal()
const [getMultiValue, setMultiValue] = createSignal([])

单选框

 <Picker placeholder='这是一个单选下拉组件' value={getSingleValue()} onSelected={e => { setSingleValue(e) }}>
  <Option name='红色' value='Red' />
  <Option name='黄色' value='Yellow' />
  <Option name='蓝色' value='Blue' />
  <Option name='白色' value='White' />
  <Option name='黑色' value='Black' />
</Picker>
只读
<Picker readonly placeholder='这是一个单选下拉组件' value={getSingleValue()} onSelected={e => { setSingleValue(e) }}>
  <Option name='红色' value='Red' />
  <Option name='黄色' value='Yellow' />
  <Option name='蓝色' value='Blue' />
  <Option name='白色' value='White' />
  <Option name='黑色' value='Black' />
</Picker>
搜索
<Picker max={3} placeholder='这是一个单选下拉组件' value={getSingleValue()} onSelected={e => { setSingleValue(e) }}>
  <Option name='红色' value='Red' />
  <Option name='黄色' value='Yellow' />
  <Option name='蓝色' value='Blue' />
  <Option name='白色' value='White' />
  <Option name='黑色' value='Black' />
</Picker>

多选框

<Picker multi placeholder='这是一个多选下拉组件' value={getMultiValue2()} onSelected={e => {setMultiValue2(e) }}>
  <Option name='红色' value='Red' />
  <Option name='黄色' value='Yellow' />
  <Option name='蓝色' value='Blue' />
  <Option name='白色' value='White' />
  <Option name='黑色' value='Black' />
  <Option name='青色' value='Cyan' />
  <Option name='紫色' value='Purple' />
</Picker>
只读
<Picker multi readonly placeholder='这是一个多选下拉组件' value={getMultiValue2()} onSelected={e => {setMultiValue2(e) }}>
  <Option name='红色' value='Red' />
  <Option name='黄色' value='Yellow' />
  <Option name='蓝色' value='Blue' />
  <Option name='白色' value='White' />
  <Option name='黑色' value='Black' />
  <Option name='青色' value='Cyan' />
  <Option name='紫色' value='Purple' />
</Picker>
搜索
<Picker multi max={3} placeholder='这是一个多选下拉组件' value={getMultiValue2()} onSelected={e => {setMultiValue2(e) }}>
  <Option name='红色' value='Red' />
  <Option name='黄色' value='Yellow' />
  <Option name='蓝色' value='Blue' />
  <Option name='白色' value='White' />
  <Option name='黑色' value='Black' />
  <Option name='青色' value='Cyan' />
  <Option name='紫色' value='Purple' />
</Picker>

属性说明

组件 名称 属性 描述
Picker multi Boolean 设置选择类型,multi=true为多选
Picker value String 输入组件名称
Picker value Number/String/Number[]/String[] 设置选择组件当前选项的值,multi=false时,传入Number/String的值;multi=true时传入Number[]/String[]
Picker readonly Boolean 设置选择组件为只读状态
Picker max Number 设置选项显示的项目数量,设置max的值时选项出现筛选功能。
Picker onSelected Function 选择内容时触发的事件,function(e){...},multi=false时,e为当前选择项的Number/String的值;multi=true时,e为当前选择项的Number[]/String[]。
Option name String 设置选项的显示名称
Option value Number/String 设置选项的值

表格

导入组件

import { createSignal, createMemo, Table, Meta } from 'zerl'

数据定义

const data = [
	{
		module: '用户管理',
		name: '用户信息',
		type: 'ILF',
		fp: 10,
		degree: '高'
	},
	{
		module: '用户管理',
		name: '添加、修改用户信息',
		type: 'EI',
		fp: 4,
		degree: '高'
	},
	{
		module: '用户管理',
		name: '删除用户信息',
		type: 'EI',
		fp: 4,
		degree: '高'
	},
	{
		module: '用户管理',
		name: '用户信息列表、条件筛选',
		type: 'EQ',
		fp: 4,
		degree: '高'
	},
	{
		module: '用户管理',
		name: '用户详细信息',
		type: 'EQ',
		fp: 4,
		degree: '高'
	}
]

基本表格

<Table dataset={data}>
  <Meta name='模块' key='module' />
  <Meta name='功能点名称' key='name' />
  <Meta name='功能点类型' key='type' />
  <Meta name='UFP' key='fp' />
  <Meta name='重用度' key='degree' />
</Table>

列属性设置

<Table dataset={data} width='700'>
  <Meta name='模块' key='module' width='100' align='center' />
  <Meta name='功能点名称' key='name' align='center' />
  <Meta name='功能点类型' key='type' width='100' align='center' />
  <Meta name='UFP' key='fp' width='100' align='center' />
  <Meta name='重用度' key='degree' width='100' align='center' />
</Table>

定义单元格数据

<Table dataset={data}>
	<Meta name='序号' width='50' align='center'>
		{(_, index) => index + 1}
	</Meta>
	<Meta name='模块' key='module' width='100' align='center' />
	<Meta name='功能点名称' key='name' align='center' />
	<Meta name='功能点类型' key='type' width='100' align='center' />
	<Meta name='UFP' key='fp' width='100' align='center' />
	<Meta name='重用度' key='degree' width='100' align='center' />
	<Meta name='费用(元)' width='120' align='center'>
		{(item) => {
			return (
				(20000 *
					item.fp *
					1.21 *
					(item.degree === '高' ? 1 / 3 : item.degree === '中' ? 2 / 3 : 1) *
					7.19) /
				174
			).toFixed(2)
		}}
	</Meta>
</Table>

紧凑模式

<Table dataset={data} slim>
  <Meta name='模块' key='module' width='100' align='center' />
  <Meta name='功能点名称' key='name' align='center' />
  <Meta name='功能点类型' key='type' width='100' align='center' />
  <Meta name='UFP' key='fp' width='100' align='center' />
  <Meta name='重用度' key='degree' width='100' align='center' />
</Table>

主题

深色
<Table dataset={data} theme='darken'>
  <Meta name='模块' key='module' width='100' align='center' />
  <Meta name='功能点名称' key='name' align='center' />
  <Meta name='功能点类型' key='type' width='100' align='center' />
  <Meta name='UFP' key='fp' width='100' align='center' />
  <Meta name='重用度' key='degree' width='100' align='center' />
</Table>
浅色
<Table dataset={data} theme='lighten'>
  <Meta name='模块' key='module' width='100' align='center' />
  <Meta name='功能点名称' key='name' align='center' />
  <Meta name='功能点类型' key='type' width='100' align='center' />
  <Meta name='UFP' key='fp' width='100' align='center' />
  <Meta name='重用度' key='degree' width='100' align='center' />
</Table>

属性说明

组件 名称 属性 描述
Table dataset Object[] 表格内呈现的数据集
Table width String/Number 表格宽度
Table slim Boolean 设置表格为紧凑模式
Table theme String 设置表格主题模式。现代主题:theme="modern";浅色主题:theme="lighten";深色主题:theme="darken"
Meta name String 列显示名称
Meta key String 映射数据键值的名称
Meta align String 单元格对齐模式设置。left:单元格左对齐;right:单元格右对齐;center:单元格居中对齐
Meta width String/Number 固定列宽设置,不设置为自适应列宽

分页

导入组件

import { Pagi, PagiHandler } from 'zerl'

组件使用

let pagi: PagiHandler

const pageChange = (page) => dataloader(page)

const setLength = () => pagi.setLength
<Pagi ref={ (handler::PagiHandler) => pagi = handler} onClick={ (page:Number) => pageChange(page)}/>

属性说明

组件 名称 属性 描述
Pagi ref Function 获取分页组件句柄,function(hanler:PagiHandler){...}
Pagi page String/Number 设置默认页码,默认值:1
Pagi size String/Number 设置每页记录数,默认值:10
Pagi length String/Number 设置设置记录总数
Pagi count String/Number 设置分页器显示按钮数量,默认值:5
Pagi onClick Function 分页器点击按钮时触发的事件,参数为相对应的页码,function(page: Number){...}
PagiHandler setPage Function 设置页码,setPage(page: number)
PagiHandler getPage Function 获取当前页码
PagiHandler setSize Function 设置每页记录条数,setSize(size: number)
PagiHandler getSize Function 获取当前记录条数
PagiHandler setLength Function 设置记录数,setLength(length: number)
PagiHandler getLength Function 获取当前设置的记录数
PagiHandler setCount Function 设置显示页码按钮数,setCount(count: number)
PagiHandler getCount Function 获取当前显示页码按钮数

对话框

导入组件

import { Dialog } from 'zerl'

数据定义

const title = () => (
	<div>
		这里设置<span style={{ color: '#857' }}>标题</span>
	</div>
)
const content = () => <div>这里设置对话框内容</div>

普通对话框

const dialog = Dialog({ content })
dialog.open() //打开对话框

设置对话框标题

const dialog = Dialog({ title, content })
dialog.open() //打开对话框

设置对话框尺寸

const dialog = Dialog({ title, content, width: 500, height: 300 })
dialog.open() //打开对话框

设置全屏对话框

const dialog = Dialog({ title, content, fullscreen: true })
dialog.open() //打开对话框

设置对话框背景滤镜

const dialog = Dialog({ title, content, filter: 30 })
dialog.open() //打开对话框

设置抽屉

从左往右打开
const dialog = Dialog({ title, content, direction: 'ltr' })
dialog.open() //打开对话框
从上向下打开
const dialog = Dialog({ title, content, direction: 'ttb' })
dialog.open() //打开对话框
从右向左打开
const dialog = Dialog({ title, content, direction: 'rtl' })
dialog.open() //打开对话框
从下往上打开
const dialog = Dialog({ title, content, direction: 'btt' })
dialog.open() //打开对话框
关闭对话框
dialog.close() //关闭对话框

属性说明

名称 属性 描述
title JSXElement 对话框标题内容。
content JSXElement 对话框内容。
width String/Number 设置对话框宽度。设置fullscreen时该属性无效
height JSXElement 设置对话框高度。设置fullscreen时该属性无效
filter Number 设置对话框对背景的滤镜效果,取值为[1-100]。设置fullscreen时该属性无效
fullscreen Boolean 设置对话框全屏状态
direction String 设置对话框高度。设置fullscreen时该属性无效
filter Number 设置抽屉模式,ttb:从上到下打开;ltr:从左到右打开;rtl:从右到左打开;btt:从下到上打开。

菜单

导入组件

import { Menu, Group, Item } from 'zerl'

基本菜单

<Menu width={200}>
  <Group name='功能组一'>
    <Item>功能一</Item>
    <Item>功能二</Item>
  </Group>
  <Group name='功能组二' expanded>
    <Item>功能三</Item>
    <Item>功能四</Item>
  </Group>
</Menu>

横向排列

<Menu width={400} direction='horizontal'>
  <Group name='功能组一'>
    <Item>功能一</Item>
    <Item>功能二</Item>
  </Group>
  <Group name='功能组二' expanded>
    <Item>功能三</Item>
    <Item>功能四</Item>
  </Group>
</Menu>

主题

深色
<Menu width={200} theme='darken'>
  <Group name='功能组一'>
    <Item>功能一</Item>
    <Item>功能二</Item>
  </Group>
  <Group name='功能组二' expanded>
    <Item>功能三</Item>
    <Item>功能四</Item>
  </Group>
</Menu>
简单
<Menu width={200} theme='simple'>
  <Group name='功能组一'>
    <Item>功能一</Item>
    <Item>功能二</Item>
  </Group>
  <Group name='功能组二' expanded>
    <Item>功能三</Item>
    <Item>功能四</Item>
  </Group>
</Menu>

属性说明

组件 名称 属性 描述
Menu width String/Number 设置菜单固定宽度。默认宽度自动填充。
Menu height String/Number 设置菜单固定高度。默认高度自动填充。
Menu top Boolean 设置菜单是否置顶显示。
Menu direction Boolean 设置菜单扩展方向。direction="vertical":菜单垂直扩展;direction="horizontal":菜单水平扩展。默认为"vertical"
Menu theme String 设置菜单主题模式。theme="darken":菜单设置为深色模式;theme="lighten":菜单设置为浅色模式;theme="simple":菜单设置为简单主题模式。默认为"lighten"
Group name String 设置菜单分组名称。
Group expanded Boolean 设置菜单分组时候是否展开。direction="vertical"时有效。
Item onClick Function 菜单项点击时触发的事件,function(e){...},e为当前触发点击事件的对象。
Item select Boolean 设置是否选中
Item route String 设置点击跳转的路由,设置此属性时onClick失效
Item icon String 设置菜单图标

导入组件

import { Tree } from 'zerl'

初始化数据

let treeHandler: TreeHandler
const data = [
	{
		id: '1',
		title: '新节点1',
		children: [
			{ id: '4', title: '新节点1.1' },
			{ id: '5', title: '新节点1.2' }
		]
	},
	{ id: '2', title: '新节点2' },
	{ id: '3', title: '新节点2' }
]

组件的使用

<Tree title='Root Name' callback={(el:TreeHandler) => treeHandler = el} dataset={data}
/>

属性说明

组件 名称 属性 描述
Tree title String 树标题,根节点标题名称
Tree callback Function 返回节点控制句柄,function(h:TreeHandler){...}
Tree dataset NodeData[] 初始化树数据,非必要项
TreeHandler eventName String 获取树回调事件名称,'onLoad' | 'nodeClick' | 'expanded' | 'unexpanded'
TreeHandler getCurrentID String 获取当前选择节点的ID
TreeHandler appendChild Function 在当前节点下加载新的子节点,function(id:string, title:string){...}
TreeHandler appendChildren Function 在当前节点下加载所有子节点,function(node: {id:string, title:string}[]){...}
TreeHandler rename Function 修改当前节点的标题内容,function(title:string){...}
TreeHandler remove Function 删除当前选择节点,function(){...}
NodeData id String 树节点ID
NodeData title String 树节点标题
NodeData children NodeData[] 子节点数据

页签

导入组件

import { Tabs, Tab } from 'zerl'

简单的页签

<Tabs>
  <Tab name='tab1' label='页签一'>
    <h1>这是页签一</h1>
  </Tab>
  <Tab name='tab2' label='页签二'>
    <h1>这是页签二</h1>
  </Tab>
  <Tab name='tab3' label='页签三'>
    <h1>这是页签三</h1>
  </Tab>
</Tabs>

可关闭页签

<Tabs>
  <Tab name='tab1' label='页签一' closable>
    <h1>这是页签一</h1>
  </Tab>
  <Tab name='tab2' label='页签二'>
    <h1>这是页签二</h1>
  </Tab>
  <Tab name='tab3' label='页签三' closable>
    <h1>这是页签三</h1>
  </Tab>
</Tabs>

动态页签

const [tabs, setTabs] = createSignal()
const tabHandler = () => {
	tabs().create({
		name: 'tab1',
		label: '动态页签',
		closable: true,
		children: <h1>这是新页签,新添加的</h1>
	})
}
<Tabs ref={el => setTabs(el)}>

属性说明

组件 名称 属性 描述
Tabs ref Function 组件属性,通过方法获取Tabs句柄,function(tabs: Tabs){...}
Tabs create Function 组件方法,动态创建页签,类型:方法;参数:name-页签名称,唯一标识,label-页签显示名称,closable-页签是否可以关闭,children-页签内显示内容。
Tabs getElement Function 组件方法,返回页签HTML对象
Tab name String 页签名称,唯一标识
Tab label String/JSXElement 页签显示名称
Tab closable Boolean 页签是否可以关闭

轮播

导入组件

import { Row, Swiper, Slide } from 'zerl'

简单的轮播组件

<Swiper>
  <Slide>
    <Row class='fill' center>
      <h1>Slide A</h1>
    </Row>
  </Slide>
  <Slide>
    <Row class='fill' center>
      <h1>Slide B</h1>
    </Row>
  </Slide>
</Swiper>

带导航的轮播组件

<Swiper nav>
  <Slide>
    <Row class='fill' center>
      <h1>Slide A</h1>
    </Row>
  </Slide>
  <Slide>
    <Row class='fill' center>
      <h1>Slide B</h1>
    </Row>
  </Slide>
</Swiper>

带分页的轮播组件

普通分页
<Swiper pagi>
  <Slide>
    <Row class='fill' center>
      <h1>Slide A</h1>
    </Row>
  </Slide>
  <Slide>
    <Row class='fill' center>
      <h1>Slide B</h1>
    </Row>
  </Slide>
</Swiper>
数字分页
<Swiper pagi='fraction'>
  <Slide>
    <Row class='fill' center>
      <h1>Slide A</h1>
    </Row>
  </Slide>
  <Slide>
    <Row class='fill' center>
      <h1>Slide B</h1>
    </Row>
  </Slide>
</Swiper>
进度条分页
<Swiper pagi='progressbar'>
  <Slide>
    <Row class='fill' center>
      <h1>Slide A</h1>
    </Row>
  </Slide>
  <Slide>
    <Row class='fill' center>
      <h1>Slide B</h1>
    </Row>
  </Slide>
</Swiper>
自定义分页
<Swiper pagi={(index, className) => (
    <div class={`${className} ${styles.bullets}`}>{index + 1}</div>
  )}
>
  <Slide>
    <Row class='fill' center>
      <h1>Slide A</h1>
    </Row>
  </Slide>
  <Slide>
    <Row class='fill' center>
      <h1>Slide B</h1>
    </Row>
  </Slide>
</Swiper>

纵向轮播

<Swiper pagi vertical>
  <Slide>
    <Row class='fill' center>
      <h1>Slide A</h1>
    </Row>
  </Slide>
  <Slide>
    <Row class='fill' center>
      <h1>Slide B</h1>
    </Row>
  </Slide>
</Swiper>

自动轮播

<Swiper pagi autoplay='5'>
  <Slide>
    <Row class='fill' center>
      <h1>Slide A</h1>
    </Row>
  </Slide>
  <Slide>
    <Row class='fill' center>
      <h1>Slide B</h1>
    </Row>
  </Slide>
</Swiper>

循环轮播

<Swiper pagi loop nav>
  <Slide>
    <Row class='fill' center>
      <h1>Slide A</h1>
    </Row>
  </Slide>
  <Slide>
    <Row class='fill' center>
      <h1>Slide B</h1>
    </Row>
  </Slide>
</Swiper>

属性说明

组件 名称 属性 描述
Swiper class String 设置轮播组件样式模块
Swiper style String/CSS对象 设置轮播组件样式
Swiper nav Boolean 设置导航按钮
Swiper loop Boolean 设置循环播放
Swiper autoplay Boolean/Number/String 设置自动播放,设置数值/字符时,设置停留时间,单位(秒)
Swiper vertical Boolean 设置为纵向轮播模式
Swiper pagi Boolean/String/Function Boolean类型时,显示分页小圆点。String类型时,progressbar-用进度条样式显示分页,fraction-用数字样式显示分页。Function类型时,自定义样式-function(index,className){...},index-页码,className-基础样式模块名称。
Slide class String 设置轮播组件样式模块
Slide style String/CSS对象 设置轮播组件样式

表单

表单的基本结构

获取表单句柄

const [getForm, setForm] = createSignal()
<form action='form.simple' ref={el => setForm1(el)}>
...
</form>

action 指向处理数据的模块,格式:[模块名称].[方法名称]

表单的验证

<form action='form.simple' ref={el => setForm2(el)}>
  <Col>
    <Row class='fill' middle>
      <Text
        title='标题'
        name='title'
        rule={val => val.length > 4}
        placeholder='请输入信息标题'
        hint='至少输入不少于5个字符'
      />
      <Button
        type='radio'
        title='类型'
        name='category'
        rule='required'
        hint='需要选择类型'
      >
        <Option name='内部信息' value='inner' />
        <Option name='保密信息' value='secret' />
        <Option name='对外信息' value='external' />
      </Button>
    </Row>
    <Row class='fill' middle>
      <Text
        type='date'
        title='显示日期'
        name='displayDate'
        rule='required'
        placeholder='请输入显示日期'
        hint='显示日期不能为空'
      />
      <Button
        type='check'
        title='标签'
        name='label'
        rule={val => val.length > 2}
        hint='至少选择三项'
      >
        <Option name='文娱' value='culture' />
        <Option name='科技' value='tech' />
        <Option name='教育' value='edu' />
        <Option name='健康' value='health' />
        <Option name='时事' value='news' />
      </Button>
    </Row>
    <Row class='fill' middle>
      <Text type='multi' title='内容' name='content' />
    </Row>
    <Row class='fill' right>
      <Button
        onClick={async () => {
          const result = await form2().submit()
          if (result) {
            Confirm(
              '表单提交信息',
              <div>
                {Object.keys(result).map(key => {
                  return (
                    <Row>
                      <div
                        style={{
                          width: '120px',
                          'font-weight': 'bold'
                        }}
                      >
                        {key}
                      </div>
                      {JSON.stringify(result[key])}
                    </Row>
                  )
                })}
              </div>
            )
          }
        }}
      >
        保存
      </Button>
    </Row>
  </Col>
</form>

文件

导入组件

import { FileList, File } from 'zerl'

简单的文件组件

<FileList />

文件多选

<FileList multiple/>

初始化文件列表

<FileList multiple>
  <File id='2fed96c8130e804e8c6f940981370fb7'></File>
  <File id='096f60ff49ec8b3039902e190c26ee10'></File>
</FileList>

文件上传

let fileList
<FileList ref={el=>fileList = el}/>
<Button onClick={async ()=>{const fileIds = await fileList.upload()}}/>

属性说明

组件 名称 属性 描述
FileList ref Function 属性,获取文件对象句柄,function(el: FileList){...}
FileList upload Function 文件上传函数,上传成功后返回文件ID
FileList files Function 返回组件中的文件信息
FileList accept String 设置筛选可选择文件的类型
FileList multiple Boolean 设置是否可以上传多个文件
FileList title String 设置组件的标题
File id String 文件指针(ID),该ID在文件上传后返回

服务组件

逻辑

组件功能

实现服务端数据处理的逻辑组件,用于与浏览器或其他客户端进行数据交互

模块文件

在模块文件头部添加 #!module 标志

组件使用

组件定义
#!module
import type { Context } from 'zerl'
export default class {
	public async save(data: Schema) {
		return data
	}
	public async load(data: queryData, ctx: Context) {
		return [data]
	}
}
UI 模块调用
import { Button } from 'zerl'
import Service from './Service'
const service = new Service()
<Button onClick = {async ()=> {
  const remoteData = await service.save({data: ‘test data’})
  }
}>
  get remote data
</Button>

远程组件调用

// 普通远程组件调用
import Service from 'rpc://192.168.1.10:8080/Service'
// ssl远程组件调用
import Service from 'rpcs://192.168.1.11:8080?Service'

任务

组件功能

应用启动时一并启动的工作任务线程,可设置一次性运行任务与周期性运行任务

任务文件

在任务文件头部添加 #!job 标志

组件使用

#!job
import { Once, Timer } from "zerl"
import type { Context, ServerInstance } from "zerl"
export default class {
  // 声明后自动注入当前启动的服务实例
  private getCurrentInstance!: ServerInstance
  @Once()
  public init(){
    // 获取服务实例
    const server = this.getCurrentInstance()
    // 添加新的服务中间件
    server.use(async (ctx: Context, next:any) => {
      if(/^\/api/?/.test(ctx.url)){
        ctx.body = "append new middleware"
      } else {
        await next()
      }
    })
  }

  @Timer(60)
  public synchronize(){
    ...
  }
}

访问控制

组件功能

服务模块的会根据客户端发起的请求进行访问权限验证,使用@Expose 装饰器则不验证访问权限

授权与撤销权限

#!module
import { Expose, Grent, Revoke } from 'zerl'
import type { Context } from 'zerl'
export default class {
	// 访问时,不验证UI端访问权限
	@Expose public async login(data, ctx: Context) {
		Grent(ctx) //授权访问权限
	}

	// 只有授权后,UI端才能访问此方法
	public async logout(data: ant, ctx: Context) {
		Revoke(ctx) //回收访问权限
	}
}

MySQL

组件使用

#!module
import { MySQL, Schema } from 'zerl'
@Schema('TABLE', 'ID')
export default class extends MySQL {
	// 数据插入
	public async insertData(data: any) {
		this.insert(data)
		return { result: 'ok' }
	}
	// 数据更新
	public async updateData(data: any) {
		this.update(data)
		return { result: 'ok' }
	}

	// 数据删除
	public async removeData(data: any) {
		this.remove({ ID: data.id })
		return { result: 'ok' }
	}

	// 数据查询
	public async queryData(data: any) {
		return await this.query('SELECT * FROM TABLE WHERE ID=?', [data.id])
	}

	// 事务处理
	public async transaction(data: any) {
		const flow = this.flow()
		flow.insert(data)
		flow.update({ name: 'new name' })
		flow.pipe('DELETE FROM TABLE WHERE ID = 3')
		await flow.exec()
		return { result: 'ok' }
	}
}

SQL 文件

命名

[文件名].sql

文件格式
--table.sql
--define query

SELECT * FROM TABLE;

每个 sql 语句结束后必须加“;”,sql 语句头部需要定义语句的变量名,声明格式:--define xxx

调用 SQL 语句
this.query('sql:table.query')

Redis

组件使用

#!module
import { Redis } from "zerl"
// 数据模型
export default class{
  public async load(data: any){
    return await Redis(async (client) => {
      const { keys } = await
      client.scan(0, { MATCH: "*,name,*" })
      return client.hGetAll(keys[0])
    }
  }
}

操作命令参考Redis 命令文档


装饰器

MySQL 控制表定义

@Schema([表名],[主键])
// 设置表定义后,所有MySQL操作都默认使用设置的表名和主键名
#!module
@Schema('TABLE', 'ID')
export default class extends MySQL {
  ...
}

访问暴露(逻辑组件内使用)

// 设置访问暴露装饰器后,该方法访问对外完全公开,不受权限控制影响。
@Expose public async login(data: any){
  ...
}

一次性执行方法(任务组件内使用)

// 应用程序启动时立即启动
@Once() public runAtStart(){
  ...
}
// 应用程序启动时,延迟5秒启动
@Once(5) public runAtStart(){
  ...
}

定时执行方法(任务组件内使用)

// 应用程序启动时,触发启动定时器,方法每5秒执行一次
@Timer(5) public tikTok(){
  ...
}

扩展类型

日期+

格式化日期
// fmt: 日期格式 y-年,M-月,d-日,h-小时,m-分钟,s-秒,q-季度,S-毫秒
Date.format(fmt: string): string
日期推算
//	year 当前日期累加年数
//  month 当前日期累加月数
//	day 当前日期累加日数
Date.calc(year: number, month: number, day: number): Date
输出日期文本格式 yyyy-MM-dd
//	year 当前日期累加年数
//  month 当前日期累加月数
//	day 当前日期累加日数
Date.date(year?: number, month?: number, day?: number): string
输出时间文本格式 hh:mm:ss
Date.time(): string
输出日期时间文本格式 yyyy-MM-dd hh:mm:ss
//	year 当前日期累加年数
//  month 当前日期累加月数
//	day 当前日期累加日数
Date.datetime(year?: number, month?: number, day?: number): string
月份数量计算
// reference 比较日期 yyyy-MM-dd hh:mm:ss
// 返回相隔月份数量
Date.monthdiff(reference: string | Date): number
时间戳
Date.timestamp(): number

字符串+

驼峰格式转连接符格式
// separator: 连接字符
String.camelfalt(separator: string): string
定长前补零
// digit: 字符串总长度
String.prefix(digit:number): string
Base64 字符串转 Blob 类型
String.blob(): Blob
字符串加密(服务组件内使用)
String.encode(): string
字符串解密(服务组件内使用)
String.decode(): string
生成 MD5 摘要信息
String.md5(): string

数字+

千分位格式
Number.thou(): string

数组+

数组分组
// algorithm: 分组函数 function(element: any): string, element为数组中的每个元素的值,返回值为分组名称
Array.group(algorithm: Function): object

Package Sidebar

Install

npm i zerl

Weekly Downloads

20

Version

0.4.0

License

ISC

Unpacked Size

1.11 MB

Total Files

25

Last publish

Collaborators

  • mickfoely