This commit is contained in:
yumoqing 2025-12-11 21:59:36 +08:00
parent c16d9fd10b
commit 32dbcd0a1f
5 changed files with 1425 additions and 3 deletions

View File

@ -1,4 +1,3 @@
"""
dspy脚本可用的变量
ahserver/globalEnv.py程序中推送到ServerEnv中的变量
各个模块init_mymodule函数推送的变量
@ -25,8 +24,8 @@ async with db.sqlorContext('sage') as sor:
ns = DictObject(**ns)
x = await sms_engine.send_validate_code(params_kw.cell_no, ns.code)
info(f'{params_kw.cell_no}, {ns.code=}, send_validatecode() return {x}')
return PopMessage(title='SMS', message=f'validate code send to {params_kw.cell_no}')
return UiMessage(title='SMS', message=f'validate code send to {params_kw.cell_no}')
return PopError(title='Error', message=f'{params_kw.cell_no} generate validate code error')
return UiError(title='Error', message=f'{params_kw.cell_no} generate validate code error')
```

View File

@ -1,3 +1,5 @@
## ui文件例子
```
{
"widgettype":"VBox",
"options":{
@ -73,3 +75,4 @@
}
]
}
```

708
kdb/rule.md Normal file
View File

@ -0,0 +1,708 @@
## 模块目录结构
模块名称为“mymodule", 以下为mymodule模块的目录文件结构
```
mymodule
+--mymodule # 存放模块的主要逻辑py代码
+--init.py # 模块初始化脚本需要定义一个load_mymodule()函数此函数将在ui和dspy文件中用到的模块变量通过ServerEnv实例传过去
+--__init__.py #python模块所需
|
| +--其他源码.py # 模块中需要的其他py源码文件
+--wwwroot # 目录,存放以.ui和.dspy结束的文件ui文件支持jinja2模板前端控件文件内容为json格式的控件描述文本 dspy是ahserver支持的受限python脚本可以按照需要设置下级目录
+--models # 存放数据表文件的目录
+--json # 存放CRUD描述文件的目录
+--pyproject.toml # pip打包文件, 其中项目名字与mymodule相同
+--README.md # 模块自说明文件
+--init # 模块初始化目录
+--data.xlsx # 初始化数据一个sheet一个表一行一个记录第一行为字>段名
+--script.py # 初始化脚本
```
## 要求
* 模块中的主要逻辑用py源码实现放在mymodule目录下
* 客户端业务功能放在wwwroot目录下以.ui和.dspy后缀结束用.ui文件遵守jinja2规范, 而.dspy文件是一个受控的脚本
* .dspy中不允许import模块
* 假设模块名为”mymodule“
## mymodule目录
“mymodule”为模块名称 模块目录下存放以py结束的代码文件其中init.py文件为必须
在此目录下的代码中可以通过ahserver.serverenv模块的ServerEnv来引用其他模块的放进来的变量
### init.py的主要内容
```
from ahserver.serverenv import ServerEnv
from appPublic.worker import awaitify
from .x import a, b, c # 从同目录下的源码x.py中import ab, c是协程
from .y import x, y # 从同目录下源码y.py中import x, y是普通函数
def load_mymodule(): # mymodule需替换为实际的模块名字
env = ServerEnv()
env.a = a # 脚本中用"a" 调用"a"
env.b = b # 脚本中用"b"调用"b"
env.cc = c # 脚本中用"cc"调用"c"
env.y = awaitify(y) # 将函数包装为协程
env.x = awaitify(x) # 将函数包装为协程
```
注释这里的load_mymodule中的mymodule是模块名字而不是“mymodule”本身
模块的其他代码都需要放在这个目录中而模块中所有需要在uidspy脚本中用到的变量均需要通过init.py函数的load_mymodule()函数传递
## ui, dspy可以直接使用的变量
### request
aiohttp.Request实例每个客户端请求有一个独立的Request实例
request._run_ns可以获得所有在.ui和.dspy源码中可以使用的变量通过ahserver.ServerEnv 传递
### params_kw
DictObjectdict子类, 支持a.b方式获取属性实例接收到客户端传来的数据如果有文件>文件都保存在服务器指定的位置params_kw中属性名保存的是其相对路径可用FileStorage().realPath(params_kw.myfile)来获得文件在服务器中的实际路径。
### get_user()
来自rbac模块协程函数获得当前登录用户如果用户没有登录返回None
### get_userorgid()
来自rbac模块协程函数获得当前登录用户的机构id如果用户没有登录返回None
### 各个模块通过load_xxxx()放到ServerEnv()中的变量
XXXX是模块名称
## pyproject.toml和README.md
编写pyproject.toml文件和README.md文件
## 模块中使用的编码
模块中如果用到编码编码需保存在appbase模块的appcodes表和appcodes_kv两个表中
appcodes(编码表)有如下字段:
[
"id" # str 32 主键,可设置为数据表字段名称
"name" # 编码名称
"hierarchy_flg", # str 1, '0':单级编码“1”:多级编码
]
appcodes_kv编码键值表字段如下
[
"id", # str, 32,主键,唯一值
"parentid" # str, 32, 一级编码为appcodes表的id, 否则为上级编码键值记录的id
“k" # str 32, 编码值
“v” “ str 255,编码显示文本
]
## 数据库表定义规范
模块中需要定义数据表要遵守一下规范
数据库表用一个json格式文件或数据来定义具体规范如下
{
"summary":[ # 仅一条记录
{
"name" # 表名
"title" # 表标题
"primary" # 主键=“id”, 所有表均以id为主键
"catelog" # 可选项entity relation dimession indication
}
],
"fields":[ # 字段
{
"name"
"title"
"type" #可选项有str char short long llong date time timestamp float double ddouble decimal text
"length"
"dec"
"nullable" # 可选项yes no
"default"
"comments" # 注释
}
]
"indexes":[
{
"name" # 每个索引一个idxname不能重复一个表中
"idxtype" # unique or index
"idxfields" # 字段名或字段名数组[f1,f1]
}
]
“codes":[ # 如果一个字段数据可以从其他表中获得,可以通过下面的模式定义选择输入逻
{
"field" # 字段的名字
"table" # 数据来源表
"valuefield" # 数据来源表值字段
"textfield" # 数据来源表显示字段
"cond" # 检索条件
}
]
]
说明:
id字段全部使用str 32类型
字典中的length如果type是str char float double ddouble decimal中的一个则必为>0的>数字
字典中的dec: 如果type是float double ddouble decimal中的一个则必须是>0的数字
## 列表形式的数据表crud定义
当一张数据表不是树形结构的就是表形结构的crud
表形机构表的crud的json说明
{
"tblname" # 表名
"alias" # 别名当需多个crud界面时可用alias来生成份不同的功能
"title" # 标题,如果不给定,使用数据表定义中的表标题
"params":{ # crud 的参数
"sortby" # 指定排序字段,可以多个字段,用["a desc", "b"]>形式给出按照多个字段排序,"desc“表示倒排不给定就是正排
"logined_userorgid" # 可选如果表中有机构编码id并且需要按照机构过滤
"logined_userid" # 可选如果表中有用户id并且需要登录用户过滤
"confidential_fields" # 可选,敏感字段名数组,如果表中有敏感字段,要填写
"editor":{ # 给定编辑时form初始化参数
# 需要参考bricks的Form控件
"binds":[ # 比如添加事件处理
{
"wid":"province_id", # 指定form的事件名
"event":"changed", # 事件,比如数据变化
"actiontype":"script", # 事件处理类型
"target":"city_id", # 目标控件
"script": # 脚本内容规定用js脚本
}
]
},
"browserfields": { # 列表显示的参数
"exclouded": ["id"], # 不显示的字段列表
"alters": { # 需要改变的字段属性
"field1":{ # 例子将字段field1改为选项输入
"uitype":"code", # 设置输入类型为"code"
# uitype类型也可以用dataurl设置一个url从服务器获取数据
# 如果设置了打他URL datamethod和dataparams可选
"data":[ # 设置选项数据内容
{
"value":"v1", # value是数据值
"text": "显示项"# text是此值对应的显示内容
},
...
]
}
}
},
"editexclouded": [ # 编辑时不包含的字段列表
],
"subtables":[ # 可选,外键清单(表中存在字段指向本表主键记录)
{
"field" # 外键表字段
"title" # 可选,不选用外键表的表标题
"url" # 当alias定义时需要用{{entire_url(...)方式定义
指向表的url
"subtable" # 外键表名称
}
]
}
}
## 树形结构的数据表crud
当一张数据表中存在一个父节点的字段指向本表中另条记录的主键id,那么这个表就是一个树形>结构的表
树形机构表的crud的json数据说明
{
"tblname" # 表名
"alias" # 别名当需多个crud界面时可用alias来生成份不同的功能
"uitype": "tree"# 给定uitype为"tree"指定用树状结构数据crud
"title" # 标题,如果不给定,使用数据表定义中的表标题
"params":{ # crud 的参数
"idField": # 必须指定树节点的id字段
"textField" # 指定树节点显示内容字段
"sortby" # 指定排序字段,可以多个字段,用[a desc, b]形式>给出按照多个字段排序,"desc“表示倒排不给定就是正排
"confidential_fields" # 可选,敏感字段名数组,如果表中有敏感字段,要填写
"browserfields": { # 列表显示的参数
"alters": {} # 需要改变的字段属性
}
"logined_userorgid" # 可选如果表中有机构编码id并且需要按照机构过滤
"logined_userid" # 可选如果表中有用户id并且需要登录用户过滤
"editable" # true代表可编辑false表示不可编辑通常给定true
"edit_exclouded_fields" # 设置不参与编辑(新增和修改)字段,数组
"parentField" # 父节点字段
"subtables":[ # 外键清单(表中存在字段指向本表主键记录)
{
"field" # 外键表字段
"title" # 可选,不选用外键表的表标题
"url" # 当alias定义时需要用{{entire_url(...)方式定义
指向表的url
"subtable" # 外键表名称
}
],
}
}
from sqlor.dbpools import DBPools
from ahserver.serverenv import ServerEnv
# 假设当前模块名称为"mymodule"
async def subcoro(sor, pid):
sql = "select * from appcoodes_kw where parentid=${pid}$"
r = await sor.sqlExe(sql, {'pid': pid})
return r
“”“
sor.R()
sor.sqlExe() 的select语句
如果ns中有page'属性,返回数据格式如下
{
"total" # 查询结果总记录数
"rows": page指定的页数据 缺省每页返回80条记录pagerows属性可设置每页记录数
}
否则返回全部记录的数组
"""
async def sqlor_op():
db = DBPools()
env = ServerEnv()
dbname = env.get_module_dbname()
async with db.sqlorContext(dbname) as sor:
id = env.uuid()
# 事务中如果代码或sql失败全部滚回正常结束自动提交
await sor.C('user', {'id':id, 'username':'john'})
# 添加数据表“user”数据
await sor.D('user', {'id': 'yuewfiuwe'})
# await sor.U('user', {'id', 'email':'test@abc.com'})
# await sor.R('user', {'id': 'yuewfiuwe'})
return subcoro(sor, 'test_data')
# 前端设计要求
使用bricks框架前台设计完成界面的每个控件的设计
每个控件设计产出内容:
1 界面源码文件名,以".ui“结束
2 控件名称
3 构造参数
4 事件处理
5 无法用现有控件实现的功能请用bricks的控件扩展方法为bricks扩展控件文件保存在wwwroot目录下
# bricks框架简介
## 目录
* bricks目标
* bricks概念
* bricks开发方法
* bricks运行
## bricks目标
* 无前端代码或极少代码
* 降低前端开发技术难度
* 数据驱动
* 常用控件包装
* 纯json开发
## bricks概念
* 控件与控件继承
* 事件以及事件处理
* 控件嵌套和页面组装
### 控件与控件继承
bricks采用控件这一概念来描述web GUI的显示部件每个控件均映射到一个html
的标签类型的一个javascript类。每个控件均可以实例化并可在页面显示。
控件分为:基本控件,容器控件。控件有内置方法,也能触发事件。
* 基本控件
基本控件是一个原子控件,不能有子控件。
* 容器控件
容器控件可以有子控件bricks通过在容器控件添加子控件以及在子容器控件中
在添加子子控件的方式来构造复杂的web页面。
bricks已实现的控件请参看[控件清单](widgets.md)
### 控件扩展
如果现有的控件没法满足系统要求bricks支持控件扩展控件扩展需遵守
* 控件class继承自某一个控件的class
* 按照需求实现控件逻辑
* 在需要的地方用this.dispatch触发此控件的事件
假设需要扩展一个名字叫ExtContainer的控件
```
bricks.ExtContainer = class extends bricks.VBox {
constructor(opts){
super(opts);
/* 新控件的创建代码 */
}
......
/* 对象的其他方法在需要的时候在某个方法中使用this.dispatch('new_event', data)方法引发事件 */
}
bricks.register('ExtContainer', bricks.ExtContainer); /* 注册新控件 */
```
新控件的使用example.ui
```
{
"widgettype":"ExtContainer",
"options":{
....
},
"subwidgets":[
...
],
"binds":[
{
"wid":"self",
"event":"new_event",
"actiontype":"urlwidget",
"target":"some_container",
"options":{
"url":"{{entire_url('./some_ui.ui')}}"
}
}
]
}
```
### 事件以及事件处理
每个控件都能触发所映射dom元素的事件以及控件js类的成员函数以及祖先类的
成员函数中dispatch出的事件
所以bricks控件的事件来源于两类dom元素原生事件以及控件类中自定义的事件。
两类事件处理方式相同。
### 控件表达形式
在服务器的后台以json文件的形式表达控件每个ui文件定义一个控件
对于容器控件可以在ui文件中的subwidgets子属性中为此控件添加子控件
#### id属性
字符串属性定义控件的id让控件可以用getWidgetById找到如果不给定系统会自动生成一个id
#### options属性
字典属性,创建控件时的选项,每个控件可接受的选项请参看控件选项说明
#### binds属性
数组属性定义零到多个事件响应每个bind字典需要遵守[事件](event.md)要求
#### 容器控件特有属性
##### subwidgets
数组属性,定义容器控件的子控件,每个元素定义一个子控件,子控件遵守控件的数据要素要求
## 应用开发开发
使用存放在服务器后台的.ui后缀的json格式文件来开发每个.ui文件定义一个控件 支持基本控件和容器空间。
关于如何书写ui文件请参考[UI文件格式](descjson.md)
## 调试
ui文件可以直接调试如在服务器根目录下的test目录下有一个hello.ui文件
就可以在浏览器中用urlhttps://sername/test/hello.ui调试
# bricks控件
bricks内置许多的显示控件所有显示控件都继承自JsWidget容器控件Layout就继承自JsWidget其他的容器HBox VBox继承自Layout
## 基础控件
* [Form](form.md):输入表单控件
自动根据options中的fields数组下的每个field的uitype属性值构造表单输入项目前uitype属性支持的数据类型有
** 'str' 对应的控件为: bricks.UiStr
** 'hide' 对应的控件为: bricks.UiHide
** 'tel' 对应的控件为: bricks.UiTel
** 'date' 对应的控件为: bricks.UiDate
** 'int' 对应的控件为: bricks.UiInt
** 'float' 对应的控件为: bricks.UiFloat
** 'check' 对应的控件为: bricks.UiCheck
** 'checkbox' 对应的控件为: bricks.UiCheckBox
** 'email' 对应的控件为: bricks.UiEmail
** 'file' 对应的控件为: bricks.UiFile
** 'image' 对应的控件为: bricks.UiImage
** 'code' 对应的控件为: bricks.UiCode
** 'text' 对应的控件为: bricks.UiText
** 'password' 对应的控件为: bricks.UiPassword
** 'audio' 对应的控件为: bricks.UiAudio
** 'video' 对应的控件为: bricks.UiVideo
上述控件都在[输入定义](inout.js)中注册为输入项控件
* [Accordion](accordion.md) bricks.Accordion
手风琴控件,支持多个标题,内容组成的控件,内容和展开和折叠
* [AudioPlayer](audio.md) bricks.AudioPlayer
音频播放控件
* [ChartBar](bar.md) bricks.ChartBar
将后台数据显示为条形图表
* [Button](button.md) bricks.Button
按钮控件
* [Cols](cols.md) bricks.Cols
列式排列控件,可动态填满父控件的宽度
* [Conform](conform.md) bricks.Conform
确认控件,弹出窗口显示内容,并要求用户确认
* [Countdown](countdown.md) bricks.Countdown
时间倒计时控件,显示从还剩下的时间
* [TimePassed](countdown.md) bricks.TimePassed
时间消耗控件,显示从开始计时开始所消耗的时间
* [DataGrid](datagrid.md) bricks.DataGrid
数据表格控件
* [DataRow](datarow.md) bricks.DataRow
数据行控件
* [DataViewer](dataviewer.md) bricks.DataViewer
数据显示控件DynamicColumn控件的后代控件
* [DOCXviewer](docxviewer.md) bricks.DOCXviewer
docx文件显示控件
* [EXCELviewer](docxviewer.md) bricks.EXCELviewer
excel文件显示控件
* [PDFviewer](accordion.md) bricks.PDFviewer
pdf显示控件
* [DynamicAccordion](dynamicaccordion.md) bricks.DynamicAccordion
动态手风琴控件
* [IconBar](floaticonbar.md) bricks.IconBar
图标条控件
* [IconTextBar](floaticonbar.md) bricks.IconTextBar
图标文本条控件
* [FloatIconBar](floaticonbar.md) bricks.FloatIconBar
浮动图标条,平时显示一个标识图标,点击此标识图标后显示图标条
* [FloatIconTextBar](floaticonbar.md) bricks.FloatIconTextBar
浮动图标正文条,平时显示一个标识图标,点击此图标后显示图标正文条
* [Html](html.md) bricks.Html
HTML控件直接显示html内容
* [IconbarPage](iconbarpage.md) bricks.IconbarPage
图标条页控件,显示图标条,不同的图标点击后显示图标对应的内容
* [NewWindow](iframe.md) bricks.NewWindow
新浏览器页签或窗口控件浏览器创建新的窗口或页签显示url的内容
* [Iframe](iframe.md) bricks.Iframe
Iframe控件用于显示外部网站内容
* [Image](image.md) bricks.Image
图像控件
* [StatedIcon](image.md) bricks.StatedIcon
多站台图标,点击后状态改变,支持多个状态,并发出状态改变事件
* [Icon](image.md) bricks.Icon
图标控件支持多种图像格式url
* [BlankIcon](image.md) bricks.BlankIcon
空白图标占位控件
* [ChartLine](line.md) bricks.ChartLine
* [LlmIO](llm.md) bricks.LlmIO
* [LlmOut](llm.md) bricks.LlmOut
* [MarkdownViewer](markdownviewer.md) bricks.MarkdownViewer
* [MdWidget](markdownviewer.md) bricks.MdWidget
* [Menu](menu.md) bricks.Menu
* [Message](message.md) bricks.Message
* [Error](message.md) bricks.Error
* [MultipleStateImage](multiple_state_image.md) bricks.MultipleStateImage
多状态图像控件
* [PeriodDays](period.md) bricks.PeriodDays
日期期间控件,自动计算时间段的起始日期
* [ChartPie](pie.md) bricks.ChartPie
饼图控件基于echrts
* [ProgressBar](progressbar.md) bricks.ProgressBar
进度条控件
* [SysCamera](recorder.md) bricks.SysCamera
照相控件,可拍摄照片
* [WidgetRecorder](recorder.md) bricks.WidgetRecorder
控件视频录制控件,可录制浏览器播放的视频
* [SysAudioRecorder](recorder.md) bricks.SysAudioRecorder
浏览器音频录制控件,用来录制音频
* [SysVideoRecorder](recorder.md) bricks.SysVideoRecorder
浏览器视频录制控件,用来录制视频
* [Running](running.md) bricks.Running
运行图标控件modal模式显示正在运行相关控件不可操作需要在完成 任务后dismiss它
* [Splitter](splitter.md) bricks.Splitter
分割器控件,显示水平或垂直分割线
* [Svg](svg.md) bricks.Svg
Svg图标控件
* [StatedSvg](svg.md) bricks.StatedSvg
带状态的svg图标控件不同状态显示不同的图标
* [MultipleStateIcon](svg.md) bricks.MultipleStateIcon
多状态图标控件
* [TabPanel](tab.md) bricks.TabPanel
页签控件
* [Tabular](tabular.md) bricks.Tabular
数据列表形式的数据维护控件,支持数据的显示,增加,修改和删除
[xls2ddl](https://git.opencomputing.cn/yumoqing/xls2ddl)工具能根据数据表结构自动生成数据Tabular控件以及相关的数据维护dspy
* [Toolbar](toolbar.md) bricks.Toolbar
工具条控件
* [Tree](tree.md) bricks.Tree
树形控件
* [VadText](vadtext.md) bricks.VadText
自动捕获语音并将捕获的语音发送给服务器
* [VideoPlayer](videoplayer.md) bricks.VideoPlayer
视频播放控件支持浏览器支持的视频格式外还支持m3u8流媒体和Dash流媒体,
bricks已在3parties目录中包含了所依赖的hls和dash包
* [Video](videoplayer.md) bricks.VideoPlayer
视频播放控件同VideoPlayer
* [WebSocket](websocket.md) bricks.WebSocket
支持websocket
* [WebTTS](webspeech.js.md) bricks.WebTTS
为完成控件,浏览器内置文本转语音能力
* [WebASR](webspeech.js.md) bricks.WebASR
未完成控件,浏览器内部的语音识别能力
* [Tooltip](widget.md) bricks.Tooltip
Tooltip控件不直接创建而是在控件中添加“tip":"提示字符串“属性为控件添加Tooltip控件
* [Text](widget.md) bricks.Text
文本控件
* [Title1](widget.md) bricks.Title1
第一号标题
* [Title2](widget.md) bricks.Title2
第二号标题
* [Title3](widget.md) bricks.Title3
第三号标题
* [Title4](widget.md) bricks.Title4
第四号标题
* [Title5](widget.md) bricks.Title5
第五号标题
* [Title6](widget.md) bricks.Title6
第六号标题
* [Wterm](wterm.md) bricks.Wterm
xterm.js在bricks中的实现
## 容器类控件
* [VScrollPanel](accordion.md) bricks.VScrollPanel
垂直滚动容器,需要设置固定的高度或占满全部父容器高度
* [HScrollPanel](accordion.md) bricks.HScrollPanel
水平滚动容器,需要设置固定的宽度或占满全部父容器宽度
* [Popup](accordion.md) bricks.Popup
弹出容器,置于当前全部控件最上面
* [PopupWindow](accordion.md) bricks.PopupWindow
弹出窗口,置于当前全部控件最上面
* [HBox](accordion.md) bricks.HBox
水平扩展容器,全部子控件水平排放
* [VBox](accordion.md) bricks.VBox
垂直扩展容器,全部子控件垂直排放
* [Filler](accordion.md) bricks.Filler
占满父容器剩余控件如果父容器有多个Filler控件则平均分配剩余控件Filler容器下可添加子控件
* [DynamicColumn](dynamiccolumn.md) bricks.DynamicColumn
子控件需要设置固定宽度,动态从左到右,从上到下排列子控件
* [ResponsableBox](layout.md) bricks.ResponsableBox
自适应容器,当宽度大则水平排列子控件,而高度大时则水平排列子控件, 并能根据宽高变化自动改变。
* [Modal](modal.md) bricks.Modal
modal容器
dspy脚本可用的变量
ahserver/globalEnv.py程序中推送到ServerEnv中的变量
各个模块init_mymodule函数推送的变量
request
```
info(f'{params_kw=}, {request.url=}, {request.path=}')
def vcode():
codes = [ str(random.randint(0, 10)) for i in range(6) ]
return ''.join(codes)
db = DBPools()
async with db.sqlorContext('sage') as sor:
ns = {
'id':params_kw.codeid,
'code':vcode()
}
r = await sor.R('validatecode', {'id':params_kw.codeid})
if len(r) == 0:
await sor.C('validatecode', ns.copy())
else:
ns = r[0]
ns = DictObject(**ns)
x = await sms_engine.send_validate_code(params_kw.cell_no, ns.code)
info(f'{params_kw.cell_no}, {ns.code=}, send_validatecode() return {x}')
return UiMessage(title='SMS', message=f'validate code send to {params_kw.cell_no}')
return UiError(title='Error', message=f'{params_kw.cell_no} generate validate code error')
```
## ui文件例子
```
{
"widgettype":"VBox",
"options":{
"width":"100%",
"height":"100%"
},
"subwidgets":[
{
"widgettype":"Filler",
"options":{},
"subwidgets":[
{
"id":"dialog",
"widgettype":"LlmDialog",
"options":{
"models":[{
"url":"{{entire_url('llm/doubao.llm')}}",
"mapi":"chat",
"icon":"{{entire_url('imgs/doubao.png')}}",
"model":"ep-20240614051803-shld5"
}
]
}
}
]
},
{
"widgettype":"HBox",
"options":{
"cheight":5
},
"subwidgets":[
{
"id":"prompt",
"widgettype":"UiAudioText",
"options":{
"css":"filler",
"upload_url":"https://sage.opencomputing.cn/stt/generate",
"name":"prompt"
}
},
{
"widgettype":"IconBar",
"id":"prompt_bar",
"options":{
"tools":[
{
"name":"submit",
"icon":"{{entire_url('imgs/submit.png')}}"
}
]
}
}
]
}
],
"binds":[
{
"wid":"prompt_bar",
"event":"submit",
"actiontype":"script",
"target":"dialog",
"script":"console.log(params); this.set_prompt(params.prompt);",
"datawidget":"prompt",
"datamethod":"getValue"
},
{
"wid":"prompt_bar",
"event":"submit",
"actiontype":"script",
"target":"prompt",
"script":"this.setValue('')"
}
]
}
```

Binary file not shown.

712
req/txt Normal file
View File

@ -0,0 +1,712 @@
定价模块,要求
1orgid机构id用来隔离不同机构
2每个定价按有效时间期间可以有多套定价但一个资源某一天只有一套定价
3如果一个资源是其他一到多个资源作为此资源的部件其部件的定价直接引用原资源定价而非重新定义
4设计定价类型设计一套计费规则针对具体资源只需按照规则给定成本和价格
5要支持tokens消耗类型的计费100万/K为计费单位包年包月模式的计费
模块名字pricing
要求生成一键生成整个模块目录及文件的shell.sh
按照以下规范要求:
## 模块目录结构
模块名称为“mymodule", 以下为mymodule模块的目录文件结构
```
mymodule
+--mymodule # 存放模块的主要逻辑py代码
+--init.py # 模块初始化脚本需要定义一个load_mymodule()函数此函数将在ui和dspy文件中用到的模块变量通过ServerEnv实例传过去
+--__init__.py #python模块所需
|
| +--其他源码.py # 模块中需要的其他py源码文件
+--wwwroot # 目录,存放以.ui和.dspy结束的文件ui文件支持jinja2模板前端控件文件内容为json格式的控件描述文本 dspy是ahserver支持的受限python脚本可以按照需要设置下级目录
+--models # 存放数据表文件的目录
+--json # 存放CRUD描述文件的目录
+--pyproject.toml # pip打包文件, 其中项目名字与mymodule相同
+--README.md # 模块自说明文件
+--init # 模块初始化目录
+--data.xlsx # 初始化数据一个sheet一个表一行一个记录第一行为字>段名
+--script.py # 初始化脚本
```
## 要求
* 模块中的主要逻辑用py源码实现放在mymodule目录下
* 客户端业务功能放在wwwroot目录下以.ui和.dspy后缀结束用.ui文件遵守jinja2规范, 而.dspy文件是一个受控的脚本
* .dspy中不允许import模块
* 假设模块名为”mymodule“
## mymodule目录
“mymodule”为模块名称 模块目录下存放以py结束的代码文件其中init.py文件为必须
在此目录下的代码中可以通过ahserver.serverenv模块的ServerEnv来引用其他模块的放进来的变量
### init.py的主要内容
```
from ahserver.serverenv import ServerEnv
from appPublic.worker import awaitify
from .x import a, b, c # 从同目录下的源码x.py中import ab, c是协程
from .y import x, y # 从同目录下源码y.py中import x, y是普通函数
def load_mymodule(): # mymodule需替换为实际的模块名字
env = ServerEnv()
env.a = a # 脚本中用"a" 调用"a"
env.b = b # 脚本中用"b"调用"b"
env.cc = c # 脚本中用"cc"调用"c"
env.y = awaitify(y) # 将函数包装为协程
env.x = awaitify(x) # 将函数包装为协程
```
注释这里的load_mymodule中的mymodule是模块名字而不是“mymodule”本身
模块的其他代码都需要放在这个目录中而模块中所有需要在uidspy脚本中用到的变量均需要通过init.py函数的load_mymodule()函数传递
## ui, dspy可以直接使用的变量
### request
aiohttp.Request实例每个客户端请求有一个独立的Request实例
request._run_ns可以获得所有在.ui和.dspy源码中可以使用的变量通过ahserver.ServerEnv 传递
### params_kw
DictObjectdict子类, 支持a.b方式获取属性实例接收到客户端传来的数据如果有文件>文件都保存在服务器指定的位置params_kw中属性名保存的是其相对路径可用FileStorage().realPath(params_kw.myfile)来获得文件在服务器中的实际路径。
### get_user()
来自rbac模块协程函数获得当前登录用户如果用户没有登录返回None
### get_userorgid()
来自rbac模块协程函数获得当前登录用户的机构id如果用户没有登录返回None
### 各个模块通过load_xxxx()放到ServerEnv()中的变量
XXXX是模块名称
## pyproject.toml和README.md
编写pyproject.toml文件和README.md文件
## 模块中使用的编码
模块中如果用到编码编码需保存在appbase模块的appcodes表和appcodes_kv两个表中
appcodes(编码表)有如下字段:
[
"id" # str 32 主键,可设置为数据表字段名称
"name" # 编码名称
"hierarchy_flg", # str 1, '0':单级编码“1”:多级编码
]
appcodes_kv编码键值表字段如下
[
"id", # str, 32,主键,唯一值
"parentid" # str, 32, 一级编码为appcodes表的id, 否则为上级编码键值记录的id
“k" # str 32, 编码值
“v” “ str 255,编码显示文本
]
## 数据库表定义规范
模块中需要定义数据表要遵守一下规范
数据库表用一个json格式文件或数据来定义具体规范如下
{
"summary":[ # 仅一条记录
{
"name" # 表名
"title" # 表标题
"primary" # 主键=“id”, 所有表均以id为主键
"catelog" # 可选项entity relation dimession indication
}
],
"fields":[ # 字段
{
"name"
"title"
"type" #可选项有str char short long llong date time timestamp float double ddouble decimal text
"length"
"dec"
"nullable" # 可选项yes no
"default"
"comments" # 注释
}
]
"indexes":[
{
"name" # 每个索引一个idxname不能重复一个表中
"idxtype" # unique or index
"idxfields" # 字段名或字段名数组[f1,f1]
}
]
“codes":[ # 如果一个字段数据可以从其他表中获得,可以通过下面的模式定义选择输入逻
{
"field" # 字段的名字
"table" # 数据来源表
"valuefield" # 数据来源表值字段
"textfield" # 数据来源表显示字段
"cond" # 检索条件
}
]
]
说明:
id字段全部使用str 32类型
字典中的length如果type是str char float double ddouble decimal中的一个则必为>0的>数字
字典中的dec: 如果type是float double ddouble decimal中的一个则必须是>0的数字
## 列表形式的数据表crud定义
当一张数据表不是树形结构的就是表形结构的crud
表形机构表的crud的json说明
{
"tblname" # 表名
"alias" # 别名当需多个crud界面时可用alias来生成份不同的功能
"title" # 标题,如果不给定,使用数据表定义中的表标题
"params":{ # crud 的参数
"sortby" # 指定排序字段,可以多个字段,用["a desc", "b"]>形式给出按照多个字段排序,"desc“表示倒排不给定就是正排
"logined_userorgid" # 可选如果表中有机构编码id并且需要按照机构过滤
"logined_userid" # 可选如果表中有用户id并且需要登录用户过滤
"confidential_fields" # 可选,敏感字段名数组,如果表中有敏感字段,要填写
"editor":{ # 给定编辑时form初始化参数
# 需要参考bricks的Form控件
"binds":[ # 比如添加事件处理
{
"wid":"province_id", # 指定form的事件名
"event":"changed", # 事件,比如数据变化
"actiontype":"script", # 事件处理类型
"target":"city_id", # 目标控件
"script": # 脚本内容规定用js脚本
}
]
},
"browserfields": { # 列表显示的参数
"exclouded": ["id"], # 不显示的字段列表
"alters": { # 需要改变的字段属性
"field1":{ # 例子将字段field1改为选项输入
"uitype":"code", # 设置输入类型为"code"
# uitype类型也可以用dataurl设置一个url从服务器获取数据
# 如果设置了打他URL datamethod和dataparams可选
"data":[ # 设置选项数据内容
{
"value":"v1", # value是数据值
"text": "显示项"# text是此值对应的显示内容
},
...
]
}
}
},
"editexclouded": [ # 编辑时不包含的字段列表
],
"subtables":[ # 可选,外键清单(表中存在字段指向本表主键记录)
{
"field" # 外键表字段
"title" # 可选,不选用外键表的表标题
"url" # 当alias定义时需要用{{entire_url(...)方式定义
指向表的url
"subtable" # 外键表名称
}
]
}
}
## 树形结构的数据表crud
当一张数据表中存在一个父节点的字段指向本表中另条记录的主键id,那么这个表就是一个树形>结构的表
树形机构表的crud的json数据说明
{
"tblname" # 表名
"alias" # 别名当需多个crud界面时可用alias来生成份不同的功能
"uitype": "tree"# 给定uitype为"tree"指定用树状结构数据crud
"title" # 标题,如果不给定,使用数据表定义中的表标题
"params":{ # crud 的参数
"idField": # 必须指定树节点的id字段
"textField" # 指定树节点显示内容字段
"sortby" # 指定排序字段,可以多个字段,用[a desc, b]形式>给出按照多个字段排序,"desc“表示倒排不给定就是正排
"confidential_fields" # 可选,敏感字段名数组,如果表中有敏感字段,要填写
"browserfields": { # 列表显示的参数
"alters": {} # 需要改变的字段属性
}
"logined_userorgid" # 可选如果表中有机构编码id并且需要按照机构过滤
"logined_userid" # 可选如果表中有用户id并且需要登录用户过滤
"editable" # true代表可编辑false表示不可编辑通常给定true
"edit_exclouded_fields" # 设置不参与编辑(新增和修改)字段,数组
"parentField" # 父节点字段
"subtables":[ # 外键清单(表中存在字段指向本表主键记录)
{
"field" # 外键表字段
"title" # 可选,不选用外键表的表标题
"url" # 当alias定义时需要用{{entire_url(...)方式定义
指向表的url
"subtable" # 外键表名称
}
],
}
}
## 数据库操作规范
from sqlor.dbpools import DBPools
from ahserver.serverenv import ServerEnv
# 假设当前模块名称为"mymodule"
async def subcoro(sor, pid):
sql = "select * from appcoodes_kw where parentid=${pid}$"
r = await sor.sqlExe(sql, {'pid': pid})
return r
“”“
sor.R()
sor.sqlExe() 的select语句
如果ns中有page'属性,返回数据格式如下
{
"total" # 查询结果总记录数
"rows": page指定的页数据 缺省每页返回80条记录pagerows属性可设置每页记录数
}
否则返回全部记录的数组
"""
async def sqlor_op():
db = DBPools()
env = ServerEnv()
dbname = env.get_module_dbname()
async with db.sqlorContext(dbname) as sor:
id = env.uuid()
# 事务中如果代码或sql失败全部滚回正常结束自动提交
await sor.C('user', {'id':id, 'username':'john'})
# 添加数据表“user”数据
await sor.D('user', {'id': 'yuewfiuwe'})
# await sor.U('user', {'id', 'email':'test@abc.com'})
# await sor.R('user', {'id': 'yuewfiuwe'})
return subcoro(sor, 'test_data')
# bricks框架简介
## 目录
* bricks目标
* bricks概念
* bricks开发方法
* bricks运行
## bricks目标
* 无前端代码或极少代码
* 降低前端开发技术难度
* 数据驱动
* 常用控件包装
* 纯json开发
## bricks概念
* 控件与控件继承
* 事件以及事件处理
* 控件嵌套和页面组装
### 控件与控件继承
bricks采用控件这一概念来描述web GUI的显示部件每个控件均映射到一个html
的标签类型的一个javascript类。每个控件均可以实例化并可在页面显示。
控件分为:基本控件,容器控件。控件有内置方法,也能触发事件。
* 基本控件
基本控件是一个原子控件,不能有子控件。
* 容器控件
容器控件可以有子控件bricks通过在容器控件添加子控件以及在子容器控件中
在添加子子控件的方式来构造复杂的web页面。
bricks已实现的控件请参看[控件清单](widgets.md)
### 控件扩展
如果现有的控件没法满足系统要求bricks支持控件扩展控件扩展需遵守
* 控件class继承自某一个控件的class
* 按照需求实现控件逻辑
* 在需要的地方用this.dispatch触发此控件的事件
假设需要扩展一个名字叫ExtContainer的控件
```
bricks.ExtContainer = class extends bricks.VBox {
constructor(opts){
super(opts);
/* 新控件的创建代码 */
}
......
/* 对象的其他方法在需要的时候在某个方法中使用this.dispatch('new_event', data)方法引发事件 */
}
bricks.register('ExtContainer', bricks.ExtContainer); /* 注册新控件 */
```
新控件的使用example.ui
```
{
"widgettype":"ExtContainer",
"options":{
....
},
"subwidgets":[
...
],
"binds":[
{
"wid":"self",
"event":"new_event",
"actiontype":"urlwidget",
"target":"some_container",
"options":{
"url":"{{entire_url('./some_ui.ui')}}"
}
}
]
}
```
### 事件以及事件处理
每个控件都能触发所映射dom元素的事件以及控件js类的成员函数以及祖先类的
成员函数中dispatch出的事件
所以bricks控件的事件来源于两类dom元素原生事件以及控件类中自定义的事件。
两类事件处理方式相同。
### 控件表达形式
在服务器的后台以json文件的形式表达控件每个ui文件定义一个控件
对于容器控件可以在ui文件中的subwidgets子属性中为此控件添加子控件
#### id属性
字符串属性定义控件的id让控件可以用getWidgetById找到如果不给定系统会自动生成一个id
#### options属性
字典属性,创建控件时的选项,每个控件可接受的选项请参看控件选项说明
#### binds属性
数组属性定义零到多个事件响应每个bind字典需要遵守[事件](event.md)要求
#### 容器控件特有属性
##### subwidgets
数组属性,定义容器控件的子控件,每个元素定义一个子控件,子控件遵守控件的数据要素要求
## 应用开发开发
使用存放在服务器后台的.ui后缀的json格式文件来开发每个.ui文件定义一个控件 支持基本控件和容器空间。
关于如何书写ui文件请参考[UI文件格式](descjson.md)
## 调试
ui文件可以直接调试如在服务器根目录下的test目录下有一个hello.ui文件
就可以在浏览器中用urlhttps://sername/test/hello.ui调试
# bricks控件
bricks内置许多的显示控件所有显示控件都继承自JsWidget容器控件Layout就继承自JsWidget其他的容器HBox VBox继承自Layout
## 基础控件
* [Form](form.md):输入表单控件
自动根据options中的fields数组下的每个field的uitype属性值构造表单输入项目前uitype属性支持的数据类型有
** 'str' 对应的控件为: bricks.UiStr
** 'hide' 对应的控件为: bricks.UiHide
** 'tel' 对应的控件为: bricks.UiTel
** 'date' 对应的控件为: bricks.UiDate
** 'int' 对应的控件为: bricks.UiInt
** 'float' 对应的控件为: bricks.UiFloat
** 'check' 对应的控件为: bricks.UiCheck
** 'checkbox' 对应的控件为: bricks.UiCheckBox
** 'email' 对应的控件为: bricks.UiEmail
** 'file' 对应的控件为: bricks.UiFile
** 'image' 对应的控件为: bricks.UiImage
** 'code' 对应的控件为: bricks.UiCode
** 'text' 对应的控件为: bricks.UiText
** 'password' 对应的控件为: bricks.UiPassword
** 'audio' 对应的控件为: bricks.UiAudio
** 'video' 对应的控件为: bricks.UiVideo
上述控件都在[输入定义](inout.js)中注册为输入项控件
* [Accordion](accordion.md) bricks.Accordion
手风琴控件,支持多个标题,内容组成的控件,内容和展开和折叠
* [AudioPlayer](audio.md) bricks.AudioPlayer
音频播放控件
* [ChartBar](bar.md) bricks.ChartBar
将后台数据显示为条形图表
* [Button](button.md) bricks.Button
按钮控件
* [Cols](cols.md) bricks.Cols
列式排列控件,可动态填满父控件的宽度
* [Conform](conform.md) bricks.Conform
确认控件,弹出窗口显示内容,并要求用户确认
* [Countdown](countdown.md) bricks.Countdown
时间倒计时控件,显示从还剩下的时间
* [TimePassed](countdown.md) bricks.TimePassed
时间消耗控件,显示从开始计时开始所消耗的时间
* [DataGrid](datagrid.md) bricks.DataGrid
数据表格控件
* [DataRow](datarow.md) bricks.DataRow
数据行控件
* [DataViewer](dataviewer.md) bricks.DataViewer
数据显示控件DynamicColumn控件的后代控件
* [DOCXviewer](docxviewer.md) bricks.DOCXviewer
docx文件显示控件
* [EXCELviewer](docxviewer.md) bricks.EXCELviewer
excel文件显示控件
* [PDFviewer](accordion.md) bricks.PDFviewer
pdf显示控件
* [DynamicAccordion](dynamicaccordion.md) bricks.DynamicAccordion
动态手风琴控件
* [IconBar](floaticonbar.md) bricks.IconBar
图标条控件
* [IconTextBar](floaticonbar.md) bricks.IconTextBar
图标文本条控件
* [FloatIconBar](floaticonbar.md) bricks.FloatIconBar
浮动图标条,平时显示一个标识图标,点击此标识图标后显示图标条
* [FloatIconTextBar](floaticonbar.md) bricks.FloatIconTextBar
浮动图标正文条,平时显示一个标识图标,点击此图标后显示图标正文条
* [Html](html.md) bricks.Html
HTML控件直接显示html内容
* [IconbarPage](iconbarpage.md) bricks.IconbarPage
图标条页控件,显示图标条,不同的图标点击后显示图标对应的内容
* [NewWindow](iframe.md) bricks.NewWindow
新浏览器页签或窗口控件浏览器创建新的窗口或页签显示url的内容
* [Iframe](iframe.md) bricks.Iframe
Iframe控件用于显示外部网站内容
* [Image](image.md) bricks.Image
图像控件
* [StatedIcon](image.md) bricks.StatedIcon
多站台图标,点击后状态改变,支持多个状态,并发出状态改变事件
* [Icon](image.md) bricks.Icon
图标控件支持多种图像格式url
* [BlankIcon](image.md) bricks.BlankIcon
空白图标占位控件
* [ChartLine](line.md) bricks.ChartLine
* [LlmIO](llm.md) bricks.LlmIO
* [LlmOut](llm.md) bricks.LlmOut
* [MarkdownViewer](markdownviewer.md) bricks.MarkdownViewer
* [MdWidget](markdownviewer.md) bricks.MdWidget
* [Menu](menu.md) bricks.Menu
* [Message](message.md) bricks.Message
* [Error](message.md) bricks.Error
* [MultipleStateImage](multiple_state_image.md) bricks.MultipleStateImage
多状态图像控件
* [PeriodDays](period.md) bricks.PeriodDays
日期期间控件,自动计算时间段的起始日期
* [ChartPie](pie.md) bricks.ChartPie
饼图控件基于echrts
* [ProgressBar](progressbar.md) bricks.ProgressBar
进度条控件
* [SysCamera](recorder.md) bricks.SysCamera
照相控件,可拍摄照片
* [WidgetRecorder](recorder.md) bricks.WidgetRecorder
控件视频录制控件,可录制浏览器播放的视频
* [SysAudioRecorder](recorder.md) bricks.SysAudioRecorder
浏览器音频录制控件,用来录制音频
* [SysVideoRecorder](recorder.md) bricks.SysVideoRecorder
浏览器视频录制控件,用来录制视频
* [Running](running.md) bricks.Running
运行图标控件modal模式显示正在运行相关控件不可操作需要在完成 任务后dismiss它
* [Splitter](splitter.md) bricks.Splitter
分割器控件,显示水平或垂直分割线
* [Svg](svg.md) bricks.Svg
Svg图标控件
* [StatedSvg](svg.md) bricks.StatedSvg
带状态的svg图标控件不同状态显示不同的图标
* [MultipleStateIcon](svg.md) bricks.MultipleStateIcon
多状态图标控件
* [TabPanel](tab.md) bricks.TabPanel
页签控件
* [Tabular](tabular.md) bricks.Tabular
数据列表形式的数据维护控件,支持数据的显示,增加,修改和删除
[xls2ddl](https://git.opencomputing.cn/yumoqing/xls2ddl)工具能根据数据表结构自动生成数据Tabular控件以及相关的数据维护dspy
* [Toolbar](toolbar.md) bricks.Toolbar
工具条控件
* [Tree](tree.md) bricks.Tree
树形控件
* [VadText](vadtext.md) bricks.VadText
自动捕获语音并将捕获的语音发送给服务器
* [VideoPlayer](videoplayer.md) bricks.VideoPlayer
视频播放控件支持浏览器支持的视频格式外还支持m3u8流媒体和Dash流媒体,
bricks已在3parties目录中包含了所依赖的hls和dash包
* [Video](videoplayer.md) bricks.VideoPlayer
视频播放控件同VideoPlayer
* [WebSocket](websocket.md) bricks.WebSocket
支持websocket
* [WebTTS](webspeech.js.md) bricks.WebTTS
为完成控件,浏览器内置文本转语音能力
* [WebASR](webspeech.js.md) bricks.WebASR
未完成控件,浏览器内部的语音识别能力
* [Tooltip](widget.md) bricks.Tooltip
Tooltip控件不直接创建而是在控件中添加“tip":"提示字符串“属性为控件添加Tooltip控件
* [Text](widget.md) bricks.Text
文本控件
* [Title1](widget.md) bricks.Title1
第一号标题
* [Title2](widget.md) bricks.Title2
第二号标题
* [Title3](widget.md) bricks.Title3
第三号标题
* [Title4](widget.md) bricks.Title4
第四号标题
* [Title5](widget.md) bricks.Title5
第五号标题
* [Title6](widget.md) bricks.Title6
第六号标题
* [Wterm](wterm.md) bricks.Wterm
xterm.js在bricks中的实现
## 容器类控件
* [VScrollPanel](accordion.md) bricks.VScrollPanel
垂直滚动容器,需要设置固定的高度或占满全部父容器高度
* [HScrollPanel](accordion.md) bricks.HScrollPanel
水平滚动容器,需要设置固定的宽度或占满全部父容器宽度
* [Popup](accordion.md) bricks.Popup
弹出容器,置于当前全部控件最上面
* [PopupWindow](accordion.md) bricks.PopupWindow
弹出窗口,置于当前全部控件最上面
* [HBox](accordion.md) bricks.HBox
水平扩展容器,全部子控件水平排放
* [VBox](accordion.md) bricks.VBox
垂直扩展容器,全部子控件垂直排放
* [Filler](accordion.md) bricks.Filler
占满父容器剩余控件如果父容器有多个Filler控件则平均分配剩余控件Filler容器下可添加子控件
* [DynamicColumn](dynamiccolumn.md) bricks.DynamicColumn
子控件需要设置固定宽度,动态从左到右,从上到下排列子控件
* [ResponsableBox](layout.md) bricks.ResponsableBox
自适应容器,当宽度大则水平排列子控件,而高度大时则水平排列子控件, 并能根据宽高变化自动改变。
* [Modal](modal.md) bricks.Modal
modal容器
## ui文件例子
```
{
"widgettype":"VBox",
"options":{
"width":"100%",
"height":"100%"
},
"subwidgets":[
{
"widgettype":"Filler",
"options":{},
"subwidgets":[
{
"id":"dialog",
"widgettype":"LlmDialog",
"options":{
"models":[{
"url":"{{entire_url('llm/doubao.llm')}}",
"mapi":"chat",
"icon":"{{entire_url('imgs/doubao.png')}}",
"model":"ep-20240614051803-shld5"
}
]
}
}
]
},
{
"widgettype":"HBox",
"options":{
"cheight":5
},
"subwidgets":[
{
"id":"prompt",
"widgettype":"UiAudioText",
"options":{
"css":"filler",
"upload_url":"https://sage.opencomputing.cn/stt/generate",
"name":"prompt"
}
},
{
"widgettype":"IconBar",
"id":"prompt_bar",
"options":{
"tools":[
{
"name":"submit",
"icon":"{{entire_url('imgs/submit.png')}}"
}
]
}
}
]
}
],
"binds":[
{
"wid":"prompt_bar",
"event":"submit",
"actiontype":"script",
"target":"dialog",
"script":"console.log(params); this.set_prompt(params.prompt);",
"datawidget":"prompt",
"datamethod":"getValue"
},
{
"wid":"prompt_bar",
"event":"submit",
"actiontype":"script",
"target":"prompt",
"script":"this.setValue('')"
}
]
}
```
## dspy文件例子
dspy脚本可用的变量
ahserver/globalEnv.py程序中推送到ServerEnv中的变量
各个模块init_mymodule函数推送的变量
request
```
info(f'{params_kw=}, {request.url=}, {request.path=}')
def vcode():
codes = [ str(random.randint(0, 10)) for i in range(6) ]
return ''.join(codes)
db = DBPools()
async with db.sqlorContext('sage') as sor:
ns = {
'id':params_kw.codeid,
'code':vcode()
}
r = await sor.R('validatecode', {'id':params_kw.codeid})
if len(r) == 0:
await sor.C('validatecode', ns.copy())
else:
ns = r[0]
ns = DictObject(**ns)
x = await sms_engine.send_validate_code(params_kw.cell_no, ns.code)
info(f'{params_kw.cell_no}, {ns.code=}, send_validatecode() return {x}')
return UiMessage(title='SMS', message=f'validate code send to {params_kw.cell_no}')
return UiError(title='Error', message=f'{params_kw.cell_no} generate validate code error')
```